├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── xcshareddata
│ └── xcschemes
│ ├── TypedDate.xcscheme
│ ├── TypedDateCore.xcscheme
│ ├── TypedDateCoreTests.xcscheme
│ ├── TypedDateMacrosTests.xcscheme
│ ├── TypedDateTests.xcscheme
│ └── swift-composable-date.xcscheme
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── TypedDate
│ ├── Date+scope.swift
│ ├── Macro.swift
│ ├── TypedDate+Codable.swift
│ ├── TypedDate+Comparable.swift
│ ├── TypedDate+Hashable.swift
│ ├── TypedDate+components.swift
│ ├── TypedDate+erase.swift
│ ├── TypedDate+fill.swift
│ ├── TypedDate+init.swift
│ ├── TypedDate+modify.swift
│ ├── TypedDate.swift
│ ├── TypedDateOf.swift
│ ├── TypedDateProtocol.swift
│ └── exported.swift
├── TypedDateCore
│ ├── TypedDateUnits+BinaryInteger.swift
│ └── TypedDateUnits.swift
└── TypedDateMacros
│ ├── EraseContextMacro.swift
│ ├── FillInContextMacro.swift
│ ├── MacroPlugins.swift
│ └── ModifyContextMacro.swift
└── Tests
├── TypedDateMacrosTests
├── EraseContextMacroTests.swift
├── FillInContextMacroTests.swift
└── ModifyContextMacroTests.swift
└── TypedDateTests
├── Scaffolding.swift
├── TypedDateAddTests.swift
├── TypedDateCodableTests.swift
├── TypedDateComparableTests.swift
├── TypedDateComponentsTests.swift
├── TypedDateEquatableTests.swift
├── TypedDateEraseTests.swift
├── TypedDateFillTests.swift
├── TypedDateInitTests.swift
├── TypedDateModifyTests.swift
├── TypedDateScopeTests.swift
└── TypedDateTestSupport.swift
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | pull_request:
5 | branches: [ "main" ]
6 | paths-ignore:
7 | - README.md
8 | push:
9 | branches: [ "main" ]
10 | paths-ignore:
11 | - README.md
12 | workflow_dispatch:
13 |
14 | concurrency:
15 | group: format-${{ github.ref }}
16 | cancel-in-progress: true
17 |
18 | jobs:
19 | build:
20 | name: Test
21 | strategy:
22 | fail-fast: false
23 | matrix:
24 | os: [macos-13]
25 | runs-on: ${{ matrix.os }}
26 | steps:
27 | - name: Checkout
28 | uses: actions/checkout@v4
29 |
30 | - name: Select Xcode 15
31 | run: sudo xcode-select -s /Applications/Xcode_15.0.app
32 |
33 | - name: Test
34 | run: swift test
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/TypedDate.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
42 |
43 |
49 |
50 |
56 |
57 |
58 |
59 |
61 |
62 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/TypedDateCore.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
42 |
43 |
49 |
50 |
56 |
57 |
58 |
59 |
61 |
62 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/TypedDateCoreTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
38 |
39 |
45 |
46 |
48 |
49 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/TypedDateMacrosTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
38 |
39 |
45 |
46 |
48 |
49 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/TypedDateTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
38 |
39 |
45 |
46 |
48 |
49 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/swift-composable-date.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
58 |
64 |
65 |
66 |
68 |
74 |
75 |
76 |
78 |
84 |
85 |
86 |
87 |
88 |
98 |
99 |
105 |
106 |
112 |
113 |
114 |
115 |
117 |
118 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Ryu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-macro-testing",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/pointfreeco/swift-macro-testing",
7 | "state" : {
8 | "revision" : "35acd9468d40ae87e75991a18af6271e8124c261",
9 | "version" : "0.2.1"
10 | }
11 | },
12 | {
13 | "identity" : "swift-snapshot-testing",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing",
16 | "state" : {
17 | "revision" : "bb0ea08db8e73324fe6c3727f755ca41a23ff2f4",
18 | "version" : "1.14.2"
19 | }
20 | },
21 | {
22 | "identity" : "swift-syntax",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/apple/swift-syntax.git",
25 | "state" : {
26 | "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
27 | "version" : "509.0.2"
28 | }
29 | },
30 | {
31 | "identity" : "swift-testing",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/apple/swift-testing.git",
34 | "state" : {
35 | "revision" : "395288f1d154d0e8b92823313957546ddb88106c",
36 | "version" : "0.1.0"
37 | }
38 | }
39 | ],
40 | "version" : 2
41 | }
42 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 | import CompilerPluginSupport
6 |
7 | let package = Package(
8 | name: "swift-typed-date",
9 | platforms: [
10 | .macOS(.v10_15),
11 | .iOS(.v13),
12 | .tvOS(.v13),
13 | .watchOS(.v6),
14 | .macCatalyst(.v13)
15 | ],
16 | products: [
17 | // Products define the executables and libraries a package produces, making them visible to other packages.
18 | .library(
19 | name: "TypedDate",
20 | targets: ["TypedDate"]
21 | )
22 | ],
23 | dependencies: [
24 | // Depend on the Swift 5.9 release of SwiftSyntax
25 | .package(url: "https://github.com/apple/swift-syntax.git", "509.0.0"..<"510.0.0"),
26 | .package(url: "https://github.com/apple/swift-testing.git", from: "0.1.0"),
27 | .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.1")
28 | ],
29 | targets: [
30 | // Targets are the basic building blocks of a package, defining a module or a test suite.
31 | // Targets can depend on other targets in this package and products from dependencies.
32 | // Macro implementation that performs the source transformation of a macro.
33 | .macro(
34 | name: "TypedDateMacros",
35 | dependencies: [
36 | "TypedDateCore",
37 | .product(name: "SwiftSyntax", package: "swift-syntax"),
38 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
39 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax")
40 | ]
41 | ),
42 |
43 | // Library that exposes a macro as part of its API, which is used in client programs.
44 | .target(
45 | name: "TypedDate",
46 | dependencies: [
47 | "TypedDateCore",
48 | "TypedDateMacros"
49 | ]
50 | ),
51 | .target(
52 | name: "TypedDateCore",
53 | dependencies: [
54 | ]
55 | ),
56 |
57 | .testTarget(
58 | name: "TypedDateMacrosTests",
59 | dependencies: [
60 | "TypedDateMacros",
61 | "TypedDateCore",
62 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
63 | .product(name: "MacroTesting", package: "swift-macro-testing")
64 | ]
65 | ),
66 | .testTarget(
67 | name: "TypedDateTests",
68 | dependencies: [
69 | "TypedDate",
70 | .product(name: "Testing", package: "swift-testing"),
71 | ]
72 | )
73 | ]
74 | )
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TypedDate
2 |
3 | 
4 | 
5 | [](https://github.com/Ryu0118/swift-typed-date/releases/latest)
6 | [](https://swiftpackageindex.com/Ryu0118/swift-typed-date)
7 | [](https://twitter.com/ryu_hu03)
8 |
9 | TypedDate is a wrapper for Swift's Date that enhances date handling at the type level. It enables the construction of custom date structures, ranging from single components like year to combinations of year, month, day, time, etc., tailored to specific development requirements.
10 |
11 | Key features of TypedDate include:
12 |
13 | * **Explicit Clarity in Date Components**
14 |
Allows developers to specify precisely which date components (year, month, day, etc.) they are working with, leading to a better understanding and less likelihood of inconsistencies.
15 | * **Flexible Customization**
16 |
Enables the creation of date objects with different levels of detail, from a simple year-only structure to more comprehensive ones including month, day, second, and nanosecond.
17 | * **Modifiable Date Components**
18 |
Provides methods for modifying individual components such as year, month or day.
19 | * **Seamless Conversion**
20 |
Enables effortless conversion between 'TypedDate' and Swift's standard Date object, adapting to different scenarios by filling in any missing components as needed.
21 |
22 | ## Usage
23 | ### Initialization
24 | To initialize a TypedDate, you can use the following syntax:
25 |
26 | ```Swift
27 | import TypedDate
28 |
29 | TypedDate(Year(2023), Month(11), Day(12))
30 | TypedDate(Year(2023), Month(11), Day(12), calendar: Calendar(identifier: .gregorian))
31 | TypedDate(Year(2023), Month(11), Day(12), Hour(11), Minute(12), Second(1), Nanosecond(10000000))
32 | TypedDate(Year(2023), Month(11), Day(12), Hour(11), Minute(12), FractionalSecond(5.12))
33 | ```
34 | This will create a TypedDate instance representing the date 2023/11/12.
35 |
Date has the following components available: Year, Month, Day, Hour, Minute, Second, and Nanosecond.
36 |
37 | ### Date to TypedDate conversion
38 | To create a TypedDate from a Date, use `Date.scope(to:calendar:)`.
39 | ```Swift
40 | let typedDate1: TypedDate<(Year, Month)> = Date().scope(to: \.month)
41 | let typedDate2: TypedDate<(Year, Month, Day, Hour)> = Date().scope(to: \.hour)
42 | let typedDate3: TypedDate<(Year, Month, Day, Hour, Minute)> = Date().scope(to: \.minute, calendar: Calendar(identifier: .gregorian))
43 | let typedDate4: TypedDateOfYear = Date().scope(to: \.year)
44 | let typedDate5: TypedDateOfDay = Date().scope(to: \.day)
45 | ```
46 |
47 | ### TypedDate to Date conversion
48 | To convert from TypedDate to Date, use date property.
49 | ```Swift
50 | typedDate.date // Date
51 | ```
52 |
53 | ### Fill
54 | The fill method allows you to fill in a specific part of a date. For example:
55 | ```Swift
56 | let typedDate = TypedDate(Year(2023), Month(11), Day(12))
57 | // typedDate: TypedDate<(Year, Month, Day)>
58 |
59 | typedDate.fill(
60 | to: \.second,
61 | arguments: (Hour(11), Minute(12), Second(10))
62 | )
63 | // TypedDate<(Year, Month, Day, Hour, Minute, Second)>
64 |
65 | typedDate.fill(
66 | to: \.hour,
67 | arguments: (Hour(11)),
68 | calendar: Calendar(identifier: .gregorian)
69 | )
70 | // TypedDate<(Year, Month, Day, Hour)>
71 | ```
72 | In this example, filledDate will represent the date 2023/11/12 11:12
73 |
74 | ### Erase
75 | The erase method allows you to erase a specific part of a date. For example:
76 |
77 | ```Swift
78 | let date = TypedDate(Year(2023), Month(11), Day(12), Hour(11), Minute(12))
79 | let erasedDate1: TypedDate<(Year, Month, Day)> = date.erase(to: \.day)
80 | let erasedDate2: TypedDate<(Year, Month)> = date.erase(to: \.month)
81 | let erasedDate2: TypedDate<(Year)> = date.erase(to: \.year, calendar: Calendar(identifier: .gregorian)
82 | ```
83 | In this example, erasedDate will be erased up to date specified in keyPath.
84 |
85 | ### Modify
86 | The modify method allows you to modify a specific part of a date. For example:
87 | ```Swift
88 | let typedDate = TypedDate(Year(2023), Month(11), Day(12), Hour(11), Minute(12))
89 | // typedDate: 2023/11/12 11:12
90 |
91 | let modifiedDate = typedDate.modifying(\.year) { $0 += 1 }
92 | .modifying(\.month) { $0 -= 2 }
93 | .modifying(\.day) { $0 += 3 }
94 | .modifying(\.hour) { $0 += 4 }
95 | .modifying(\.minute) { $0 += 5 }
96 | .add(\.year, 1)
97 | .add(\.month, -2)
98 | .add(\.day, 3)
99 | .add(\.hour, -2)
100 | .add(\.minute, -3)
101 | // modifiedDate: 2025/07/18 13:14
102 | ```
103 | or use `TypedDate.modify(_:calendar:modify:)` method
104 | ```Swift
105 | var typedDate = TypedDate(Year(2023), Month(11))
106 |
107 | typedDate.modify(\.year) { $0 += 1 }
108 | // typedDate: 2024/11
109 | ```
110 |
111 | ### Access to each date component
112 | ```Swift
113 | let typedDate = TypedDate(Year(2023), Month(11), Day(12), Hour(11), Minute(12), Second(1), Nanosecond(10000000))
114 | typedDate.year() // 2023
115 | typedDate.month(calendar: Calendar(identifier: .gregorian)) // 11
116 | typedDate.day() // 12
117 | typedDate.hour(calendar: Calendar(identifier: .gregorian)) // 11
118 | typedDate.minute() // 12
119 | typedDate.second(calendar: Calendar(identifier: .gregorian) // 1
120 | typedDate.nanosecond() // 10000000
121 | ```
122 |
123 | ### Simplifying TypedDate with TypedDateOf〇〇 Aliases
124 | This library includes the following typealias:
125 | ```Swift
126 | public typealias TypedDateOfYear = TypedDate
127 | public typealias TypedDateOfMonth = TypedDate<(Year, Month)>
128 | public typealias TypedDateOfDay = TypedDate<(Year, Month, Day)>
129 | public typealias TypedDateOfHour = TypedDate<(Year, Month, Day, Hour)>
130 | public typealias TypedDateOfMinute = TypedDate<(Year, Month, Day, Hour, Minute)>
131 | public typealias TypedDateOfSecond = TypedDate<(Year, Month, Day, Hour, Minute, Second)>
132 | public typealias TypedDateOfNanosecond = TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)>
133 | ```
134 |
135 | ### Conformance to Standard Protocols
136 | TypedDate conforms to the Comparable, Equatable, Hashable, and Codable protocols, which makes it even more powerful and convenient compared to traditional date handling:
137 |
138 | #### **Comparable and Equatable**
139 | These protocols allow for easy comparison of TypedDate instances. You can check if one date is equal to, less than, or greater than another date using standard comparison operators (==, <, >, etc.). This is much more intuitive and less error-prone than comparing individual date components.
140 | ```Swift
141 | let date1: Date // 2023/11/12
142 | let date2: Date // 2023/11/11
143 |
144 | // To check the date have the same year
145 | date1.scope(to: \.year) == date2.scope(to: \.year) // -> true
146 |
147 | // To check the date have the same year, month, and day
148 | date1.scope(to: \.day) == date2.scope(to: \.day) // -> false
149 |
150 | // Compare days
151 | date1.scope(to: \.day) > date2.scope(to: \.day) // -> true
152 | ```
153 | #### **Codable**
154 | The Codable conformance means that TypedDate instances can be easily encoded to and decoded from various formats such as JSON. This is particularly useful when working with APIs or saving dates to persistent storage.
155 |
156 | ## Installation
157 | ```Swift
158 | .package(url: "https://github.com/Ryu0118/swift-typed-date", exact: "0.6.0")
159 | ```
160 |
--------------------------------------------------------------------------------
/Sources/TypedDate/Date+scope.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Date {
4 | /// Returns a `TypedDate` representing the specified date components.
5 | ///
6 | /// - Parameters:
7 | /// - keyPath: A key path to the specific date component.
8 | /// - calendar: The calendar used for date calculations. Defaults to the current calendar.
9 | /// - Returns: A `TypedDate` representing the specified date components.
10 | @_disfavoredOverload
11 | public func scope(
12 | to keyPath: KeyPath<_TypedDateContext, TypedDate?>,
13 | calendar: Calendar = .current
14 | ) -> TypedDate {
15 | scope(to: keyPath, calendar: calendar)!
16 | }
17 |
18 | // To test that this method does not become nil, a @_disfavoredOverload attribute is given to the public method.
19 | func scope(
20 | to keyPath: KeyPath<_TypedDateContext, TypedDate?>,
21 | calendar: Calendar = .current
22 | ) -> TypedDate? {
23 | _TypedDateContext(date: self, calendar: calendar)[keyPath: keyPath]
24 | }
25 | }
26 |
27 | public final class _TypedDateContext {
28 | private let date: Date
29 | private let calendar: Calendar
30 |
31 | public lazy var year: TypedDate<(Year)>? = .init(date: date, calendar: calendar)
32 | public lazy var month: TypedDate<(Year, Month)>? = .init(date: date, calendar: calendar)
33 | public lazy var day: TypedDate<(Year, Month, Day)>? = .init(date: date, calendar: calendar)
34 | public lazy var hour: TypedDate<(Year, Month, Day, Hour)>? = .init(date: date, calendar: calendar)
35 | public lazy var minute: TypedDate<(Year, Month, Day, Hour, Minute)>? = .init(date: date, calendar: calendar)
36 | public lazy var second: TypedDate<(Year, Month, Day, Hour, Minute, Second)>? = .init(date: date, calendar: calendar)
37 | public lazy var nanosecond: TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)>? = .init(date: date, calendar: calendar)
38 |
39 | public init(date: Date, calendar: Calendar) {
40 | self.date = date
41 | self.calendar = calendar
42 | }
43 | }
44 |
45 | extension TypedDate {
46 | init?(
47 | date: Date,
48 | calendar: Calendar
49 | ) where Components == Year {
50 | let dc = calendar.dateComponents([.year], from: date)
51 | guard let date = calendar.date(from: dc),
52 | let year = dc.year
53 | else {
54 | return nil
55 | }
56 | self.date = date
57 | self.components = Year(year)
58 | }
59 |
60 | init?(
61 | date: Date,
62 | calendar: Calendar
63 | ) where Components == (Year, Month) {
64 | let dc = calendar.dateComponents([.year, .month], from: date)
65 | guard let date = calendar.date(from: dc),
66 | let year = dc.year,
67 | let month = dc.month
68 | else {
69 | return nil
70 | }
71 | self.date = date
72 | self.components = (Year(year), Month(month))
73 | }
74 |
75 | init?(
76 | date: Date,
77 | calendar: Calendar
78 | ) where Components == (Year, Month, Day) {
79 | let dc = calendar.dateComponents([.year, .month, .day], from: date)
80 | guard let date = calendar.date(from: dc),
81 | let year = dc.year,
82 | let month = dc.month,
83 | let day = dc.day
84 | else {
85 | return nil
86 | }
87 | self.date = date
88 | self.components = (Year(year), Month(month), Day(day))
89 | }
90 |
91 | init?(
92 | date: Date,
93 | calendar: Calendar
94 | ) where Components == (Year, Month, Day, Hour) {
95 | let dc = calendar.dateComponents([.year, .month, .day, .hour], from: date)
96 | guard let date = calendar.date(from: dc),
97 | let year = dc.year,
98 | let month = dc.month,
99 | let day = dc.day,
100 | let hour = dc.hour
101 | else {
102 | return nil
103 | }
104 | self.date = date
105 | self.components = (Year(year), Month(month), Day(day), Hour(hour))
106 | }
107 |
108 | init?(
109 | date: Date,
110 | calendar: Calendar
111 | ) where Components == (Year, Month, Day, Hour, Minute) {
112 | let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
113 | guard let date = calendar.date(from: dc),
114 | let year = dc.year,
115 | let month = dc.month,
116 | let day = dc.day,
117 | let hour = dc.hour,
118 | let minute = dc.minute
119 | else {
120 | return nil
121 | }
122 | self.date = date
123 | self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute))
124 | }
125 |
126 | init?(
127 | date: Date,
128 | calendar: Calendar
129 | ) where Components == (Year, Month, Day, Hour, Minute, Second) {
130 | let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
131 | guard let date = calendar.date(from: dc),
132 | let year = dc.year,
133 | let month = dc.month,
134 | let day = dc.day,
135 | let hour = dc.hour,
136 | let minute = dc.minute,
137 | let second = dc.second
138 | else {
139 | return nil
140 | }
141 | self.date = date
142 | self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second))
143 | }
144 |
145 | init?(
146 | date: Date,
147 | calendar: Calendar
148 | ) where Components == (Year, Month, Day, Hour, Minute, Second, Nanosecond) {
149 | let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: date)
150 | guard let date = calendar.date(from: dc),
151 | let year = dc.year,
152 | let month = dc.month,
153 | let day = dc.day,
154 | let hour = dc.hour,
155 | let minute = dc.minute,
156 | let second = dc.second
157 | else {
158 | return nil
159 | }
160 | let nanosecond = dc.nanosecond ?? 0
161 | self.date = date
162 | self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second), Nanosecond(nanosecond))
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/Sources/TypedDate/Macro.swift:
--------------------------------------------------------------------------------
1 | @attached(member, names: arbitrary)
2 | internal macro FillInContext() = #externalMacro(module: "TypedDateMacros", type: "FillInContextMacro")
3 |
4 | @attached(member, names: arbitrary)
5 | internal macro EraseContext() = #externalMacro(module: "TypedDateMacros", type: "EraseContextMacro")
6 |
7 | @attached(member, names: arbitrary)
8 | internal macro ModifyContext() = #externalMacro(module: "TypedDateMacros", type: "ModifyContextMacro")
9 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+Codable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension TypedDate: Codable {
4 | public init(from decoder: Decoder) throws {
5 | let container = try decoder.container(keyedBy: CodingKeys.self)
6 | let date = try container.decode(Date.self, forKey: .date)
7 | let calendar = Calendar.current
8 |
9 | switch Components.self {
10 | case is (Year).Type:
11 | let dc = calendar.dateComponents([.year], from: date)
12 | guard let date = calendar.date(from: dc),
13 | let year = dc.year
14 | else {
15 | throw TypedDateDecodingError.invalidDate
16 | }
17 | self.date = date
18 | self.components = Year(year) as! Components
19 |
20 | case is (Year, Month).Type:
21 | let dc = calendar.dateComponents([.year, .month], from: date)
22 | guard let date = calendar.date(from: dc),
23 | let year = dc.year,
24 | let month = dc.month
25 | else {
26 | throw TypedDateDecodingError.invalidDate
27 | }
28 | self.date = date
29 | self.components = (Year(year), Month(month)) as! Components
30 |
31 | case is (Year, Month, Day).Type:
32 | let dc = calendar.dateComponents([.year, .month, .day], from: date)
33 | guard let date = calendar.date(from: dc),
34 | let year = dc.year,
35 | let month = dc.month,
36 | let day = dc.day
37 | else {
38 | throw TypedDateDecodingError.invalidDate
39 | }
40 | self.date = date
41 | self.components = (Year(year), Month(month), Day(day)) as! Components
42 |
43 | case is (Year, Month, Day, Hour).Type:
44 | let dc = calendar.dateComponents([.year, .month, .day, .hour], from: date)
45 | guard let date = calendar.date(from: dc),
46 | let year = dc.year,
47 | let month = dc.month,
48 | let day = dc.day,
49 | let hour = dc.hour
50 | else {
51 | throw TypedDateDecodingError.invalidDate
52 | }
53 | self.date = date
54 | self.components = (Year(year), Month(month), Day(day), Hour(hour)) as! Components
55 |
56 | case is (Year, Month, Day, Hour, Minute).Type:
57 | let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
58 | guard let date = calendar.date(from: dc),
59 | let year = dc.year,
60 | let month = dc.month,
61 | let day = dc.day,
62 | let hour = dc.hour,
63 | let minute = dc.minute
64 | else {
65 | throw TypedDateDecodingError.invalidDate
66 | }
67 | self.date = date
68 | self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute)) as! Components
69 |
70 | case is (Year, Month, Day, Hour, Minute, Second).Type:
71 | let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
72 | guard let date = calendar.date(from: dc),
73 | let year = dc.year,
74 | let month = dc.month,
75 | let day = dc.day,
76 | let hour = dc.hour,
77 | let minute = dc.minute,
78 | let second = dc.second
79 | else {
80 | throw TypedDateDecodingError.invalidDate
81 | }
82 | self.date = date
83 | self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second)) as! Components
84 |
85 | case is (Year, Month, Day, Hour, Minute, Second, Nanosecond).Type:
86 | let dc = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond], from: date)
87 | guard let date = calendar.date(from: dc),
88 | let year = dc.year,
89 | let month = dc.month,
90 | let day = dc.day,
91 | let hour = dc.hour,
92 | let minute = dc.minute,
93 | let second = dc.second
94 | else {
95 | throw TypedDateDecodingError.invalidDate
96 | }
97 | let nanosecond = dc.nanosecond ?? 0
98 | self.date = date
99 | self.components = (Year(year), Month(month), Day(day), Hour(hour), Minute(minute), Second(second), Nanosecond(nanosecond)) as! Components
100 |
101 | default: throw TypedDateDecodingError.invalidComponents
102 | }
103 | }
104 |
105 | public func encode(to encoder: Encoder) throws {
106 | var container = encoder.container(keyedBy: CodingKeys.self)
107 | try container.encode(date, forKey: .date)
108 | }
109 |
110 | public enum CodingKeys: CodingKey {
111 | case date
112 | }
113 | }
114 |
115 | public enum TypedDateDecodingError: Error {
116 | case invalidDate
117 | case invalidComponents
118 | }
119 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+Comparable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension TypedDate: Comparable {
4 | public static func == (lhs: TypedDate, rhs: TypedDate) -> Bool {
5 | lhs.date == rhs.date
6 | }
7 |
8 | public static func < (lhs: TypedDate, rhs: TypedDate) -> Bool {
9 | lhs.date < rhs.date
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+Hashable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension TypedDate: Hashable {
4 | public func hash(into hasher: inout Hasher) {
5 | hasher.combine(date)
6 | switch Components.self {
7 | case is (Year).Type:
8 | hasher.combine(1)
9 |
10 | case is (Year, Month).Type:
11 | hasher.combine(2)
12 |
13 | case is (Year, Month, Day).Type:
14 | hasher.combine(3)
15 |
16 | case is (Year, Month, Day, Hour).Type:
17 | hasher.combine(4)
18 |
19 | case is (Year, Month, Day, Hour, Minute).Type:
20 | hasher.combine(5)
21 |
22 | case is (Year, Month, Day, Hour, Minute, Second).Type:
23 | hasher.combine(6)
24 |
25 | case is (Year, Month, Day, Hour, Minute, Second, Nanosecond).Type:
26 | hasher.combine(6)
27 |
28 | default: hasher.combine(7)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+components.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// `TypedDate` extension. This allows you to get the year component of a date.
4 | public extension TypedDate {
5 | /// Returns the year component of `TypedDate`.
6 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
7 | func year(calendar: Calendar = .current) -> Int {
8 | calendar.dateComponents([.year], from: date).year ?? components.value
9 | }
10 | }
11 |
12 | /// `TypedDate<(Year, Month)>` extension. This allows you to get the year and month components of a date.
13 | public extension TypedDate<(Year, Month)> {
14 | /// Returns the year component of `TypedDate<(Year, Month)>`.
15 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
16 | func year(calendar: Calendar = .current) -> Int {
17 | calendar.dateComponents([.year], from: date).year ?? components.0.value
18 | }
19 |
20 | /// Returns the month component of `TypedDate<(Year, Month)>`.
21 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
22 | func month(calendar: Calendar = .current) -> Int {
23 | calendar.dateComponents([.month], from: date).month ?? components.1.value
24 | }
25 | }
26 |
27 | /// `TypedDate<(Year, Month, Day)>` extension. This allows you to get the year, month, and day components of a date.
28 | public extension TypedDate<(Year, Month, Day)> {
29 | /// Returns the year component of `TypedDate<(Year, Month, Day)>`.
30 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
31 | func year(calendar: Calendar = .current) -> Int {
32 | calendar.dateComponents([.year], from: date).year ?? components.0.value
33 | }
34 |
35 | /// Returns the month component of `TypedDate<(Year, Month, Day)>`.
36 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
37 | func month(calendar: Calendar = .current) -> Int {
38 | calendar.dateComponents([.month], from: date).month ?? components.1.value
39 | }
40 |
41 | /// Returns the day component of `TypedDate<(Year, Month, Day)>`.
42 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
43 | func day(calendar: Calendar = .current) -> Int {
44 | calendar.dateComponents([.day], from: date).day ?? components.2.value
45 | }
46 | }
47 |
48 | /// `TypedDate<(Year, Month, Day, Hour)>` extension. This allows you to get the year, month, day, and hour components of a date.
49 | public extension TypedDate<(Year, Month, Day, Hour)> {
50 | /// Returns the year component of `TypedDate<(Year, Month, Day, Hour)>`.
51 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
52 | func year(calendar: Calendar = .current) -> Int {
53 | calendar.dateComponents([.year], from: date).year ?? components.0.value
54 | }
55 |
56 | /// Returns the month component of `TypedDate<(Year, Month, Day, Hour)>`.
57 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
58 | func month(calendar: Calendar = .current) -> Int {
59 | calendar.dateComponents([.month], from: date).month ?? components.1.value
60 | }
61 |
62 | /// Returns the day component of `TypedDate<(Year, Month, Day, Hour)>`.
63 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
64 | func day(calendar: Calendar = .current) -> Int {
65 | calendar.dateComponents([.day], from: date).day ?? components.2.value
66 | }
67 |
68 | /// Returns the hour component of `TypedDate<(Year, Month, Day, Hour)>`.
69 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
70 | func hour(calendar: Calendar = .current) -> Int {
71 | calendar.dateComponents([.hour], from: date).hour ?? components.3.value
72 | }
73 | }
74 |
75 | /// `TypedDate<(Year, Month, Day, Hour, Minute)>` extension. This allows you to get the year, month, day, hour, and minute components of a date.
76 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {
77 | /// Returns the year component of `TypedDate<(Year, Month, Day, Hour, Minute)>`.
78 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
79 | func year(calendar: Calendar = .current) -> Int {
80 | calendar.dateComponents([.year], from: date).year ?? components.0.value
81 | }
82 |
83 | /// Returns the month component of `TypedDate<(Year, Month, Day, Hour, Minute)>`.
84 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
85 | func month(calendar: Calendar = .current) -> Int {
86 | calendar.dateComponents([.month], from: date).month ?? components.1.value
87 | }
88 |
89 | /// Returns the day component of `TypedDate<(Year, Month, Day, Hour, Minute)>`.
90 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
91 | func day(calendar: Calendar = .current) -> Int {
92 | calendar.dateComponents([.day], from: date).day ?? components.2.value
93 | }
94 |
95 | /// Returns the hour component of `TypedDate<(Year, Month, Day, Hour, Minute)>`.
96 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
97 | func hour(calendar: Calendar = .current) -> Int {
98 | calendar.dateComponents([.hour], from: date).hour ?? components.3.value
99 | }
100 |
101 | /// Returns the minute component of `TypedDate<(Year, Month, Day, Hour, Minute)>`.
102 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
103 | func minute(calendar: Calendar = .current) -> Int {
104 | calendar.dateComponents([.minute], from: date).minute ?? components.4.value
105 | }
106 | }
107 |
108 | /// `TypedDate<(Year, Month, Day, Hour, Minute, Second)>` extension. This allows you to get the year, month, day, hour, minute, and second components of a date.
109 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
110 | /// Returns the year component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
111 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
112 | func year(calendar: Calendar = .current) -> Int {
113 | calendar.dateComponents([.year], from: date).year ?? components.0.value
114 | }
115 |
116 | /// Returns the month component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
117 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
118 | func month(calendar: Calendar = .current) -> Int {
119 | calendar.dateComponents([.month], from: date).month ?? components.1.value
120 | }
121 |
122 | /// Returns the day component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
123 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
124 | func day(calendar: Calendar = .current) -> Int {
125 | calendar.dateComponents([.day], from: date).day ?? components.2.value
126 | }
127 |
128 | /// Returns the hour component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
129 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
130 | func hour(calendar: Calendar = .current) -> Int {
131 | calendar.dateComponents([.hour], from: date).hour ?? components.3.value
132 | }
133 |
134 | /// Returns the minute component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
135 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
136 | func minute(calendar: Calendar = .current) -> Int {
137 | calendar.dateComponents([.minute], from: date).minute ?? components.4.value
138 | }
139 |
140 | /// Returns the second component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
141 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
142 | /// - Returns: The second component as an Int.
143 | func second(calendar: Calendar = .current) -> Int {
144 | calendar.dateComponents([.second], from: date).second ?? components.5.value
145 | }
146 | }
147 |
148 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {
149 | /// Returns the year component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
150 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
151 | /// - Returns: Returns the year component of the date as an integer value.
152 | func year(calendar: Calendar = .current) -> Int {
153 | calendar.dateComponents([.year], from: date).year ?? components.0.value
154 | }
155 |
156 | /// Returns the month component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
157 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
158 | /// - Returns: Returns the month component of the date as an integer value.
159 | func month(calendar: Calendar = .current) -> Int {
160 | calendar.dateComponents([.month], from: date).month ?? components.1.value
161 | }
162 |
163 | /// Returns the day component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
164 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
165 | /// - Returns: Returns the day component of the date as an integer value.
166 | func day(calendar: Calendar = .current) -> Int {
167 | calendar.dateComponents([.day], from: date).day ?? components.2.value
168 | }
169 |
170 | /// Returns the hour component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
171 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
172 | /// - Returns: Returns the hour component of the date as an integer value.
173 | func hour(calendar: Calendar = .current) -> Int {
174 | calendar.dateComponents([.hour], from: date).hour ?? components.3.value
175 | }
176 |
177 | /// Returns the minute component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
178 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
179 | /// - Returns: Returns the minute component of the date as an integer value.
180 | func minute(calendar: Calendar = .current) -> Int {
181 | calendar.dateComponents([.minute], from: date).minute ?? components.4.value
182 | }
183 |
184 | /// Returns the second component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
185 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
186 | /// - Returns: Returns the second component of the date as an integer value.
187 | func second(calendar: Calendar = .current) -> Int {
188 | calendar.dateComponents([.second], from: date).second ?? components.5.value
189 | }
190 |
191 | /// Returns the nanosecond component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
192 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
193 | /// - Returns: Returns the nanosecond component of the date as an integer value.
194 | func nanosecond(calendar: Calendar = .current) -> Int {
195 | calendar.dateComponents([.nanosecond], from: date).nanosecond ?? components.6.value
196 | }
197 | }
198 |
199 | extension Date {
200 | /// Returns the year component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
201 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
202 | /// - Returns: Returns the year component of the date as an integer value.
203 | func year(calendar: Calendar) -> Int? {
204 | calendar.dateComponents([.year], from: self).year
205 | }
206 |
207 | /// Returns the month component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
208 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
209 | /// - Returns: Returns the month component of the date as an integer value.
210 | func month(calendar: Calendar) -> Int? {
211 | calendar.dateComponents([.month], from: self).month
212 | }
213 |
214 | /// Returns the day component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
215 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
216 | /// - Returns: Returns the day component of the date as an integer value.
217 | func day(calendar: Calendar ) -> Int? {
218 | calendar.dateComponents([.day], from: self).day
219 | }
220 |
221 | /// Returns the hour component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
222 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
223 | /// - Returns: Returns the hour component of the date as an integer value.
224 | func hour(calendar: Calendar = .current) -> Int? {
225 | calendar.dateComponents([.hour], from: self).hour
226 | }
227 |
228 | /// Returns the minute component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
229 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
230 | /// - Returns: Returns the minute component of the date as an integer value.
231 | func minute(calendar: Calendar = .current) -> Int? {
232 | calendar.dateComponents([.minute], from: self).minute
233 | }
234 |
235 | /// Returns the second component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
236 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
237 | /// - Returns: Returns the second component of the date as an integer value.
238 | func second(calendar: Calendar = .current) -> Int? {
239 | calendar.dateComponents([.second], from: self).second
240 | }
241 |
242 | /// Returns the nanosecond component of `TypedDate<(Year, Month, Day, Hour, Minute, Second)>`.
243 | /// - Parameter calendar: Calendar used for date calculations, defaults to the current calendar.
244 | /// - Returns: Returns the nanosecond component of the date as an integer value.
245 | func nanosecond(calendar: Calendar = .current) -> Int? {
246 | calendar.dateComponents([.nanosecond], from: self).nanosecond
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+erase.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @EraseContext
4 | public extension TypedDate<(Year, Month)> {
5 | /// Type erase TypedDate<(Year, Month)> to the specified type.
6 | /// - Parameters:
7 | /// - keyPath: Type erase up to the component of Date specified by KeyPath
8 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
9 | /// - Returns: type-erased instance
10 | func erase(
11 | to keyPath: KeyPath<_MonthEraseContext, (TypedDate.Type, T)>,
12 | calendar: Calendar = .current
13 | ) -> TypedDate {
14 | let context = _MonthEraseContext(base: components) [keyPath: keyPath]
15 | return context.0.init(context.1, calendar: calendar)
16 | }
17 | }
18 |
19 | @EraseContext
20 | public extension TypedDate<(Year, Month, Day)> {
21 | /// Type erase TypedDate<(Year, Month, Day)> to the specified type.
22 | /// - Parameters:
23 | /// - keyPath: Type erase up to the component of Date specified by KeyPath
24 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
25 | /// - Returns: type-erased instance
26 | func erase(
27 | to keyPath: KeyPath<_DayEraseContext, (TypedDate.Type, T)>,
28 | calendar: Calendar = .current
29 | ) -> TypedDate {
30 | let context = _DayEraseContext(base: components) [keyPath: keyPath]
31 | return context.0.init(context.1, calendar: calendar)
32 | }
33 | }
34 |
35 | @EraseContext
36 | public extension TypedDate<(Year, Month, Day, Hour)> {
37 | /// Type erase TypedDate<(Year, Month, Day, Hour)> to the specified type.
38 | /// - Parameters:
39 | /// - keyPath: Type erase up to the component of Date specified by KeyPath
40 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
41 | /// - Returns: type-erased instance
42 | func erase(
43 | to keyPath: KeyPath<_HourEraseContext, (TypedDate.Type, T)>,
44 | calendar: Calendar = .current
45 | ) -> TypedDate {
46 | let context = _HourEraseContext(base: components) [keyPath: keyPath]
47 | return context.0.init(context.1, calendar: calendar)
48 | }
49 | }
50 |
51 | @EraseContext
52 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {
53 | /// Type erase TypedDate<(Year, Month, Day, Hour, Minute)> to the specified type.
54 | /// - Parameters:
55 | /// - keyPath: Type erase up to the component of Date specified by KeyPath
56 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
57 | /// - Returns: type-erased instance
58 | func erase(
59 | to keyPath: KeyPath<_MinuteEraseContext, (TypedDate.Type, T)>,
60 | calendar: Calendar = .current
61 | ) -> TypedDate {
62 | let context = _MinuteEraseContext(base: components) [keyPath: keyPath]
63 | return context.0.init(context.1, calendar: calendar)
64 | }
65 | }
66 |
67 | @EraseContext
68 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
69 | /// Type erase TypedDate<(Year, Month, Day, Hour, Minute, Second)> to the specified type.
70 | /// - Parameters:
71 | /// - keyPath: Type erase up to the component of Date specified by KeyPath
72 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
73 | /// - Returns: type-erased instance
74 | func erase(
75 | to keyPath: KeyPath<_SecondEraseContext, (TypedDate.Type, T)>,
76 | calendar: Calendar = .current
77 | ) -> TypedDate {
78 | let context = _SecondEraseContext(base: components) [keyPath: keyPath]
79 | return context.0.init(context.1, calendar: calendar)
80 | }
81 | }
82 |
83 | @EraseContext
84 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {
85 | /// Type erase TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> to the specified type.
86 | /// - Parameters:
87 | /// - keyPath: Type erase up to the component of Date specified by KeyPath
88 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
89 | /// - Returns: type-erased instance
90 | func erase(
91 | to keyPath: KeyPath<_NanosecondEraseContext, (TypedDate.Type, T)>,
92 | calendar: Calendar = .current
93 | ) -> TypedDate {
94 | let context = _NanosecondEraseContext(base: components) [keyPath: keyPath]
95 | return context.0.init(context.1, calendar: calendar)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+fill.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @FillInContext
4 | public extension TypedDate {
5 | /// Fill in missing Date components
6 | ///
7 | /// - Parameters:
8 | /// - to: KeyPath for specifying components of a Date.
9 | /// - arguments: Tuple of Components of Date to be filled.
10 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
11 | /// - Returns: Instance of TypedDate with more Date Components.
12 | func fill(
13 | to keyPath: KeyPath<_YearFillInContext,(U) -> T>,
14 | arguments: U,
15 | calendar: Calendar = .current
16 | ) -> TypedDate {
17 | let context = _YearFillInContext(base: components)
18 | let transform = context[keyPath: keyPath]
19 | return .init(transform(arguments), calendar: calendar)
20 | }
21 | }
22 |
23 | @FillInContext
24 | public extension TypedDate<(Year, Month)> {
25 | /// Fill in missing Date components
26 | ///
27 | /// - Parameters:
28 | /// - to: KeyPath for specifying components of a Date.
29 | /// - arguments: Tuple of Components of Date to be filled.
30 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
31 | /// - Returns: Instance of TypedDate with more Date Components.
32 | func fill(
33 | to keyPath: KeyPath<_MonthFillInContext,(U) -> T>,
34 | arguments: U,
35 | calendar: Calendar = .current
36 | ) -> TypedDate {
37 | let context = _MonthFillInContext(base: components)
38 | let transform = context[keyPath: keyPath]
39 | return .init(transform(arguments), calendar: calendar)
40 | }
41 | }
42 |
43 | @FillInContext
44 | public extension TypedDate<(Year, Month, Day)> {
45 | /// Fill in missing Date components
46 | ///
47 | /// - Parameters:
48 | /// - to: KeyPath for specifying components of a Date.
49 | /// - arguments: Tuple of Components of Date to be filled.
50 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
51 | /// - Returns: Instance of TypedDate with more Date Components.
52 | func fill(
53 | to keyPath: KeyPath<_DayFillInContext, (U) -> T>,
54 | arguments: U,
55 | calendar: Calendar = .current
56 | ) -> TypedDate {
57 | let context = _DayFillInContext(base: components)
58 | let transform = context[keyPath: keyPath]
59 | return .init(transform(arguments), calendar: calendar)
60 | }
61 | }
62 |
63 | @FillInContext
64 | public extension TypedDate<(Year, Month, Day, Hour)> {
65 | /// Fill in missing Date components
66 | ///
67 | /// - Parameters:
68 | /// - to: KeyPath for specifying components of a Date.
69 | /// - arguments: Tuple of Components of Date to be filled.
70 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
71 | /// - Returns: Instance of TypedDate with more Date Components.
72 | func fill(
73 | to keyPath: KeyPath<_HourFillInContext, (U) -> T>,
74 | arguments: U,
75 | calendar: Calendar = .current
76 | ) -> TypedDate {
77 | let context = _HourFillInContext(base: components)
78 | let transform = context[keyPath: keyPath]
79 | return .init(transform(arguments), calendar: calendar)
80 | }
81 | }
82 |
83 | @FillInContext
84 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {
85 | /// Fill in missing Date components
86 | ///
87 | /// - Parameters:
88 | /// - to: KeyPath for specifying components of a Date.
89 | /// - arguments: Tuple of Components of Date to be filled.
90 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
91 | /// - Returns: Instance of TypedDate with more Date Components.
92 | func fill(
93 | to keyPath: KeyPath<_MinuteFillInContext, (U) -> T>,
94 | arguments: U,
95 | calendar: Calendar = .current
96 | ) -> TypedDate {
97 | let context = _MinuteFillInContext(base: components)
98 | let transform = context[keyPath: keyPath]
99 | return .init(transform(arguments), calendar: calendar)
100 | }
101 | }
102 |
103 | @FillInContext
104 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
105 | /// Fill in missing Date components
106 | ///
107 | /// - Parameters:
108 | /// - to: KeyPath for specifying components of a Date.
109 | /// - arguments: Tuple of Components of Date to be filled.
110 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
111 | /// - Returns: Instance of TypedDate with more Date Components.
112 | func fill(
113 | to keyPath: KeyPath<_SecondFillInContext, (U) -> T>,
114 | arguments: U,
115 | calendar: Calendar = .current
116 | ) -> TypedDate {
117 | let context = _SecondFillInContext(base: components)
118 | let transform = context[keyPath: keyPath]
119 | return .init(transform(arguments), calendar: calendar)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+init.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// `TypedDate` is a structure for handling dates in a type-safe manner.
4 | public extension TypedDate {
5 | /// Initializes a `TypedDate` by specifying the year.
6 | /// - Parameters:
7 | /// - year: The year component of the date.
8 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
9 | /// - Returns: Instance of `TypedDate` with the year component.
10 | init(_ year: Year, calendar: Calendar = .current) where Components == Year {
11 | self.components = year
12 | self.date = DateComponents(
13 | calendar: calendar,
14 | year: year.value
15 | ).date!
16 | }
17 |
18 | /// Initializes a `TypedDate` by specifying the year and month.
19 | /// - Parameters:
20 | /// - year: The year component of the date.
21 | /// - month: The month component of the date.
22 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
23 | /// - Returns: Instance of `TypedDate` with the year and month components.
24 | init(
25 | _ year: Year,
26 | _ month: Month,
27 | calendar: Calendar = .current
28 | ) where Components == (Year, Month) {
29 | self.components = (year, month)
30 | self.date = DateComponents(
31 | calendar: calendar,
32 | year: year.value,
33 | month: month.value
34 | ).date!
35 | }
36 |
37 | /// Initializes a `TypedDate` by specifying the year, month, and day.
38 | /// - Parameters:
39 | /// - year: The year component of the date.
40 | /// - month: The month component of the date.
41 | /// - day: The day component of the date.
42 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
43 | /// - Returns: Instance of `TypedDate` with the year, month, and day components.
44 | init(
45 | _ year: Year,
46 | _ month: Month,
47 | _ day: Day,
48 | calendar: Calendar = .current
49 | ) where Components == (Year, Month, Day) {
50 | self.components = (year, month, day)
51 | self.date = DateComponents(
52 | calendar: calendar,
53 | year: year.value,
54 | month: month.value,
55 | day: day.value
56 | ).date!
57 | }
58 |
59 | /// Initializes a `TypedDate` by specifying the year, month, day, and hour.
60 | /// - Parameters:
61 | /// - year: The year component of the date.
62 | /// - month: The month component of the date.
63 | /// - day: The day component of the date.
64 | /// - hour: The hour component of the date.
65 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
66 | /// - Returns: Instance of `TypedDate` with the year, month, day, and hour components.
67 | init(
68 | _ year: Year,
69 | _ month: Month,
70 | _ day: Day,
71 | _ hour: Hour,
72 | calendar: Calendar = .current
73 | ) where Components == (Year, Month, Day, Hour) {
74 | self.components = (year, month, day, hour)
75 | self.date = DateComponents(
76 | calendar: calendar,
77 | year: year.value,
78 | month: month.value,
79 | day: day.value,
80 | hour: hour.value
81 | ).date!
82 | }
83 |
84 | /// Initializes a `TypedDate` by specifying the year, month, day, hour, and minute.
85 | /// - Parameters:
86 | /// - year: The year component of the date.
87 | /// - month: The month component of the date.
88 | /// - day: The day component of the date.
89 | /// - hour: The hour component of the date.
90 | /// - minute: The minute component of the date.
91 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
92 | /// - Returns: Instance of `TypedDate` with the year, month, day, hour, and minute components.
93 | init(
94 | _ year: Year,
95 | _ month: Month,
96 | _ day: Day,
97 | _ hour: Hour,
98 | _ minute: Minute,
99 | calendar: Calendar = .current
100 | ) where Components == (Year, Month, Day, Hour, Minute) {
101 | self.components = (year, month, day, hour, minute)
102 | self.date = DateComponents(
103 | calendar: calendar,
104 | year: year.value,
105 | month: month.value,
106 | day: day.value,
107 | hour: hour.value,
108 | minute: minute.value
109 | ).date!
110 | }
111 |
112 | /// Initializes a `TypedDate` by specifying the year, month, day, hour, minute, and second.
113 | /// - Parameters:
114 | /// - year: The year component of the date.
115 | /// - month: The month component of the date.
116 | /// - day: The day component of the date.
117 | /// - hour: The hour component of the date.
118 | /// - minute: The minute component of the date.
119 | /// - second: The second component of the date.
120 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
121 | /// - Returns: Instance of `TypedDate` with the year, month, day, hour, minute, and second components.
122 | init(
123 | _ year: Year,
124 | _ month: Month,
125 | _ day: Day,
126 | _ hour: Hour,
127 | _ minute: Minute,
128 | _ second: Second,
129 | calendar: Calendar = .current
130 | ) where Components == (Year, Month, Day, Hour, Minute, Second) {
131 | self.components = (year, month, day, hour, minute, second)
132 | self.date = DateComponents(
133 | calendar: calendar,
134 | year: year.value,
135 | month: month.value,
136 | day: day.value,
137 | hour: hour.value,
138 | minute: minute.value,
139 | second: second.value
140 | ).date!
141 | }
142 |
143 | /// Initializes a `TypedDate` by specifying the year, month, day, hour, minute, second, and nanosecond.
144 | /// - Parameters:
145 | /// - year: The year component of the date.
146 | /// - month: The month component of the date.
147 | /// - day: The day component of the date.
148 | /// - hour: The hour component of the date.
149 | /// - minute: The minute component of the date.
150 | /// - second: The second component of the date.
151 | /// - nanosecond: The nanosecond component of the date.
152 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
153 | /// - Returns: Instance of `TypedDate` with the year, month, day, hour, minute, second, and nanosecond components.
154 | init(
155 | _ year: Year,
156 | _ month: Month,
157 | _ day: Day,
158 | _ hour: Hour,
159 | _ minute: Minute,
160 | _ second: Second,
161 | _ nanosecond: Nanosecond,
162 | calendar: Calendar = .current
163 | ) where Components == (Year, Month, Day, Hour, Minute, Second, Nanosecond) {
164 | self.components = (year, month, day, hour, minute, second, nanosecond)
165 | self.date = DateComponents(
166 | calendar: calendar,
167 | year: year.value,
168 | month: month.value,
169 | day: day.value,
170 | hour: hour.value,
171 | minute: minute.value,
172 | second: second.value,
173 | nanosecond: nanosecond.value
174 | ).date!
175 | }
176 |
177 | /// Initializes a `TypedDate` by specifying the year, month, day, hour, minute, and fractional second.
178 | /// - Parameters:
179 | /// - year: The year component of the date.
180 | /// - month: The month component of the date.
181 | /// - day: The day component of the date.
182 | /// - hour: The hour component of the date.
183 | /// - minute: The minute component of the date.
184 | /// - fractionalSecond: The fractional second component of the date.
185 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
186 | /// - Returns: Instance of `TypedDate` with the year, month, day, hour, minute, and fractional second components.
187 | init(
188 | _ year: Year,
189 | _ month: Month,
190 | _ day: Day,
191 | _ hour: Hour,
192 | _ minute: Minute,
193 | _ fractionalSecond: FractionalSecond,
194 | calendar: Calendar = .current
195 | ) where Components == (Year, Month, Day, Hour, Minute, Second, Nanosecond) {
196 | self.components = (year, month, day, hour, minute, Second(fractionalSecond.seconds), Nanosecond(fractionalSecond.nanoseconds))
197 | self.date = DateComponents(
198 | calendar: calendar,
199 | year: year.value,
200 | month: month.value,
201 | day: day.value,
202 | hour: hour.value,
203 | minute: minute.value,
204 | second: components.5.value,
205 | nanosecond: components.6.value
206 | ).date!
207 | }
208 | }
209 |
210 | extension TypedDate {
211 | init(_ components: Components, calendar: Calendar = .current) {
212 | switch components {
213 | case let components as Year:
214 | self.date = DateComponents(
215 | calendar: calendar,
216 | year: components.value
217 | ).date!
218 | self.components = Year(date.year(calendar: calendar) ?? components.value) as! Components
219 | case let components as (Year, Month):
220 | self.date = DateComponents(
221 | calendar: calendar,
222 | year: components.0.value,
223 | month: components.1.value
224 | ).date!
225 | self.components = (
226 | Year(date.year(calendar: calendar) ?? components.0.value),
227 | Month(date.month(calendar: calendar) ?? components.1.value)
228 | ) as! Components
229 | case let components as (Year, Month, Day):
230 | self.date = DateComponents(
231 | calendar: calendar,
232 | year: components.0.value,
233 | month: components.1.value,
234 | day: components.2.value
235 | ).date!
236 | self.components = (
237 | Year(date.year(calendar: calendar) ?? components.0.value),
238 | Month(date.month(calendar: calendar) ?? components.1.value),
239 | Day(date.day(calendar: calendar) ?? components.2.value)
240 | ) as! Components
241 | case let components as (Year, Month, Day, Hour):
242 | self.date = DateComponents(
243 | calendar: calendar,
244 | year: components.0.value,
245 | month: components.1.value,
246 | day: components.2.value,
247 | hour: components.3.value
248 | ).date!
249 | self.components = (
250 | Year(date.year(calendar: calendar) ?? components.0.value),
251 | Month(date.month(calendar: calendar) ?? components.1.value),
252 | Day(date.day(calendar: calendar) ?? components.2.value),
253 | Hour(date.hour(calendar: calendar) ?? components.3.value)
254 | ) as! Components
255 | case let components as (Year, Month, Day, Hour, Minute):
256 | self.date = DateComponents(
257 | calendar: calendar,
258 | year: components.0.value,
259 | month: components.1.value,
260 | day: components.2.value,
261 | hour: components.3.value,
262 | minute: components.4.value
263 | ).date!
264 | self.components = (
265 | Year(date.year(calendar: calendar) ?? components.0.value),
266 | Month(date.month(calendar: calendar) ?? components.1.value),
267 | Day(date.day(calendar: calendar) ?? components.2.value),
268 | Hour(date.hour(calendar: calendar) ?? components.3.value),
269 | Minute(date.minute(calendar: calendar) ?? components.4.value)
270 | ) as! Components
271 | case let components as (Year, Month, Day, Hour, Minute, Second):
272 | self.date = DateComponents(
273 | calendar: calendar,
274 | year: components.0.value,
275 | month: components.1.value,
276 | day: components.2.value,
277 | hour: components.3.value,
278 | minute: components.4.value,
279 | second: components.5.value
280 | ).date!
281 | self.components = (
282 | Year(date.year(calendar: calendar) ?? components.0.value),
283 | Month(date.month(calendar: calendar) ?? components.1.value),
284 | Day(date.day(calendar: calendar) ?? components.2.value),
285 | Hour(date.hour(calendar: calendar) ?? components.3.value),
286 | Minute(date.minute(calendar: calendar) ?? components.4.value),
287 | Second(date.second(calendar: calendar) ?? components.5.value)
288 | ) as! Components
289 | case let components as (Year, Month, Day, Hour, Minute, Second, Nanosecond):
290 | self.date = DateComponents(
291 | calendar: calendar,
292 | year: components.0.value,
293 | month: components.1.value,
294 | day: components.2.value,
295 | hour: components.3.value,
296 | minute: components.4.value,
297 | second: components.5.value,
298 | nanosecond: components.6.value
299 | ).date!
300 | self.components = (
301 | Year(date.year(calendar: calendar) ?? components.0.value),
302 | Month(date.month(calendar: calendar) ?? components.1.value),
303 | Day(date.day(calendar: calendar) ?? components.2.value),
304 | Hour(date.hour(calendar: calendar) ?? components.3.value),
305 | Minute(date.minute(calendar: calendar) ?? components.4.value),
306 | Second(date.second(calendar: calendar) ?? components.5.value),
307 | Nanosecond(date.nanosecond(calendar: calendar) ?? components.6.value)
308 | ) as! Components
309 |
310 | default:
311 | fatalError(
312 | """
313 | Unexpected components: \(components)
314 | """
315 | )
316 | }
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate+modify.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @ModifyContext
4 | public extension TypedDate {
5 | /// Modifies a specific date component with a provided modification closure.
6 | /// - Parameters:
7 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
8 | /// - modify: Closure to change the value.
9 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
10 | /// - Returns: A new `TypedDate` instance with the modified component.
11 | func modifying(
12 | _ keyPath: KeyPath<_YearModifyContext, (T, (T) -> Components)>,
13 | calendar: Calendar = .current,
14 | modify: (inout T) -> Void
15 | ) -> TypedDate {
16 | let context = _YearModifyContext(base: components)
17 | var (target, transform) = context[keyPath: keyPath]
18 | modify(&target)
19 | return .init(transform(target), calendar: calendar)
20 | }
21 |
22 | /// Mutates a specific date component of with a provided modification closure.
23 | /// - Parameters:
24 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
25 | /// - modify: Closure to change the value.
26 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
27 | mutating func modify(
28 | _ keyPath: KeyPath<_YearModifyContext, (T, (T) -> Components)>,
29 | calendar: Calendar = .current,
30 | modify: (inout T) -> Void
31 | ) {
32 | self = modifying(keyPath, calendar: calendar, modify: modify)
33 | }
34 |
35 | /// Adds a specific value to a date component.
36 | /// - Parameters:
37 | /// - keyPath: A KeyPath specifying the date component to be modified.
38 | /// - value: The value to be added.
39 | /// - calendar: The calendar used for date calculations, defaults to the current calendar.
40 | /// - Returns: A new `TypedDate` instance with the modified component.
41 | func add(
42 | _ keyPath: KeyPath<_YearModifyContext, (T, (T) -> Components)>,
43 | _ value: T,
44 | calendar: Calendar = .current
45 | ) -> TypedDate {
46 | modifying(keyPath, calendar: calendar, modify: { $0 += value })
47 | }
48 | }
49 |
50 | @ModifyContext
51 | public extension TypedDate<(Year, Month)> {
52 | /// Modifies a specific date component with a provided modification closure.
53 | /// - Parameters:
54 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
55 | /// - modify: Closure to change the value.
56 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
57 | /// - Returns: A new `TypedDate` instance with the modified component.
58 | func modifying(
59 | _ keyPath: KeyPath<_MonthModifyContext, (T, (T) -> Components)>,
60 | calendar: Calendar = .current,
61 | modify: (inout T) -> Void
62 | ) -> TypedDate {
63 | let context = _MonthModifyContext(base: components)
64 | var (target, transform) = context[keyPath: keyPath]
65 | modify(&target)
66 | return .init(transform(target), calendar: calendar)
67 | }
68 |
69 | /// Mutates a specific date component with a provided modification closure.
70 | /// - Parameters:
71 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
72 | /// - modify: Closure to change the value.
73 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
74 | mutating func modify(
75 | _ keyPath: KeyPath<_MonthModifyContext, (T, (T) -> Components)>,
76 | calendar: Calendar = .current,
77 | modify: (inout T) -> Void
78 | ) {
79 | self = modifying(keyPath, calendar: calendar, modify: modify)
80 | }
81 |
82 | /// Adds a specific value to a date component.
83 | /// - Parameters:
84 | /// - keyPath: A KeyPath specifying the date component to be modified.
85 | /// - value: The value to be added.
86 | /// - calendar: The calendar used for date calculations, defaults to the current calendar.
87 | /// - Returns: A new `TypedDate` instance with the modified component.
88 | func add(
89 | _ keyPath: KeyPath<_MonthModifyContext, (T, (T) -> Components)>,
90 | _ value: T,
91 | calendar: Calendar = .current
92 | ) -> TypedDate {
93 | modifying(keyPath, calendar: calendar, modify: { $0 += value })
94 | }
95 | }
96 |
97 | @ModifyContext
98 | public extension TypedDate<(Year, Month, Day)> {
99 | /// Modifies a specific date component with a provided modification closure.
100 | /// - Parameters:
101 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
102 | /// - modify: Closure to change the value.
103 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
104 | /// - Returns: A new `TypedDate` instance with the modified component.
105 | func modifying(
106 | _ keyPath: KeyPath<_DayModifyContext, (T, (T) -> Components)>,
107 | calendar: Calendar = .current,
108 | modify: (inout T) -> Void
109 | ) -> TypedDate {
110 | let context = _DayModifyContext(base: components)
111 | var (target, transform) = context[keyPath: keyPath]
112 | modify(&target)
113 | return .init(transform(target), calendar: calendar)
114 | }
115 |
116 | /// Mutates a specific date component with a provided modification closure.
117 | /// - Parameters:
118 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
119 | /// - modify: Closure to change the value.
120 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
121 | mutating func modify(
122 | _ keyPath: KeyPath<_DayModifyContext, (T, (T) -> Components)>,
123 | calendar: Calendar = .current,
124 | modify: (inout T) -> Void
125 | ) {
126 | self = modifying(keyPath, calendar: calendar, modify: modify)
127 | }
128 |
129 | /// Adds a specific value to a date component.
130 | /// - Parameters:
131 | /// - keyPath: A KeyPath specifying the date component to be modified.
132 | /// - value: The value to be added.
133 | /// - calendar: The calendar used for date calculations, defaults to the current calendar.
134 | /// - Returns: A new `TypedDate` instance with the modified component.
135 | func add(
136 | _ keyPath: KeyPath<_DayModifyContext, (T, (T) -> Components)>,
137 | _ value: T,
138 | calendar: Calendar = .current
139 | ) -> TypedDate {
140 | modifying(keyPath, calendar: calendar, modify: { $0 += value })
141 | }
142 | }
143 |
144 | @ModifyContext
145 | public extension TypedDate<(Year, Month, Day, Hour)> {
146 | /// Modifies a specific date component with a provided modification closure.
147 | /// - Parameters:
148 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
149 | /// - modify: Closure to change the value.
150 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
151 | /// - Returns: A new `TypedDate` instance with the modified component.
152 | func modifying(
153 | _ keyPath: KeyPath<_HourModifyContext, (T, (T) -> Components)>,
154 | calendar: Calendar = .current,
155 | modify: (inout T) -> Void
156 | ) -> TypedDate {
157 | let context = _HourModifyContext(base: components)
158 | var (target, transform) = context[keyPath: keyPath]
159 | modify(&target)
160 | return .init(transform(target), calendar: calendar)
161 | }
162 |
163 | /// Mutates a specific date component with a provided modification closure.
164 | /// - Parameters:
165 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
166 | /// - modify: Closure to change the value.
167 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
168 | mutating func modify(
169 | _ keyPath: KeyPath<_HourModifyContext, (T, (T) -> Components)>,
170 | calendar: Calendar = .current,
171 | modify: (inout T) -> Void
172 | ) {
173 | self = modifying(keyPath, calendar: calendar, modify: modify)
174 | }
175 |
176 | /// Adds a specific value to a date component.
177 | /// - Parameters:
178 | /// - keyPath: A KeyPath specifying the date component to be modified.
179 | /// - value: The value to be added.
180 | /// - calendar: The calendar used for date calculations, defaults to the current calendar.
181 | /// - Returns: A new `TypedDate` instance with the modified component.
182 | func add(
183 | _ keyPath: KeyPath<_HourModifyContext, (T, (T) -> Components)>,
184 | _ value: T,
185 | calendar: Calendar = .current
186 | ) -> TypedDate {
187 | modifying(keyPath, calendar: calendar, modify: { $0 += value })
188 | }
189 | }
190 |
191 | @ModifyContext
192 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {
193 | /// Modifies a specific date component with a provided modification closure.
194 | /// - Parameters:
195 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
196 | /// - modify: Closure to change the value.
197 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
198 | /// - Returns: A new `TypedDate` instance with the modified component.
199 | func modifying(
200 | _ keyPath: KeyPath<_MinuteModifyContext, (T, (T) -> Components)>,
201 | calendar: Calendar = .current,
202 | modify: (inout T) -> Void
203 | ) -> TypedDate {
204 | let context = _MinuteModifyContext(base: components)
205 | var (target, transform) = context[keyPath: keyPath]
206 | modify(&target)
207 | return .init(transform(target), calendar: calendar)
208 | }
209 |
210 | /// Mutates a specific date component with a provided modification closure.
211 | /// - Parameters:
212 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
213 | /// - modify: Closure to change the value.
214 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
215 | mutating func modify(
216 | _ keyPath: KeyPath<_MinuteModifyContext, (T, (T) -> Components)>,
217 | calendar: Calendar = .current,
218 | modify: (inout T) -> Void
219 | ) {
220 | self = modifying(keyPath, calendar: calendar, modify: modify)
221 | }
222 |
223 | /// Adds a specific value to a date component.
224 | /// - Parameters:
225 | /// - keyPath: A KeyPath specifying the date component to be modified.
226 | /// - value: The value to be added.
227 | /// - calendar: The calendar used for date calculations, defaults to the current calendar.
228 | /// - Returns: A new `TypedDate` instance with the modified component.
229 | func add(
230 | _ keyPath: KeyPath<_MinuteModifyContext, (T, (T) -> Components)>,
231 | _ value: T,
232 | calendar: Calendar = .current
233 | ) -> TypedDate {
234 | modifying(keyPath, calendar: calendar, modify: { $0 += value })
235 | }
236 | }
237 |
238 | @ModifyContext
239 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
240 | /// Modifies a specific date component with a provided modification closure.
241 | /// - Parameters:
242 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
243 | /// - modify: Closure to change the value.
244 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
245 | /// - Returns: A new `TypedDate` instance with the modified component.
246 | func modifying(
247 | _ keyPath: KeyPath<_SecondModifyContext, (T, (T) -> Components)>,
248 | calendar: Calendar = .current,
249 | modify: (inout T) -> Void
250 | ) -> TypedDate {
251 | let context = _SecondModifyContext(base: components)
252 | var (target, transform) = context[keyPath: keyPath]
253 | modify(&target)
254 | return .init(transform(target), calendar: calendar)
255 | }
256 |
257 | /// Mutates a specific date component with a provided modification closure.
258 | /// - Parameters:
259 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
260 | /// - modify: Closure to change the value.
261 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
262 | mutating func modify(
263 | _ keyPath: KeyPath<_SecondModifyContext, (T, (T) -> Components)>,
264 | calendar: Calendar = .current,
265 | modify: (inout T) -> Void
266 | ) {
267 | self = modifying(keyPath, calendar: calendar, modify: modify)
268 | }
269 |
270 | /// Adds a specific value to a date component.
271 | /// - Parameters:
272 | /// - keyPath: A KeyPath specifying the date component to be modified.
273 | /// - value: The value to be added.
274 | /// - calendar: The calendar used for date calculations, defaults to the current calendar.
275 | /// - Returns: A new `TypedDate` instance with the modified component.
276 | func add(
277 | _ keyPath: KeyPath<_SecondModifyContext, (T, (T) -> Components)>,
278 | _ value: T,
279 | calendar: Calendar = .current
280 | ) -> TypedDate {
281 | modifying(keyPath, calendar: calendar, modify: { $0 += value })
282 | }
283 | }
284 |
285 | @ModifyContext
286 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {
287 | /// Modifies a specific date component with a provided modification closure.
288 | /// - Parameters:
289 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
290 | /// - modify: Closure to change the value.
291 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
292 | /// - Returns: A new `TypedDate` instance with the modified component.
293 | func modifying(
294 | _ keyPath: KeyPath<_NanosecondModifyContext, (T, (T) -> Components)>,
295 | calendar: Calendar = .current,
296 | modify: (inout T) -> Void
297 | ) -> TypedDate {
298 | let context = _NanosecondModifyContext(base: components)
299 | var (target, transform) = context[keyPath: keyPath]
300 | modify(&target)
301 | return .init(transform(target), calendar: calendar)
302 | }
303 |
304 | /// Mutates a specific date component with a provided modification closure.
305 | /// - Parameters:
306 | /// - keyPath: Specify the Components of the Date you want to change by keyPath.
307 | /// - modify: Closure to change the value.
308 | /// - calendar: Calendar used for date calculations, defaults to the current calendar.
309 | mutating func modify(
310 | _ keyPath: KeyPath<_NanosecondModifyContext, (T, (T) -> Components)>,
311 | calendar: Calendar = .current,
312 | modify: (inout T) -> Void
313 | ) {
314 | self = modifying(keyPath, calendar: calendar, modify: modify)
315 | }
316 |
317 | /// Adds a specific value to a date component.
318 | /// - Parameters:
319 | /// - keyPath: A KeyPath specifying the date component to be modified.
320 | /// - value: The value to be added.
321 | /// - calendar: The calendar used for date calculations, defaults to the current calendar.
322 | /// - Returns: A new `TypedDate` instance with the modified component.
323 | func add(
324 | _ keyPath: KeyPath<_NanosecondModifyContext, (T, (T) -> Components)>,
325 | _ value: T,
326 | calendar: Calendar = .current
327 | ) -> TypedDate {
328 | modifying(keyPath, calendar: calendar, modify: { $0 += value })
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDate.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A `TypedDate` struct that encapsulates a `Date` object with associated date components.
4 | /// This struct provides a type-safe way to handle dates with specific components,
5 | /// such as year, month, day, etc. The `Components` type parameter defines the date components
6 | /// associated with this `TypedDate`.
7 | public struct TypedDate: TypedDateProtocol, Sendable {
8 | /// The underlying `Date` object.
9 | /// This property stores the actual date value represented by the `TypedDate` instance.
10 | public let date: Date
11 |
12 | /// The components associated with the `TypedDate`.
13 | /// These components are of the type specified by the `Components` generic parameter,
14 | /// and they represent various aspects of the date, such as year, month, day, etc.
15 | let components: Components
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDateOf.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A convenience type alias that represents a `TypedDate` with only the year component.
4 | public typealias TypedDateOfYear = TypedDate
5 |
6 | /// A convenience type alias that represents a `TypedDate` with the year and month components.
7 | public typealias TypedDateOfMonth = TypedDate<(Year, Month)>
8 |
9 | /// A convenience type alias that represents a `TypedDate` with the year, month, and day components.
10 | public typealias TypedDateOfDay = TypedDate<(Year, Month, Day)>
11 |
12 | /// A convenience type alias that represents a `TypedDate` with the year, month, day, and hour components.
13 | public typealias TypedDateOfHour = TypedDate<(Year, Month, Day, Hour)>
14 |
15 | /// A convenience type alias that represents a `TypedDate` with the year, month, day, hour, and minute components.
16 | public typealias TypedDateOfMinute = TypedDate<(Year, Month, Day, Hour, Minute)>
17 |
18 | /// A convenience type alias that represents a `TypedDate` with the year, month, day, hour, minute, and second components.
19 | public typealias TypedDateOfSecond = TypedDate<(Year, Month, Day, Hour, Minute, Second)>
20 |
21 | /// A convenience type alias that represents a `TypedDate` with the year, month, day, hour, minute, second, and nanosecond components.
22 | public typealias TypedDateOfNanosecond = TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)>
23 |
24 | public extension TypedDateOfYear {
25 | /// Returns a `TypedDate` instance that represents the current year, at the moment of access.
26 | static var now: Self {
27 | Date().scope(to: \.year)
28 | }
29 | }
30 |
31 | public extension TypedDateOfMonth {
32 | /// Returns a `TypedDate` instance that represents the current year and month, at the moment of access.
33 | static var now: Self {
34 | Date().scope(to: \.month)
35 | }
36 | }
37 |
38 | public extension TypedDateOfDay {
39 | /// Returns a `TypedDate` instance that represents the current year, month, and day, at the moment of access.
40 | static var now: Self {
41 | Date().scope(to: \.day)
42 | }
43 | }
44 |
45 | public extension TypedDateOfHour {
46 | /// Returns a `TypedDate` instance that represents the current year, month, day, and hour, at the moment of access.
47 | static var now: Self {
48 | Date().scope(to: \.hour)
49 | }
50 | }
51 |
52 | public extension TypedDateOfMinute {
53 | /// Returns a `TypedDate` instance that represents the current year, month, day, hour, and minute, at the moment of access.
54 | static var now: Self {
55 | Date().scope(to: \.minute)
56 | }
57 | }
58 |
59 | public extension TypedDateOfSecond {
60 | /// Returns a `TypedDate` instance that represents the current year, month, day, hour, minute, and second, at the moment of access.
61 | static var now: Self {
62 | Date().scope(to: \.second)
63 | }
64 | }
65 |
66 | public extension TypedDateOfNanosecond {
67 | /// Returns a `TypedDate` instance that represents the current year, month, day, hour, minute, second, and nanosecond, at the moment of access.
68 | static var now: Self {
69 | Date().scope(to: \.nanosecond)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Sources/TypedDate/TypedDateProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// The base protocol for typed date
4 | public protocol TypedDateProtocol: Hashable, Codable, Equatable, Comparable {
5 | var date: Date { get }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/TypedDate/exported.swift:
--------------------------------------------------------------------------------
1 | @_exported import TypedDateCore
2 |
--------------------------------------------------------------------------------
/Sources/TypedDateCore/TypedDateUnits+BinaryInteger.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol TypedDateUnit: BinaryInteger where Words == Int.Words, Magnitude == Int.Magnitude, IntegerLiteralType == Int.IntegerLiteralType {
4 | var value: Int { get }
5 | init(_: Int)
6 | }
7 |
8 | public extension TypedDateUnit {
9 | static var zero: Self {
10 | .init(0)
11 | }
12 |
13 | static func + (lhs: Self, rhs: Self) -> Self {
14 | Self(lhs.value + rhs.value)
15 | }
16 |
17 | static func - (lhs: Self, rhs: Self) -> Self {
18 | Self(lhs.value - rhs.value)
19 | }
20 |
21 | static var isSigned: Bool {
22 | Int.isSigned
23 | }
24 |
25 | init(_ source: T) where T : BinaryFloatingPoint {
26 | self.init(Int(source))
27 | }
28 |
29 | var words: Int.Words {
30 | value.words
31 | }
32 |
33 | var bitWidth: Int {
34 | value.bitWidth
35 | }
36 |
37 | var trailingZeroBitCount: Int {
38 | value.trailingZeroBitCount
39 | }
40 |
41 | static func / (lhs: Self, rhs: Self) -> Self {
42 | Self(lhs.value / rhs.value)
43 | }
44 |
45 | static func % (lhs: Self, rhs: Self) -> Self {
46 | Self(lhs.value % rhs.value)
47 | }
48 |
49 | static func %= (lhs: inout Self, rhs: Self) {
50 | lhs = Self(lhs.value % rhs.value)
51 | }
52 |
53 | static func * (lhs: Self, rhs: Self) -> Self {
54 | Self(lhs.value * rhs.value)
55 | }
56 |
57 | static func *= (lhs: inout Self, rhs: Self) {
58 | lhs = Self(lhs.value * rhs.value)
59 | }
60 |
61 | static func &= (lhs: inout Self, rhs: Self) {
62 | lhs = Self(lhs.value & rhs.value)
63 | }
64 |
65 | static func |= (lhs: inout Self, rhs: Self) {
66 | lhs = Self(lhs.value | rhs.value)
67 | }
68 |
69 | static func ^= (lhs: inout Self, rhs: Self) {
70 | lhs = Self(lhs.value ^ rhs.value)
71 | }
72 |
73 | var magnitude: UInt {
74 | value.magnitude
75 | }
76 |
77 | init(integerLiteral value: Int) {
78 | self.init(value)
79 | }
80 |
81 | static func <<= (lhs: inout Self, rhs: RHS) where RHS : BinaryInteger {
82 | lhs = Self(lhs.value << rhs)
83 | }
84 |
85 | static func >>= (lhs: inout Self, rhs: RHS) where RHS : BinaryInteger {
86 | lhs = Self(lhs.value >> rhs)
87 | }
88 |
89 | static prefix func ~ (x: Self) -> Self {
90 | Self(~x.value)
91 | }
92 |
93 | static func /= (lhs: inout Self, rhs: Self) {
94 | lhs = Self(lhs.value / rhs.value)
95 | }
96 |
97 | init?(exactly source: T) where T : BinaryInteger {
98 | if let integer = Int(exactly: source) {
99 | self.init(integer)
100 | } else {
101 | return nil
102 | }
103 | }
104 |
105 | init?(exactly source: T) where T : BinaryFloatingPoint {
106 | if let integer = Int(exactly: source) {
107 | self.init(integer)
108 | } else {
109 | return nil
110 | }
111 | }
112 |
113 | init(_ source: T) where T : BinaryInteger {
114 | self.init(Int(source))
115 | }
116 |
117 | init(truncatingIfNeeded source: T) where T : BinaryInteger {
118 | self.init(Int(truncatingIfNeeded: source))
119 | }
120 |
121 | init(clamping source: T) where T : BinaryInteger {
122 | self.init(Int(clamping: source))
123 | }
124 |
125 | func add(_ value: Int) -> Self {
126 | Self(self.value + value)
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Sources/TypedDateCore/TypedDateUnits.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | package let allUnitNames = [
4 | Year.typeName,
5 | Month.typeName,
6 | Day.typeName,
7 | Hour.typeName,
8 | Minute.typeName,
9 | Second.typeName,
10 | Nanosecond.typeName
11 | ]
12 |
13 | package extension TypedDateUnit {
14 | static var typeName: String {
15 | String(String(describing: Self.self))
16 | }
17 | }
18 |
19 | /// Representing a year.
20 | public struct Year: TypedDateUnit, Sendable {
21 | public let value: Int
22 |
23 | public init(_ year: Int) {
24 | self.value = year
25 | }
26 | }
27 |
28 | /// Representing a month.
29 | public struct Month: TypedDateUnit, Sendable {
30 | public let value: Int
31 |
32 | public init(_ month: Int) {
33 | self.value = month
34 | }
35 | }
36 |
37 | /// Representing a day.
38 | public struct Day: TypedDateUnit, Sendable {
39 | public let value: Int
40 |
41 | public init(_ day: Int) {
42 | self.value = day
43 | }
44 | }
45 |
46 | /// Representing a hour.
47 | public struct Hour: TypedDateUnit, Sendable {
48 | public let value: Int
49 |
50 | public init(_ hours: Int) {
51 | self.value = hours
52 | }
53 | }
54 |
55 | /// Representing a month.
56 | public struct Minute: TypedDateUnit, Sendable {
57 | public let value: Int
58 |
59 | public init(_ value: Int) {
60 | self.value = value
61 | }
62 | }
63 |
64 | /// Representing a second.
65 | public struct Second: TypedDateUnit, Sendable {
66 | public let value: Int
67 |
68 | public init(_ second: Int) {
69 | self.value = second
70 | }
71 | }
72 |
73 | /// Representing a nanosecond.
74 | public struct Nanosecond: TypedDateUnit, Sendable {
75 | public let value: Int
76 |
77 | public init(_ nanosecond: Int) {
78 | self.value = nanosecond
79 | }
80 | }
81 |
82 | /// Representing a fractional second.
83 | public struct FractionalSecond: Sendable {
84 | public let value: Double
85 |
86 | public var seconds: Int {
87 | Int(value)
88 | }
89 | public var nanoseconds: Int {
90 | Int((value - floor(value)) * 1_000_000_000)
91 | }
92 |
93 | public init(_ value: Double) {
94 | self.value = value
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Sources/TypedDateMacros/EraseContextMacro.swift:
--------------------------------------------------------------------------------
1 | import SwiftCompilerPlugin
2 | import SwiftSyntax
3 | import SwiftSyntaxBuilder
4 | import SwiftSyntaxMacros
5 | import SwiftDiagnostics
6 | import TypedDateCore
7 | import Foundation
8 |
9 | // Note: This macro does not generate a detailed DiagnosticError because it is only used in TypedDate package.
10 | package struct EraseContextMacro: MemberMacro {
11 | package static func expansion(
12 | of node: SwiftSyntax.AttributeSyntax,
13 | providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,
14 | in context: some SwiftSyntaxMacros.MacroExpansionContext
15 | ) throws -> [SwiftSyntax.DeclSyntax] {
16 | guard let extensionDecl = declaration.as(ExtensionDeclSyntax.self),
17 | let genericArgument = extensionDecl.extendedType.as(IdentifierTypeSyntax.self)?.genericArgumentClause?.arguments.first?.argument
18 | else {
19 | throw DiagnosticsError.init(diagnostics: [])
20 | }
21 |
22 | let baseComponents = if let identifierType = genericArgument.as(IdentifierTypeSyntax.self) {
23 | [identifierType.name.text]
24 | } else if let tupleType = genericArgument.as(TupleTypeSyntax.self) {
25 | tupleType.argumentTypes
26 | } else {
27 | throw DiagnosticsError.init(diagnostics: [])
28 | }
29 |
30 | return [
31 | try generateEraseContext(of: baseComponents).cast(DeclSyntax.self),
32 | ]
33 | }
34 |
35 | private static func generateEraseContext(of baseComponents: [String]) throws -> StructDeclSyntax {
36 | StructDeclSyntax(
37 | name: .identifier("_\(baseComponents.last ?? "_")EraseContext"),
38 | memberBlock: try MemberBlockSyntax {
39 | try MemberBlockItemListSyntax {
40 | try MemberBlockItemSyntax(
41 | decl: VariableDeclSyntax("private let base: Components")
42 | )
43 | let erasedComponents = baseComponents.allSubarrays().dropLast()
44 | for components in erasedComponents {
45 | let type = components.returnType
46 | let typeName = components.last?.lowercased() ?? ""
47 | MemberBlockItemSyntax(
48 | decl: DeclSyntax("public let \(raw: typeName): (TypedDate<\(raw: type)>.Type, \(raw: type))")
49 | )
50 | }
51 | try MemberBlockItemSyntax(
52 | decl: InitializerDeclSyntax("init(base: Components)") {
53 | """
54 | self.base = base
55 | \(raw: erasedComponents.map { generateInitializerCodeBlockList(of: $0) }.joined(separator: "\n"))
56 | """
57 | }
58 | )
59 | }
60 | }
61 | )
62 | }
63 |
64 | private static func generateInitializerCodeBlockList(of erasedComponents: [String]) -> String {
65 | let type = erasedComponents.returnType
66 | let arguments = erasedComponents
67 | .enumerated()
68 | .map { i, _ in "base.\(i)" }
69 | .returnType
70 | return "\(erasedComponents.last!.lowercased()) = (TypedDate<\(type)>.self, \(arguments))"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Sources/TypedDateMacros/FillInContextMacro.swift:
--------------------------------------------------------------------------------
1 | import SwiftCompilerPlugin
2 | import SwiftSyntax
3 | import SwiftSyntaxBuilder
4 | import SwiftSyntaxMacros
5 | import SwiftDiagnostics
6 | import TypedDateCore
7 | import Foundation
8 |
9 | // Note: This macro does not generate a detailed DiagnosticError because it is only used in TypedDate package.
10 | package struct FillInContextMacro: MemberMacro {
11 | package static func expansion(
12 | of node: SwiftSyntax.AttributeSyntax,
13 | providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,
14 | in context: some SwiftSyntaxMacros.MacroExpansionContext
15 | ) throws -> [SwiftSyntax.DeclSyntax] {
16 | guard let extensionDecl = declaration.as(ExtensionDeclSyntax.self),
17 | let genericArgument = extensionDecl.extendedType.as(IdentifierTypeSyntax.self)?.genericArgumentClause?.arguments.first?.argument
18 | else {
19 | throw DiagnosticsError.init(diagnostics: [])
20 | }
21 |
22 | let baseComponents = if let identifierType = genericArgument.as(IdentifierTypeSyntax.self) {
23 | [identifierType.name.text]
24 | } else if let tupleType = genericArgument.as(TupleTypeSyntax.self) {
25 | tupleType.argumentTypes
26 | } else {
27 | throw DiagnosticsError.init(diagnostics: [])
28 | }
29 |
30 | return [
31 | try generateFillContext(of: baseComponents).cast(DeclSyntax.self),
32 | ]
33 | }
34 |
35 | private static func generateFillContext(of baseComponents: [String]) throws -> StructDeclSyntax {
36 | StructDeclSyntax(
37 | name: .identifier("_\(baseComponents.last ?? "")FillInContext"),
38 | memberBlock: try MemberBlockSyntax {
39 | try MemberBlockItemListSyntax {
40 | let convertComponents = allUnitNames.allSubarrays().filter({ !($0.count == 1 && $0.first == baseComponents.first ?? "") })
41 | for subarray in convertComponents {
42 | if let property = try generateUnitProperty(baseComponents: baseComponents, to: subarray) {
43 | MemberBlockItemSyntax(decl: property)
44 | }
45 | }
46 |
47 | try MemberBlockItemSyntax(
48 | decl: VariableDeclSyntax("private let base: Components")
49 | )
50 |
51 | try MemberBlockItemSyntax(
52 | decl: InitializerDeclSyntax("init(base: Components)") {
53 | """
54 | self.base = base
55 | \(raw: convertComponents.compactMap { generateInitializerCodeBlockList(baseComponents: baseComponents, to: $0) }.joined(separator: "\n"))
56 | """
57 | }
58 | )
59 | }
60 | }
61 | )
62 | }
63 |
64 | private static func generateNecessaryArgumentTypes(
65 | baseComponents: [String],
66 | to convertComponents: [String]
67 | ) -> [String] {
68 | var convertComponents = convertComponents
69 | convertComponents.removeAll { baseComponents.contains($0) }
70 | return convertComponents
71 | }
72 |
73 | private static func generateInitializerCodeBlockList(
74 | baseComponents: [String],
75 | to convertComponents: [String]
76 | ) -> String? {
77 | let bases: String = baseComponents.count == 1 ? "base" : baseComponents.enumerated().map { i, _ in "base.\(i)" }.joined(separator: ", ")
78 | let diff = convertComponents.count - baseComponents.count
79 |
80 | guard diff >= 1 else {
81 | return nil
82 | }
83 |
84 | let passedArguments: String = diff == 1 ? "data" : (0.. VariableDeclSyntax? {
94 | let types = generateNecessaryArgumentTypes(baseComponents: baseComponents, to: convertComponents)
95 | guard types.count >= 1 else {
96 | return nil
97 | }
98 | return try VariableDeclSyntax(
99 | """
100 | public let \(raw: convertComponents.last!.lowercased()): \(raw: types.closureParameter) -> \(raw: convertComponents.returnType)
101 | """
102 | )
103 | }
104 | }
105 |
106 | extension TupleTypeSyntax {
107 | var argumentTypes: [String] {
108 | elements.compactMap { $0.type.as(IdentifierTypeSyntax.self)?.name.text }
109 | }
110 | }
111 |
112 | extension [String] {
113 | var closureParameter: String {
114 | if indices.count == 1 {
115 | "(\(self[0]))"
116 | } else {
117 | "((\(joined(separator: ", "))))"
118 | }
119 | }
120 |
121 | var returnType: String {
122 | if indices.count == 1 {
123 | self[0]
124 | } else {
125 | "(\(self.joined(separator: ", ")))"
126 | }
127 | }
128 | }
129 |
130 | extension Array {
131 | func allSubarrays() -> [[Element]] {
132 | var result: [[Element]] = []
133 | for end in 1...self.count {
134 | result.append(Array(self.prefix(end)))
135 | }
136 | return result
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Sources/TypedDateMacros/MacroPlugins.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftCompilerPlugin
3 | import SwiftSyntaxMacros
4 |
5 | @main
6 | struct TypedDatePlugin: CompilerPlugin {
7 | let providingMacros: [Macro.Type] = [
8 | FillInContextMacro.self,
9 | EraseContextMacro.self,
10 | ModifyContextMacro.self
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/TypedDateMacros/ModifyContextMacro.swift:
--------------------------------------------------------------------------------
1 | import SwiftCompilerPlugin
2 | import SwiftSyntax
3 | import SwiftSyntaxBuilder
4 | import SwiftSyntaxMacros
5 | import SwiftDiagnostics
6 | import TypedDateCore
7 | import Foundation
8 |
9 | // Note: This macro does not generate a detailed DiagnosticError because it is only used in TypedDate package.
10 | package struct ModifyContextMacro: MemberMacro {
11 | package static func expansion(
12 | of node: SwiftSyntax.AttributeSyntax,
13 | providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,
14 | in context: some SwiftSyntaxMacros.MacroExpansionContext
15 | ) throws -> [SwiftSyntax.DeclSyntax] {
16 | guard let extensionDecl = declaration.as(ExtensionDeclSyntax.self),
17 | let genericArgument = extensionDecl.extendedType.as(IdentifierTypeSyntax.self)?.genericArgumentClause?.arguments.first?.argument
18 | else {
19 | throw DiagnosticsError.init(diagnostics: [])
20 | }
21 |
22 | let baseComponents = if let identifierType = genericArgument.as(IdentifierTypeSyntax.self) {
23 | [identifierType.name.text]
24 | } else if let tupleType = genericArgument.as(TupleTypeSyntax.self) {
25 | tupleType.argumentTypes
26 | } else {
27 | throw DiagnosticsError.init(diagnostics: [])
28 | }
29 |
30 | return [
31 | try generateModifyContext(of: baseComponents).cast(DeclSyntax.self),
32 | ]
33 | }
34 |
35 | private static func generateModifyContext(of baseComponents: [String]) throws -> StructDeclSyntax {
36 | StructDeclSyntax(
37 | name: .identifier("_\(baseComponents.last ?? "_")ModifyContext"),
38 | memberBlock: try MemberBlockSyntax {
39 | try MemberBlockItemListSyntax {
40 | try MemberBlockItemSyntax(
41 | decl: VariableDeclSyntax("private let base: Components")
42 | )
43 | for component in baseComponents {
44 | let typeName = component.lowercased()
45 | MemberBlockItemSyntax(
46 | decl: DeclSyntax("public let \(raw: typeName): (\(raw: component), (\(raw: component)) -> Components)")
47 | )
48 | }
49 | try MemberBlockItemSyntax(
50 | decl: InitializerDeclSyntax("init(base: Components)") {
51 | """
52 | self.base = base
53 | \(raw: generateInitializerCodeBlockList(of: baseComponents))
54 | """
55 | }
56 | )
57 | }
58 | }
59 | )
60 | }
61 |
62 | private static func generateInitializerCodeBlockList(
63 | of baseComponents: [String]
64 | ) -> String {
65 | var codeBlock = [String]()
66 | for (rowIndex, component) in baseComponents.enumerated() {
67 | let typeName = component.lowercased()
68 | let argument = baseComponents.count == 1 ? "base" : "base.\(rowIndex)"
69 | let closureReturnValue = baseComponents
70 | .enumerated()
71 | .map { closureIndex, closureComponent in
72 | if rowIndex == closureIndex {
73 | typeName
74 | } else {
75 | "base.\(closureIndex)"
76 | }
77 | }
78 | .joined(separator: ", ")
79 | codeBlock.append(
80 | """
81 | self.\(typeName) = (\(argument), { \(typeName) in (\(closureReturnValue)) })
82 | """
83 | )
84 | }
85 | return codeBlock.joined(separator: "\n")
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Tests/TypedDateMacrosTests/EraseContextMacroTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 | import MacroTesting
4 | import TypedDateCore
5 | @testable import TypedDateMacros
6 |
7 | final class EraseContextMacroTests: XCTestCase {
8 | override func invokeTest() {
9 | withMacroTesting(macros: ["EraseContext": EraseContextMacro.self]) {
10 | super.invokeTest()
11 | }
12 | }
13 |
14 | func testNanosecondExpansion() {
15 | assertMacro {
16 | """
17 | @EraseContext
18 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {}
19 | """
20 | } expansion: {
21 | """
22 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {
23 |
24 | struct _NanosecondEraseContext {
25 | private let base: Components
26 | public let year: (TypedDate.Type, Year)
27 | public let month: (TypedDate<(Year, Month)>.Type, (Year, Month))
28 | public let day: (TypedDate<(Year, Month, Day)>.Type, (Year, Month, Day))
29 | public let hour: (TypedDate<(Year, Month, Day, Hour)>.Type, (Year, Month, Day, Hour))
30 | public let minute: (TypedDate<(Year, Month, Day, Hour, Minute)>.Type, (Year, Month, Day, Hour, Minute))
31 | public let second: (TypedDate<(Year, Month, Day, Hour, Minute, Second)>.Type, (Year, Month, Day, Hour, Minute, Second))
32 | init(base: Components) {
33 | self.base = base
34 | year = (TypedDate.self, base.0)
35 | month = (TypedDate<(Year, Month)>.self, (base.0, base.1))
36 | day = (TypedDate<(Year, Month, Day)>.self, (base.0, base.1, base.2))
37 | hour = (TypedDate<(Year, Month, Day, Hour)>.self, (base.0, base.1, base.2, base.3))
38 | minute = (TypedDate<(Year, Month, Day, Hour, Minute)>.self, (base.0, base.1, base.2, base.3, base.4))
39 | second = (TypedDate<(Year, Month, Day, Hour, Minute, Second)>.self, (base.0, base.1, base.2, base.3, base.4, base.5))
40 | }
41 | }}
42 | """
43 | }
44 | }
45 |
46 | func testSecondExpansion() {
47 | assertMacro {
48 | """
49 | @EraseContext
50 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {}
51 | """
52 | } expansion: {
53 | """
54 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
55 |
56 | struct _SecondEraseContext {
57 | private let base: Components
58 | public let year: (TypedDate.Type, Year)
59 | public let month: (TypedDate<(Year, Month)>.Type, (Year, Month))
60 | public let day: (TypedDate<(Year, Month, Day)>.Type, (Year, Month, Day))
61 | public let hour: (TypedDate<(Year, Month, Day, Hour)>.Type, (Year, Month, Day, Hour))
62 | public let minute: (TypedDate<(Year, Month, Day, Hour, Minute)>.Type, (Year, Month, Day, Hour, Minute))
63 | init(base: Components) {
64 | self.base = base
65 | year = (TypedDate.self, base.0)
66 | month = (TypedDate<(Year, Month)>.self, (base.0, base.1))
67 | day = (TypedDate<(Year, Month, Day)>.self, (base.0, base.1, base.2))
68 | hour = (TypedDate<(Year, Month, Day, Hour)>.self, (base.0, base.1, base.2, base.3))
69 | minute = (TypedDate<(Year, Month, Day, Hour, Minute)>.self, (base.0, base.1, base.2, base.3, base.4))
70 | }
71 | }}
72 | """
73 | }
74 | }
75 |
76 | func testMinuteExpansion() {
77 | assertMacro {
78 | """
79 | @EraseContext
80 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {}
81 | """
82 | } expansion: {
83 | """
84 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {
85 |
86 | struct _MinuteEraseContext {
87 | private let base: Components
88 | public let year: (TypedDate.Type, Year)
89 | public let month: (TypedDate<(Year, Month)>.Type, (Year, Month))
90 | public let day: (TypedDate<(Year, Month, Day)>.Type, (Year, Month, Day))
91 | public let hour: (TypedDate<(Year, Month, Day, Hour)>.Type, (Year, Month, Day, Hour))
92 | init(base: Components) {
93 | self.base = base
94 | year = (TypedDate.self, base.0)
95 | month = (TypedDate<(Year, Month)>.self, (base.0, base.1))
96 | day = (TypedDate<(Year, Month, Day)>.self, (base.0, base.1, base.2))
97 | hour = (TypedDate<(Year, Month, Day, Hour)>.self, (base.0, base.1, base.2, base.3))
98 | }
99 | }}
100 | """
101 | }
102 | }
103 | func testHourExpansion() {
104 | assertMacro {
105 | """
106 | @EraseContext
107 | public extension TypedDate<(Year, Month, Day, Hour)> {}
108 | """
109 | } expansion: {
110 | """
111 | public extension TypedDate<(Year, Month, Day, Hour)> {
112 |
113 | struct _HourEraseContext {
114 | private let base: Components
115 | public let year: (TypedDate.Type, Year)
116 | public let month: (TypedDate<(Year, Month)>.Type, (Year, Month))
117 | public let day: (TypedDate<(Year, Month, Day)>.Type, (Year, Month, Day))
118 | init(base: Components) {
119 | self.base = base
120 | year = (TypedDate.self, base.0)
121 | month = (TypedDate<(Year, Month)>.self, (base.0, base.1))
122 | day = (TypedDate<(Year, Month, Day)>.self, (base.0, base.1, base.2))
123 | }
124 | }}
125 | """
126 | }
127 | }
128 | func testDayExpansion() {
129 | assertMacro {
130 | """
131 | @EraseContext
132 | public extension TypedDate<(Year, Month, Day)> {}
133 | """
134 | } expansion: {
135 | """
136 | public extension TypedDate<(Year, Month, Day)> {
137 |
138 | struct _DayEraseContext {
139 | private let base: Components
140 | public let year: (TypedDate.Type, Year)
141 | public let month: (TypedDate<(Year, Month)>.Type, (Year, Month))
142 | init(base: Components) {
143 | self.base = base
144 | year = (TypedDate.self, base.0)
145 | month = (TypedDate<(Year, Month)>.self, (base.0, base.1))
146 | }
147 | }}
148 | """
149 | }
150 | }
151 | func testMonthExpansion() {
152 | assertMacro {
153 | """
154 | @EraseContext
155 | public extension TypedDate<(Year, Month)> {}
156 | """
157 | } expansion: {
158 | """
159 | public extension TypedDate<(Year, Month)> {
160 |
161 | struct _MonthEraseContext {
162 | private let base: Components
163 | public let year: (TypedDate.Type, Year)
164 | init(base: Components) {
165 | self.base = base
166 | year = (TypedDate.self, base.0)
167 | }
168 | }}
169 | """
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/Tests/TypedDateMacrosTests/FillInContextMacroTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 | import MacroTesting
4 | import TypedDateCore
5 | @testable import TypedDateMacros
6 |
7 | final class FillInContextMacroTests: XCTestCase {
8 | override func invokeTest() {
9 | withMacroTesting(macros: ["FillInContext": FillInContextMacro.self]) {
10 | super.invokeTest()
11 | }
12 | }
13 |
14 | func testSecondExpansion() {
15 | assertMacro {
16 | """
17 | @FillInContext
18 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {}
19 | """
20 | } expansion: {
21 | """
22 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
23 |
24 | struct _SecondFillInContext {
25 | public let nanosecond: (Nanosecond) -> (Year, Month, Day, Hour, Minute, Second, Nanosecond)
26 | private let base: Components
27 | init(base: Components) {
28 | self.base = base
29 | nanosecond = { data in (base.0, base.1, base.2, base.3, base.4, base.5, data) }
30 | }
31 | }}
32 | """
33 | }
34 | }
35 |
36 | func testMinuteExpansion() {
37 | assertMacro {
38 | """
39 | @FillInContext
40 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {}
41 | """
42 | } expansion: {
43 | """
44 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {
45 |
46 | struct _MinuteFillInContext {
47 | public let second: (Second) -> (Year, Month, Day, Hour, Minute, Second)
48 | public let nanosecond: ((Second, Nanosecond)) -> (Year, Month, Day, Hour, Minute, Second, Nanosecond)
49 | private let base: Components
50 | init(base: Components) {
51 | self.base = base
52 | second = { data in (base.0, base.1, base.2, base.3, base.4, data) }
53 | nanosecond = { data in (base.0, base.1, base.2, base.3, base.4, data.0, data.1) }
54 | }
55 | }}
56 | """
57 | }
58 | }
59 | func testHourExpansion() {
60 | assertMacro {
61 | """
62 | @FillInContext
63 | public extension TypedDate<(Year, Month, Day, Hour)> {}
64 | """
65 | } expansion: {
66 | """
67 | public extension TypedDate<(Year, Month, Day, Hour)> {
68 |
69 | struct _HourFillInContext {
70 | public let minute: (Minute) -> (Year, Month, Day, Hour, Minute)
71 | public let second: ((Minute, Second)) -> (Year, Month, Day, Hour, Minute, Second)
72 | public let nanosecond: ((Minute, Second, Nanosecond)) -> (Year, Month, Day, Hour, Minute, Second, Nanosecond)
73 | private let base: Components
74 | init(base: Components) {
75 | self.base = base
76 | minute = { data in (base.0, base.1, base.2, base.3, data) }
77 | second = { data in (base.0, base.1, base.2, base.3, data.0, data.1) }
78 | nanosecond = { data in (base.0, base.1, base.2, base.3, data.0, data.1, data.2) }
79 | }
80 | }}
81 | """
82 | }
83 | }
84 | func testDayExpansion() {
85 | assertMacro {
86 | """
87 | @FillInContext
88 | public extension TypedDate<(Year, Month, Day)> {}
89 | """
90 | } expansion: {
91 | """
92 | public extension TypedDate<(Year, Month, Day)> {
93 |
94 | struct _DayFillInContext {
95 | public let hour: (Hour) -> (Year, Month, Day, Hour)
96 | public let minute: ((Hour, Minute)) -> (Year, Month, Day, Hour, Minute)
97 | public let second: ((Hour, Minute, Second)) -> (Year, Month, Day, Hour, Minute, Second)
98 | public let nanosecond: ((Hour, Minute, Second, Nanosecond)) -> (Year, Month, Day, Hour, Minute, Second, Nanosecond)
99 | private let base: Components
100 | init(base: Components) {
101 | self.base = base
102 | hour = { data in (base.0, base.1, base.2, data) }
103 | minute = { data in (base.0, base.1, base.2, data.0, data.1) }
104 | second = { data in (base.0, base.1, base.2, data.0, data.1, data.2) }
105 | nanosecond = { data in (base.0, base.1, base.2, data.0, data.1, data.2, data.3) }
106 | }
107 | }}
108 | """
109 | }
110 | }
111 | func testMonthExpansion() {
112 | assertMacro {
113 | """
114 | @FillInContext
115 | public extension TypedDate<(Year, Month)> {}
116 | """
117 | } expansion: {
118 | """
119 | public extension TypedDate<(Year, Month)> {
120 |
121 | struct _MonthFillInContext {
122 | public let day: (Day) -> (Year, Month, Day)
123 | public let hour: ((Day, Hour)) -> (Year, Month, Day, Hour)
124 | public let minute: ((Day, Hour, Minute)) -> (Year, Month, Day, Hour, Minute)
125 | public let second: ((Day, Hour, Minute, Second)) -> (Year, Month, Day, Hour, Minute, Second)
126 | public let nanosecond: ((Day, Hour, Minute, Second, Nanosecond)) -> (Year, Month, Day, Hour, Minute, Second, Nanosecond)
127 | private let base: Components
128 | init(base: Components) {
129 | self.base = base
130 | day = { data in (base.0, base.1, data) }
131 | hour = { data in (base.0, base.1, data.0, data.1) }
132 | minute = { data in (base.0, base.1, data.0, data.1, data.2) }
133 | second = { data in (base.0, base.1, data.0, data.1, data.2, data.3) }
134 | nanosecond = { data in (base.0, base.1, data.0, data.1, data.2, data.3, data.4) }
135 | }
136 | }}
137 | """
138 | }
139 | }
140 |
141 | func testYearExpansion() {
142 | assertMacro {
143 | """
144 | @FillInContext
145 | public extension TypedDate {}
146 | """
147 | } expansion: {
148 | """
149 | public extension TypedDate {
150 |
151 | struct _YearFillInContext {
152 | public let month: (Month) -> (Year, Month)
153 | public let day: ((Month, Day)) -> (Year, Month, Day)
154 | public let hour: ((Month, Day, Hour)) -> (Year, Month, Day, Hour)
155 | public let minute: ((Month, Day, Hour, Minute)) -> (Year, Month, Day, Hour, Minute)
156 | public let second: ((Month, Day, Hour, Minute, Second)) -> (Year, Month, Day, Hour, Minute, Second)
157 | public let nanosecond: ((Month, Day, Hour, Minute, Second, Nanosecond)) -> (Year, Month, Day, Hour, Minute, Second, Nanosecond)
158 | private let base: Components
159 | init(base: Components) {
160 | self.base = base
161 | month = { data in (base, data) }
162 | day = { data in (base, data.0, data.1) }
163 | hour = { data in (base, data.0, data.1, data.2) }
164 | minute = { data in (base, data.0, data.1, data.2, data.3) }
165 | second = { data in (base, data.0, data.1, data.2, data.3, data.4) }
166 | nanosecond = { data in (base, data.0, data.1, data.2, data.3, data.4, data.5) }
167 | }
168 | }}
169 | """
170 | }
171 | }
172 | }
173 |
174 |
--------------------------------------------------------------------------------
/Tests/TypedDateMacrosTests/ModifyContextMacroTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 | import MacroTesting
4 | import TypedDateCore
5 | @testable import TypedDateMacros
6 |
7 | final class ModifyContextMacroTests: XCTestCase {
8 | override func invokeTest() {
9 | withMacroTesting(macros: ["ModifyContext": ModifyContextMacro.self]) {
10 | super.invokeTest()
11 | }
12 | }
13 |
14 | func testNanosecondExpansion() {
15 | assertMacro {
16 | """
17 | @ModifyContext
18 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {}
19 | """
20 | } expansion: {
21 | """
22 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {
23 |
24 | struct _NanosecondModifyContext {
25 | private let base: Components
26 | public let year: (Year, (Year) -> Components)
27 | public let month: (Month, (Month) -> Components)
28 | public let day: (Day, (Day) -> Components)
29 | public let hour: (Hour, (Hour) -> Components)
30 | public let minute: (Minute, (Minute) -> Components)
31 | public let second: (Second, (Second) -> Components)
32 | public let nanosecond: (Nanosecond, (Nanosecond) -> Components)
33 | init(base: Components) {
34 | self.base = base
35 | self.year = (base.0, { year in (year, base.1, base.2, base.3, base.4, base.5, base.6) })
36 | self.month = (base.1, { month in (base.0, month, base.2, base.3, base.4, base.5, base.6) })
37 | self.day = (base.2, { day in (base.0, base.1, day, base.3, base.4, base.5, base.6) })
38 | self.hour = (base.3, { hour in (base.0, base.1, base.2, hour, base.4, base.5, base.6) })
39 | self.minute = (base.4, { minute in (base.0, base.1, base.2, base.3, minute, base.5, base.6) })
40 | self.second = (base.5, { second in (base.0, base.1, base.2, base.3, base.4, second, base.6) })
41 | self.nanosecond = (base.6, { nanosecond in (base.0, base.1, base.2, base.3, base.4, base.5, nanosecond) })
42 | }
43 | }}
44 | """
45 | }
46 | }
47 |
48 | func testSecondExpansion() {
49 | assertMacro {
50 | """
51 | @ModifyContext
52 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {}
53 | """
54 | } expansion: {
55 | """
56 | public extension TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
57 |
58 | struct _SecondModifyContext {
59 | private let base: Components
60 | public let year: (Year, (Year) -> Components)
61 | public let month: (Month, (Month) -> Components)
62 | public let day: (Day, (Day) -> Components)
63 | public let hour: (Hour, (Hour) -> Components)
64 | public let minute: (Minute, (Minute) -> Components)
65 | public let second: (Second, (Second) -> Components)
66 | init(base: Components) {
67 | self.base = base
68 | self.year = (base.0, { year in (year, base.1, base.2, base.3, base.4, base.5) })
69 | self.month = (base.1, { month in (base.0, month, base.2, base.3, base.4, base.5) })
70 | self.day = (base.2, { day in (base.0, base.1, day, base.3, base.4, base.5) })
71 | self.hour = (base.3, { hour in (base.0, base.1, base.2, hour, base.4, base.5) })
72 | self.minute = (base.4, { minute in (base.0, base.1, base.2, base.3, minute, base.5) })
73 | self.second = (base.5, { second in (base.0, base.1, base.2, base.3, base.4, second) })
74 | }
75 | }}
76 | """
77 | }
78 | }
79 |
80 | func testMinuteExpansion() {
81 | assertMacro {
82 | """
83 | @ModifyContext
84 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {}
85 | """
86 | } expansion: {
87 | """
88 | public extension TypedDate<(Year, Month, Day, Hour, Minute)> {
89 |
90 | struct _MinuteModifyContext {
91 | private let base: Components
92 | public let year: (Year, (Year) -> Components)
93 | public let month: (Month, (Month) -> Components)
94 | public let day: (Day, (Day) -> Components)
95 | public let hour: (Hour, (Hour) -> Components)
96 | public let minute: (Minute, (Minute) -> Components)
97 | init(base: Components) {
98 | self.base = base
99 | self.year = (base.0, { year in (year, base.1, base.2, base.3, base.4) })
100 | self.month = (base.1, { month in (base.0, month, base.2, base.3, base.4) })
101 | self.day = (base.2, { day in (base.0, base.1, day, base.3, base.4) })
102 | self.hour = (base.3, { hour in (base.0, base.1, base.2, hour, base.4) })
103 | self.minute = (base.4, { minute in (base.0, base.1, base.2, base.3, minute) })
104 | }
105 | }}
106 | """
107 | }
108 | }
109 |
110 | func testHourExpansion() {
111 | assertMacro {
112 | """
113 | @ModifyContext
114 | public extension TypedDate<(Year, Month, Day, Hour)> {}
115 | """
116 | } expansion: {
117 | """
118 | public extension TypedDate<(Year, Month, Day, Hour)> {
119 |
120 | struct _HourModifyContext {
121 | private let base: Components
122 | public let year: (Year, (Year) -> Components)
123 | public let month: (Month, (Month) -> Components)
124 | public let day: (Day, (Day) -> Components)
125 | public let hour: (Hour, (Hour) -> Components)
126 | init(base: Components) {
127 | self.base = base
128 | self.year = (base.0, { year in (year, base.1, base.2, base.3) })
129 | self.month = (base.1, { month in (base.0, month, base.2, base.3) })
130 | self.day = (base.2, { day in (base.0, base.1, day, base.3) })
131 | self.hour = (base.3, { hour in (base.0, base.1, base.2, hour) })
132 | }
133 | }}
134 | """
135 | }
136 | }
137 |
138 | func testDayExpansion() {
139 | assertMacro {
140 | """
141 | @ModifyContext
142 | public extension TypedDate<(Year, Month, Day)> {}
143 | """
144 | } expansion: {
145 | """
146 | public extension TypedDate<(Year, Month, Day)> {
147 |
148 | struct _DayModifyContext {
149 | private let base: Components
150 | public let year: (Year, (Year) -> Components)
151 | public let month: (Month, (Month) -> Components)
152 | public let day: (Day, (Day) -> Components)
153 | init(base: Components) {
154 | self.base = base
155 | self.year = (base.0, { year in (year, base.1, base.2) })
156 | self.month = (base.1, { month in (base.0, month, base.2) })
157 | self.day = (base.2, { day in (base.0, base.1, day) })
158 | }
159 | }}
160 | """
161 | }
162 | }
163 |
164 | func testMonthExpansion() {
165 | assertMacro {
166 | """
167 | @ModifyContext
168 | public extension TypedDate<(Year, Month)> {}
169 | """
170 | } expansion: {
171 | """
172 | public extension TypedDate<(Year, Month)> {
173 |
174 | struct _MonthModifyContext {
175 | private let base: Components
176 | public let year: (Year, (Year) -> Components)
177 | public let month: (Month, (Month) -> Components)
178 | init(base: Components) {
179 | self.base = base
180 | self.year = (base.0, { year in (year, base.1) })
181 | self.month = (base.1, { month in (base.0, month) })
182 | }
183 | }}
184 | """
185 | }
186 | }
187 |
188 | func testYearExpansion() {
189 | assertMacro {
190 | """
191 | @ModifyContext
192 | public extension TypedDate<(Year)> {}
193 | """
194 | } expansion: {
195 | """
196 | public extension TypedDate<(Year)> {
197 |
198 | struct _YearModifyContext {
199 | private let base: Components
200 | public let year: (Year, (Year) -> Components)
201 | init(base: Components) {
202 | self.base = base
203 | self.year = (base, { year in (year) })
204 | }
205 | }}
206 | """
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/Scaffolding.swift:
--------------------------------------------------------------------------------
1 | import Testing
2 | import XCTest
3 |
4 | final class AllTests: XCTestCase {
5 | func testAll() async {
6 | await XCTestScaffold.runAllTests(hostedBy: self)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateAddTests.swift:
--------------------------------------------------------------------------------
1 | import Testing
2 | @testable import TypedDate
3 | import TypedDateCore
4 | import Foundation
5 |
6 | @Suite
7 | struct TypedDateAddTests {
8 | let calendar: Calendar
9 | let testSupport: TypedDateTestSupport
10 |
11 | init() {
12 | calendar = Calendar(identifier: .gregorian)
13 | testSupport = TypedDateTestSupport.init(calendar: calendar)
14 | }
15 |
16 | @Test
17 | func addYear() {
18 | let typedDate = testSupport.generateTypedYearDate().add(\.year, 1)
19 |
20 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
21 | }
22 |
23 | @Test
24 | func addMonth() {
25 | let typedDate = testSupport.generateTypedMonthDate().add(\.month, 1)
26 | #expect(typedDate.month(calendar: calendar) == testSupport.month.add(1))
27 | }
28 |
29 | @Test
30 | func addHour() {
31 | let typedDate = testSupport.generateTypedHourDate().add(\.hour, 1)
32 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.add(1))
33 | }
34 |
35 | @Test
36 | func addMinute() {
37 | let typedDate = testSupport.generateTypedMinuteDate().add(\.minute, 1)
38 | #expect(typedDate.minute(calendar: calendar) == testSupport.minute.add(1))
39 | }
40 |
41 | @Test
42 | func addSecond() {
43 | let typedDate = testSupport.generateTypedSecondDate().add(\.second, 1)
44 | #expect(typedDate.second(calendar: calendar) == testSupport.second.add(1))
45 | }
46 |
47 | @Test
48 | func subtractSecond() {
49 | let typedDate = testSupport.generateTypedSecondDate()
50 | let yesterdayTypedDate = typedDate.add(\.second, Second(-(60 * 60 * 24)))
51 | #expect(typedDate.erase(to: \.day) != yesterdayTypedDate.erase(to: \.day))
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateCodableTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | @Suite
6 | struct TypedDateCodableTests {
7 | let calendar: Calendar
8 | let testSupport: TypedDateTestSupport
9 |
10 | init() {
11 | calendar = Calendar(identifier: .gregorian)
12 | testSupport = TypedDateTestSupport.init(calendar: calendar)
13 | }
14 |
15 | @Test
16 | func testCodable() throws {
17 | let typedDate = testSupport.generateTypedNanosecondDate()
18 | let jsonEncoder = JSONEncoder()
19 | let data = try jsonEncoder.encode(typedDate)
20 | let decoded1 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)>.self, from: data)
21 | testSupport.assertTypedDate(for: decoded1)
22 | let decoded2 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour, Minute, Second)>.self, from: data)
23 | testSupport.assertTypedDate(for: decoded2)
24 | let decoded3 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour, Minute)>.self, from: data)
25 | testSupport.assertTypedDate(for: decoded3)
26 | let decoded4 = try JSONDecoder().decode(TypedDate<(Year, Month, Day, Hour)>.self, from: data)
27 | testSupport.assertTypedDate(for: decoded4)
28 | let decoded5 = try JSONDecoder().decode(TypedDate<(Year, Month, Day)>.self, from: data)
29 | testSupport.assertTypedDate(for: decoded5)
30 | let decoded6 = try JSONDecoder().decode(TypedDate<(Year, Month)>.self, from: data)
31 | testSupport.assertTypedDate(for: decoded6)
32 | let decoded7 = try JSONDecoder().decode(TypedDate<(Year, Month)>.self, from: data)
33 | testSupport.assertTypedDate(for: decoded7)
34 | let decoded8 = try JSONDecoder().decode(TypedDate<(Year)>.self, from: data)
35 | testSupport.assertTypedDate(for: decoded8)
36 | }
37 |
38 | @Test
39 | func testAnotherModelCodable() throws {
40 | struct Model: Codable, Equatable {
41 | let id: Int
42 | let name: String
43 | let date: TypedDate<(Year, Month, Day)>
44 | }
45 | let typedDate = TypedDate(Year(2023), Month(11), Day(10))
46 | let model = Model(id: 1, name: "John", date: typedDate)
47 |
48 | let data = try JSONEncoder().encode(model)
49 | let decodedModel = try JSONDecoder().decode(Model.self, from: data)
50 | #expect(model == decodedModel)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateComparableTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | @Suite
6 | struct TypedDateComparableTests {
7 | let calendar: Calendar
8 | let testSupport: TypedDateTestSupport
9 |
10 | init() {
11 | calendar = Calendar(identifier: .gregorian)
12 | testSupport = TypedDateTestSupport.init(calendar: calendar)
13 | }
14 |
15 | @Test
16 | func compareYear() {
17 | let lhsDate = TypedDate(testSupport.year, calendar: calendar)
18 | let rhsDate = TypedDate(testSupport.year.add(1), calendar: calendar)
19 |
20 | #expect(lhsDate < rhsDate)
21 | }
22 |
23 | @Test
24 | func compareMonth() {
25 | let lhsDate = TypedDate(testSupport.year, testSupport.month, calendar: calendar)
26 | let rhsDate = TypedDate(testSupport.year, testSupport.month.add(1), calendar: calendar)
27 |
28 | #expect(lhsDate < rhsDate)
29 | }
30 |
31 | @Test
32 | func compareDay() {
33 | let lhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, calendar: calendar)
34 | let rhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day.add(1), calendar: calendar)
35 |
36 | #expect(lhsDate < rhsDate)
37 | }
38 |
39 | @Test
40 | func compareHour() {
41 | let lhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, calendar: calendar)
42 | let rhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour.add(1), calendar: calendar)
43 |
44 | #expect(lhsDate < rhsDate)
45 | }
46 |
47 | @Test
48 | func compareMinute() {
49 | let lhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, calendar: calendar)
50 | let rhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, testSupport.minute.add(1), calendar: calendar)
51 |
52 | #expect(lhsDate < rhsDate)
53 | }
54 |
55 | @Test
56 | func compareSecond() {
57 | let lhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, testSupport.second, calendar: calendar)
58 | let rhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, testSupport.second.add(1), calendar: calendar)
59 |
60 | #expect(lhsDate < rhsDate)
61 | }
62 |
63 | @Test
64 | func compareNanosecond() {
65 | let lhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, testSupport.second, testSupport.nanosecond, calendar: calendar)
66 | let rhsDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, testSupport.second, testSupport.nanosecond.add(-10000), calendar: calendar)
67 |
68 | #expect(lhsDate > rhsDate)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateComponentsTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | @Suite
6 | struct TypedDateComponentsTests {
7 | let calendar: Calendar
8 | let testSupport: TypedDateTestSupport
9 |
10 | init() {
11 | calendar = Calendar(identifier: .gregorian)
12 | testSupport = TypedDateTestSupport(calendar: calendar)
13 | }
14 |
15 | @Test
16 | func year() {
17 | let typedDate = testSupport.generateTypedYearDate()
18 | #expect(typedDate.year(calendar: calendar) == testSupport.year.value)
19 | }
20 |
21 | @Test
22 | func month() {
23 | let typedDate = testSupport.generateTypedMonthDate()
24 | #expect(typedDate.year(calendar: calendar) == testSupport.year.value)
25 | #expect(typedDate.month(calendar: calendar) == testSupport.month.value)
26 | }
27 |
28 | @Test
29 | func day() {
30 | let typedDate = testSupport.generateTypedDayDate()
31 | #expect(typedDate.year(calendar: calendar) == testSupport.year.value)
32 | #expect(typedDate.month(calendar: calendar) == testSupport.month.value)
33 | #expect(typedDate.day(calendar: calendar) == testSupport.day.value)
34 | }
35 |
36 | @Test
37 | func hour() {
38 | let typedDate = testSupport.generateTypedHourDate()
39 | #expect(typedDate.year(calendar: calendar) == testSupport.year.value)
40 | #expect(typedDate.month(calendar: calendar) == testSupport.month.value)
41 | #expect(typedDate.day(calendar: calendar) == testSupport.day.value)
42 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.value)
43 | }
44 |
45 | @Test
46 | func minute() {
47 | let typedDate = testSupport.generateTypedMinuteDate()
48 | #expect(typedDate.year(calendar: calendar) == testSupport.year.value)
49 | #expect(typedDate.month(calendar: calendar) == testSupport.month.value)
50 | #expect(typedDate.day(calendar: calendar) == testSupport.day.value)
51 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.value)
52 | #expect(typedDate.minute(calendar: calendar) == testSupport.minute.value)
53 | }
54 |
55 | @Test
56 | func second() {
57 | let typedDate = testSupport.generateTypedSecondDate()
58 | #expect(typedDate.year(calendar: calendar) == testSupport.year.value)
59 | #expect(typedDate.month(calendar: calendar) == testSupport.month.value)
60 | #expect(typedDate.day(calendar: calendar) == testSupport.day.value)
61 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.value)
62 | #expect(typedDate.minute(calendar: calendar) == testSupport.minute.value)
63 | #expect(typedDate.second(calendar: calendar) == testSupport.second.value)
64 | }
65 |
66 | @Test
67 | func nanosecond() {
68 | let typedDate = testSupport.generateTypedNanosecondDate()
69 | #expect(typedDate.year(calendar: calendar) == testSupport.year.value)
70 | #expect(typedDate.month(calendar: calendar) == testSupport.month.value)
71 | #expect(typedDate.day(calendar: calendar) == testSupport.day.value)
72 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.value)
73 | #expect(typedDate.minute(calendar: calendar) == testSupport.minute.value)
74 | #expect(typedDate.second(calendar: calendar) == testSupport.second.value)
75 | #expect(typedDate.nanosecond(calendar: calendar) == testSupport.nanosecond.value)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateEquatableTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | @Suite
6 | struct TypedDateEquatableTests {
7 | let calendar: Calendar
8 | let testSupport: TypedDateTestSupport
9 |
10 | init() {
11 | calendar = Calendar(identifier: .gregorian)
12 | testSupport = TypedDateTestSupport.init(calendar: calendar)
13 | }
14 |
15 | @Test
16 | func equalYear() {
17 | let lhsDate = testSupport.generateTypedYearDate()
18 | let rhsDate = testSupport.generateTypedYearDate()
19 |
20 | #expect(lhsDate == rhsDate)
21 | }
22 |
23 | @Test
24 | func equalMonth() {
25 | let lhsDate = testSupport.generateTypedMonthDate()
26 | let rhsDate = testSupport.generateTypedMonthDate()
27 |
28 | #expect(lhsDate == rhsDate)
29 | }
30 |
31 | @Test
32 | func equalDay() {
33 | let lhsDate = testSupport.generateTypedDayDate()
34 | let rhsDate = testSupport.generateTypedDayDate()
35 |
36 | #expect(lhsDate == rhsDate)
37 | }
38 |
39 | @Test
40 | func equalHour() {
41 | let lhsDate = testSupport.generateTypedHourDate()
42 | let rhsDate = testSupport.generateTypedHourDate()
43 |
44 | #expect(lhsDate == rhsDate)
45 | }
46 |
47 | @Test
48 | func equalMinute() {
49 | let lhsDate = testSupport.generateTypedMinuteDate()
50 | let rhsDate = testSupport.generateTypedMinuteDate()
51 |
52 | #expect(lhsDate == rhsDate)
53 | }
54 |
55 | @Test
56 | func equalSecond() {
57 | let lhsDate = testSupport.generateTypedSecondDate()
58 | let rhsDate = testSupport.generateTypedSecondDate()
59 |
60 | #expect(lhsDate == rhsDate)
61 | }
62 |
63 | @Test
64 | func equalNanosecond() {
65 | let lhsDate = testSupport.generateTypedNanosecondDate()
66 | let rhsDate = testSupport.generateTypedNanosecondDate()
67 |
68 | #expect(lhsDate == rhsDate)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateEraseTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | @Suite
6 | struct TypedDateEraseTests {
7 | let calendar: Calendar
8 | let testSupport: TypedDateTestSupport
9 |
10 | init() {
11 | calendar = Calendar(identifier: .gregorian)
12 | testSupport = TypedDateTestSupport(calendar: calendar)
13 | }
14 |
15 | @Test
16 | func testMonthErasedDate() {
17 | let typedDate = testSupport.generateTypedMonthDate()
18 | testSupport.assertTypedDate(for: typedDate)
19 | testSupport.assertTypedDate(for: typedDate.erase(to: \.year, calendar: calendar))
20 | }
21 |
22 | @Test
23 | func testDayErasedDate() {
24 | let typedDate = testSupport.generateTypedDayDate()
25 | testSupport.assertTypedDate(for: typedDate)
26 | testSupport.assertTypedDate(for: typedDate.erase(to: \.year, calendar: calendar))
27 | testSupport.assertTypedDate(for: typedDate.erase(to: \.month, calendar: calendar))
28 | }
29 |
30 | @Test
31 | func testHourErasedDate() {
32 | let typedDate = testSupport.generateTypedHourDate()
33 | testSupport.assertTypedDate(for: typedDate)
34 | testSupport.assertTypedDate(for: typedDate.erase(to: \.year, calendar: calendar))
35 | testSupport.assertTypedDate(for: typedDate.erase(to: \.month, calendar: calendar))
36 | testSupport.assertTypedDate(for: typedDate.erase(to: \.day, calendar: calendar))
37 | }
38 |
39 | @Test
40 | func testMinuteErasedDate() {
41 | let typedDate = testSupport.generateTypedMinuteDate()
42 | testSupport.assertTypedDate(for: typedDate)
43 | testSupport.assertTypedDate(for: typedDate.erase(to: \.year, calendar: calendar))
44 | testSupport.assertTypedDate(for: typedDate.erase(to: \.month, calendar: calendar))
45 | testSupport.assertTypedDate(for: typedDate.erase(to: \.day, calendar: calendar))
46 | testSupport.assertTypedDate(for: typedDate.erase(to: \.hour, calendar: calendar))
47 | }
48 |
49 | @Test
50 | func testSecondErasedDate() {
51 | let typedDate = testSupport.generateTypedNanosecondDate()
52 | testSupport.assertTypedDate(for: typedDate)
53 | testSupport.assertTypedDate(for: typedDate.erase(to: \.year, calendar: calendar))
54 | testSupport.assertTypedDate(for: typedDate.erase(to: \.month, calendar: calendar))
55 | testSupport.assertTypedDate(for: typedDate.erase(to: \.day, calendar: calendar))
56 | testSupport.assertTypedDate(for: typedDate.erase(to: \.hour, calendar: calendar))
57 | testSupport.assertTypedDate(for: typedDate.erase(to: \.minute, calendar: calendar))
58 | }
59 |
60 | @Test
61 | func testNanoSecondErasedDate() {
62 | let typedDate = testSupport.generateTypedNanosecondDate()
63 | testSupport.assertTypedDate(for: typedDate)
64 | testSupport.assertTypedDate(for: typedDate.erase(to: \.year, calendar: calendar))
65 | testSupport.assertTypedDate(for: typedDate.erase(to: \.month, calendar: calendar))
66 | testSupport.assertTypedDate(for: typedDate.erase(to: \.day, calendar: calendar))
67 | testSupport.assertTypedDate(for: typedDate.erase(to: \.hour, calendar: calendar))
68 | testSupport.assertTypedDate(for: typedDate.erase(to: \.minute, calendar: calendar))
69 | testSupport.assertTypedDate(for: typedDate.erase(to: \.second, calendar: calendar))
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateFillTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | @Suite
6 | struct TypedDateFillTests {
7 | let calendar: Calendar
8 | let testSupport: TypedDateTestSupport
9 |
10 | init() {
11 | self.calendar = Calendar(identifier: .gregorian)
12 | self.testSupport = TypedDateTestSupport(calendar: calendar)
13 | }
14 |
15 | @Test
16 | func testFillInYearDate() {
17 | let typedDate = testSupport.generateTypedYearDate()
18 | testSupport.assertTypedDate(
19 | for: typedDate.fill(
20 | to: \.month,
21 | arguments: (testSupport.month)
22 | )
23 | )
24 | testSupport.assertTypedDate(
25 | for: typedDate.fill(
26 | to: \.day,
27 | arguments: (testSupport.month, testSupport.day)
28 | )
29 | )
30 | testSupport.assertTypedDate(
31 | for: typedDate.fill(
32 | to: \.hour,
33 | arguments: (testSupport.month, testSupport.day, testSupport.hour)
34 | )
35 | )
36 | testSupport.assertTypedDate(
37 | for: typedDate.fill(
38 | to: \.minute,
39 | arguments: (testSupport.month, testSupport.day, testSupport.hour, testSupport.minute)
40 | )
41 | )
42 | testSupport.assertTypedDate(
43 | for: typedDate.fill(
44 | to: \.second,
45 | arguments: (testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, testSupport.second)
46 | )
47 | )
48 | testSupport.assertTypedDate(
49 | for: typedDate.fill(
50 | to: \.nanosecond,
51 | arguments: (testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, testSupport.second, testSupport.nanosecond)
52 | )
53 | )
54 | }
55 |
56 | @Test
57 | func testFillInMonthDate() {
58 | let typedDate = testSupport.generateTypedMonthDate()
59 | testSupport.assertTypedDate(
60 | for: typedDate.fill(
61 | to: \.day,
62 | arguments: (testSupport.day)
63 | )
64 | )
65 | testSupport.assertTypedDate(
66 | for: typedDate.fill(
67 | to: \.hour,
68 | arguments: (testSupport.day, testSupport.hour)
69 | )
70 | )
71 | testSupport.assertTypedDate(
72 | for: typedDate.fill(
73 | to: \.minute,
74 | arguments: (testSupport.day, testSupport.hour, testSupport.minute)
75 | )
76 | )
77 | testSupport.assertTypedDate(
78 | for: typedDate.fill(
79 | to: \.second,
80 | arguments: (testSupport.day, testSupport.hour, testSupport.minute, testSupport.second)
81 | )
82 | )
83 | testSupport.assertTypedDate(
84 | for: typedDate.fill(
85 | to: \.nanosecond,
86 | arguments: (testSupport.day, testSupport.hour, testSupport.minute, testSupport.second, testSupport.nanosecond)
87 | )
88 | )
89 | }
90 |
91 | @Test
92 | func testFillInDayDate() {
93 | let typedDate = testSupport.generateTypedDayDate()
94 | testSupport.assertTypedDate(
95 | for: typedDate.fill(
96 | to: \.hour,
97 | arguments: ( testSupport.hour)
98 | )
99 | )
100 | testSupport.assertTypedDate(
101 | for: typedDate.fill(
102 | to: \.minute,
103 | arguments: (testSupport.hour, testSupport.minute)
104 | )
105 | )
106 | testSupport.assertTypedDate(
107 | for: typedDate.fill(
108 | to: \.second,
109 | arguments: (testSupport.hour, testSupport.minute, testSupport.second)
110 | )
111 | )
112 | testSupport.assertTypedDate(
113 | for: typedDate.fill(
114 | to: \.nanosecond,
115 | arguments: (testSupport.hour, testSupport.minute, testSupport.second, testSupport.nanosecond)
116 | )
117 | )
118 | }
119 |
120 | @Test
121 | func testFillInHourDate() {
122 | let typedDate = testSupport.generateTypedHourDate()
123 | testSupport.assertTypedDate(
124 | for: typedDate.fill(
125 | to: \.minute,
126 | arguments: (testSupport.minute)
127 | )
128 | )
129 | testSupport.assertTypedDate(
130 | for: typedDate.fill(
131 | to: \.second,
132 | arguments: (testSupport.minute, testSupport.second)
133 | )
134 | )
135 | testSupport.assertTypedDate(
136 | for: typedDate.fill(
137 | to: \.nanosecond,
138 | arguments: (testSupport.minute, testSupport.second, testSupport.nanosecond)
139 | )
140 | )
141 | }
142 |
143 | @Test
144 | func testFillInMinuteDate() {
145 | let typedDate = testSupport.generateTypedMinuteDate()
146 | testSupport.assertTypedDate(
147 | for: typedDate.fill(
148 | to: \.second,
149 | arguments: (testSupport.second)
150 | )
151 | )
152 | testSupport.assertTypedDate(
153 | for: typedDate.fill(
154 | to: \.nanosecond,
155 | arguments: (testSupport.second, testSupport.nanosecond)
156 | )
157 | )
158 | }
159 |
160 | @Test
161 | func testFillInSecondDate() {
162 | let typedDate = testSupport.generateTypedSecondDate()
163 | testSupport.assertTypedDate(
164 | for: typedDate.fill(
165 | to: \.nanosecond,
166 | arguments: (testSupport.nanosecond)
167 | )
168 | )
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateInitTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | @Suite
6 | struct TypedDateInitTests {
7 | let calendar: Calendar
8 | let testSupport: TypedDateTestSupport
9 |
10 | init() {
11 | self.calendar = Calendar(identifier: .gregorian)
12 | self.testSupport = TypedDateTestSupport(calendar: calendar)
13 | }
14 |
15 | @Test
16 | func year() {
17 | let typedDate = testSupport.generateTypedYearDate()
18 | testSupport.assertTypedDate(for: typedDate)
19 | }
20 |
21 | @Test
22 | func month() {
23 | let typedDate = testSupport.generateTypedMonthDate()
24 | testSupport.assertTypedDate(for: typedDate)
25 | }
26 |
27 | @Test
28 | func day() {
29 | let typedDate = testSupport.generateTypedDayDate()
30 | testSupport.assertTypedDate(for: typedDate)
31 | }
32 |
33 | @Test
34 | func hour() {
35 | let typedDate = testSupport.generateTypedHourDate()
36 | testSupport.assertTypedDate(for: typedDate)
37 | }
38 |
39 | @Test
40 | func minute() {
41 | let typedDate = testSupport.generateTypedMinuteDate()
42 | testSupport.assertTypedDate(for: typedDate)
43 | }
44 |
45 | @Test
46 | func second() {
47 | let typedDate = testSupport.generateTypedSecondDate()
48 | testSupport.assertTypedDate(for: typedDate)
49 | }
50 |
51 | @Test
52 | func nanosecond() {
53 | let typedDate = testSupport.generateTypedNanosecondDate()
54 | testSupport.assertTypedDate(for: typedDate)
55 | }
56 |
57 | @Test
58 | func fractionalSecond() {
59 | let typedDate = TypedDate(testSupport.year, testSupport.month, testSupport.day, testSupport.hour, testSupport.minute, testSupport.fractionalSecond, calendar: calendar)
60 | testSupport.assertTypedDate(for: typedDate)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateModifyTests.swift:
--------------------------------------------------------------------------------
1 | import Testing
2 | @testable import TypedDate
3 | import TypedDateCore
4 | import Foundation
5 |
6 | @Suite
7 | struct TypedDateModifyTests {
8 | let testSupport: TypedDateTestSupport
9 | let calendar: Calendar
10 |
11 | init() {
12 | calendar = Calendar(identifier: .gregorian)
13 | testSupport = TypedDateTestSupport(calendar: calendar)
14 | }
15 |
16 | @Test
17 | func testNanosecondModify() {
18 | let typedDate = testSupport.generateTypedNanosecondDate()
19 | .modifying(\.year) { $0 += 1 }
20 | .modifying(\.month) { $0 -= 2 }
21 | .modifying(\.day) { $0 += 3 }
22 | .modifying(\.hour) { $0 += 4 }
23 | .modifying(\.minute) { $0 += 5 }
24 | .modifying(\.second) { $0 += 6 }
25 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
26 | #expect(typedDate.month(calendar: calendar) == testSupport.month.add(-2))
27 | #expect(typedDate.day(calendar: calendar) == testSupport.day.add(3))
28 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.add(4))
29 | #expect(typedDate.minute(calendar: calendar) == testSupport.minute.add(5))
30 | #expect(typedDate.second(calendar: calendar) == testSupport.second.add(6))
31 | #expect(typedDate.nanosecond(calendar: calendar) == testSupport.nanosecond)
32 | }
33 |
34 | @Test
35 | func testSecondModify() {
36 | let typedDate = testSupport.generateTypedSecondDate()
37 | .modifying(\.year) { $0 += 1 }
38 | .modifying(\.month) { $0 -= 2 }
39 | .modifying(\.day) { $0 += 3 }
40 | .modifying(\.hour) { $0 += 4 }
41 | .modifying(\.minute) { $0 += 5 }
42 | .modifying(\.second) { $0 += 6 }
43 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
44 | #expect(typedDate.month(calendar: calendar) == testSupport.month.add(-2))
45 | #expect(typedDate.day(calendar: calendar) == testSupport.day.add(3))
46 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.add(4))
47 | #expect(typedDate.minute(calendar: calendar) == testSupport.minute.add(5))
48 | #expect(typedDate.second(calendar: calendar) == testSupport.second.add(6))
49 | }
50 |
51 | @Test
52 | func testMinuteModify() {
53 | let typedDate = testSupport.generateTypedMinuteDate()
54 | .modifying(\.year) { $0 += 1 }
55 | .modifying(\.month) { $0 -= 2 }
56 | .modifying(\.day) { $0 += 3 }
57 | .modifying(\.hour) { $0 += 4 }
58 | .modifying(\.minute) { $0 += 5 }
59 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
60 | #expect(typedDate.month(calendar: calendar) == testSupport.month.add(-2))
61 | #expect(typedDate.day(calendar: calendar) == testSupport.day.add(3))
62 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.add(4))
63 | #expect(typedDate.minute(calendar: calendar) == testSupport.minute.add(5))
64 | }
65 |
66 | @Test
67 | func testHourModify() {
68 | let typedDate = testSupport.generateTypedHourDate()
69 | .modifying(\.year) { $0 += 1 }
70 | .modifying(\.month) { $0 -= 2 }
71 | .modifying(\.day) { $0 += 3 }
72 | .modifying(\.hour) { $0 += 4 }
73 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
74 | #expect(typedDate.month(calendar: calendar) == testSupport.month.add(-2))
75 | #expect(typedDate.day(calendar: calendar) == testSupport.day.add(3))
76 | #expect(typedDate.hour(calendar: calendar) == testSupport.hour.add(4))
77 | }
78 |
79 | @Test
80 | func testDayModify() {
81 | let typedDate = testSupport.generateTypedDayDate()
82 | .modifying(\.year) { $0 += 1 }
83 | .modifying(\.month) { $0 -= 2 }
84 | .modifying(\.day) { $0 += 3 }
85 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
86 | #expect(typedDate.month(calendar: calendar) == testSupport.month.add(-2))
87 | #expect(typedDate.day(calendar: calendar) == testSupport.day.add(3))
88 | }
89 |
90 | @Test
91 | func testMonthModify() {
92 | let typedDate = testSupport.generateTypedMonthDate()
93 | .modifying(\.year) { $0 += 1 }
94 | .modifying(\.month) { $0 -= 2 }
95 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
96 | #expect(typedDate.month(calendar: calendar) == testSupport.month.add(-2))
97 | }
98 |
99 | @Test
100 | func testYearModify() {
101 | let typedDate = testSupport.generateTypedYearDate()
102 | .modifying(\.year) { $0 += 1 }
103 | #expect(typedDate.year(calendar: calendar) == testSupport.year.add(1))
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateScopeTests.swift:
--------------------------------------------------------------------------------
1 | import Testing
2 | @testable import TypedDate
3 | import TypedDateCore
4 | import Foundation
5 |
6 | @Suite
7 | struct TypedDateScopeTests {
8 | let calendar: Calendar
9 | let testSupport: TypedDateTestSupport
10 |
11 | init() {
12 | calendar = Calendar(identifier: .gregorian)
13 | testSupport = TypedDateTestSupport(calendar: calendar)
14 | }
15 |
16 | @Test
17 | func testScope() throws {
18 | let generatedDate = testSupport.generateDate()
19 | let date = try #require(generatedDate)
20 | testSupport.assertTypedDate(for: date.scope(to: \.year, calendar: calendar))
21 | testSupport.assertTypedDate(for: date.scope(to: \.month, calendar: calendar))
22 | testSupport.assertTypedDate(for: date.scope(to: \.day, calendar: calendar))
23 | testSupport.assertTypedDate(for: date.scope(to: \.hour, calendar: calendar))
24 | testSupport.assertTypedDate(for: date.scope(to: \.minute, calendar: calendar))
25 | testSupport.assertTypedDate(for: date.scope(to: \.second, calendar: calendar))
26 | testSupport.assertTypedDate(for: date.scope(to: \.nanosecond, calendar: calendar))
27 | }
28 |
29 | @Test(arguments: Calendar.Identifier.allCases)
30 | func testScopeNotNil(identifier: Calendar.Identifier) {
31 | let calendar = Calendar(identifier: identifier)
32 | testSupport.dates(calendar: calendar).compactMap { $0 }.forEach {
33 | let nanosecond = $0.scope(to: \.nanosecond, calendar: calendar)
34 | #expect(nanosecond != nil)
35 | let second = $0.scope(to: \.second, calendar: calendar)
36 | #expect(second != nil)
37 | let minute = $0.scope(to: \.minute, calendar: calendar)
38 | #expect(minute != nil)
39 | let hour = $0.scope(to: \.hour, calendar: calendar)
40 | #expect(hour != nil)
41 | let day = $0.scope(to: \.day, calendar: calendar)
42 | #expect(day != nil)
43 | let month = $0.scope(to: \.month, calendar: calendar)
44 | #expect(month != nil)
45 | let year = $0.scope(to: \.year, calendar: calendar)
46 | #expect(year != nil)
47 | }
48 | }
49 |
50 | @Test(arguments: Calendar.Identifier.allCases)
51 | func testDateNotNil(identifier: Calendar.Identifier) {
52 | testSupport.dates(calendar: Calendar(identifier: identifier)).forEach { date in
53 | #expect(date != nil)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Tests/TypedDateTests/TypedDateTestSupport.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Testing
3 | @testable import TypedDate
4 |
5 | struct TypedDateTestSupport {
6 | let year = Year(2023)
7 | let month = Month(11)
8 | let day = Day(10)
9 | let hour = Hour(3)
10 | let minute = Minute(50)
11 | let second = Second(10)
12 | let nanosecond = Nanosecond(111111164)
13 | let fractionalSecond = FractionalSecond(10.111111164)
14 |
15 | let calendar: Calendar
16 |
17 | init(calendar: Calendar) {
18 | self.calendar = calendar
19 | }
20 |
21 | func assertTypedDate(for typedDate: TypedDate<(Year)>) {
22 | #expect(typedDate.year(calendar: calendar) == year.value)
23 | #expect(calendar.dateComponents([.month], from: typedDate.date).month == 1)
24 | #expect(calendar.dateComponents([.day], from: typedDate.date).day == 1)
25 | #expect(calendar.dateComponents([.hour], from: typedDate.date).hour == 0)
26 | #expect(calendar.dateComponents([.minute], from: typedDate.date).minute == 0)
27 | #expect(calendar.dateComponents([.second], from: typedDate.date).second == 0)
28 | }
29 |
30 | func assertTypedDate(for typedDate: TypedDate<(Year, Month)>) {
31 | #expect(typedDate.year(calendar: calendar) == year.value)
32 | #expect(typedDate.month(calendar: calendar) == month.value)
33 | #expect(calendar.dateComponents([.day], from: typedDate.date).day == 1)
34 | #expect(calendar.dateComponents([.hour], from: typedDate.date).hour == 0)
35 | #expect(calendar.dateComponents([.minute], from: typedDate.date).minute == 0)
36 | #expect(calendar.dateComponents([.second], from: typedDate.date).second == 0)
37 | }
38 |
39 | func assertTypedDate(for typedDate: TypedDate<(Year, Month, Day)>) {
40 | #expect(typedDate.year(calendar: calendar) == year.value)
41 | #expect(typedDate.month(calendar: calendar) == month.value)
42 | #expect(typedDate.day(calendar: calendar) == day.value)
43 | #expect(calendar.dateComponents([.hour], from: typedDate.date).hour == 0)
44 | #expect(calendar.dateComponents([.minute], from: typedDate.date).minute == 0)
45 | #expect(calendar.dateComponents([.second], from: typedDate.date).second == 0)
46 | }
47 |
48 | func assertTypedDate(for typedDate: TypedDate<(Year, Month, Day, Hour)>) {
49 | #expect(typedDate.year(calendar: calendar) == year.value)
50 | #expect(typedDate.month(calendar: calendar) == month.value)
51 | #expect(typedDate.day(calendar: calendar) == day.value)
52 | #expect(typedDate.hour(calendar: calendar) == hour.value)
53 | #expect(calendar.dateComponents([.minute], from: typedDate.date).minute == 0)
54 | #expect(calendar.dateComponents([.second], from: typedDate.date).second == 0)
55 | }
56 |
57 | func assertTypedDate(for typedDate: TypedDate<(Year, Month, Day, Hour, Minute)>) {
58 | #expect(typedDate.year(calendar: calendar) == year.value)
59 | #expect(typedDate.month(calendar: calendar) == month.value)
60 | #expect(typedDate.day(calendar: calendar) == day.value)
61 | #expect(typedDate.hour(calendar: calendar) == hour.value)
62 | #expect(typedDate.minute(calendar: calendar) == minute.value)
63 | #expect(calendar.dateComponents([.second], from: typedDate.date).second == 0)
64 | #expect(calendar.dateComponents([.nanosecond], from: typedDate.date).nanosecond == 0)
65 | }
66 |
67 | func assertTypedDate(for typedDate: TypedDate<(Year, Month, Day, Hour, Minute, Second)>) {
68 | #expect(typedDate.year(calendar: calendar) == year.value)
69 | #expect(typedDate.month(calendar: calendar) == month.value)
70 | #expect(typedDate.day(calendar: calendar) == day.value)
71 | #expect(typedDate.hour(calendar: calendar) == hour.value)
72 | #expect(typedDate.minute(calendar: calendar) == minute.value)
73 | #expect(typedDate.second(calendar: calendar) == second.value)
74 | #expect(calendar.dateComponents([.nanosecond], from: typedDate.date).nanosecond == 0)
75 | }
76 |
77 | func assertTypedDate(for typedDate: TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)>) {
78 | #expect(typedDate.year(calendar: calendar) == year.value)
79 | #expect(typedDate.month(calendar: calendar) == month.value)
80 | #expect(typedDate.day(calendar: calendar) == day.value)
81 | #expect(typedDate.hour(calendar: calendar) == hour.value)
82 | #expect(typedDate.minute(calendar: calendar) == minute.value)
83 | #expect(typedDate.second(calendar: calendar) == second.value)
84 | #expect(typedDate.nanosecond(calendar: calendar) == nanosecond.value)
85 | }
86 |
87 | func dates(calendar: Calendar) -> [Date?] {
88 | [
89 | generateDate(calendar: calendar),
90 | generateDate(calendar: calendar, year: 2023), generateDate(calendar: calendar, month: 1), generateDate(calendar: calendar, day: 1),
91 | generateDate(calendar: calendar, minute: 1), generateDate(calendar: calendar, second: 1), generateDate(calendar: calendar, nanosecond: 1),
92 | generateDate(calendar: calendar, year: 2023, month: 1, day: 31),
93 | generateDate(calendar: calendar, year: 2023, month: 2, day: 28),
94 | generateDate(calendar: calendar, year: 2024, month: 2, day: 29),
95 | generateDate(calendar: calendar, year: 2023, month: 2, day: 30),
96 | generateDate(calendar: calendar, year: 2023, month: 13, day: 1),
97 | generateDate(calendar: calendar, hour: 23, minute: 59, second: 59),
98 | generateDate(calendar: calendar, year: 2023, month: 12, day: 31, hour: 23, minute: 59, second: 59),
99 | generateDate(calendar: calendar, year: 3000, month: 1, day: 1),
100 | generateDate(calendar: calendar, year: 1900, month: 1, day: 1),
101 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 25, minute: 0, second: 0),
102 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 12, minute: 60, second: 0),
103 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 12, minute: 0, second: 61),
104 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 12, minute: 0, second: 0, nanosecond: 500000000),
105 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 12, minute: 0, second: 0, nanosecond: 1_000_000_000),
106 | generateDate(calendar: calendar, year: -1, month: 1, day: 1),
107 | generateDate(calendar: calendar, year: 2023, month: -1, day: 1),
108 | generateDate(calendar: calendar, year: 2023, month: 1, day: -1),
109 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 0, minute: -1, second: 0),
110 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 0, minute: 0, second: -1),
111 | generateDate(calendar: calendar, year: 2023, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: -1)
112 | ]
113 | }
114 |
115 | func generateTypedNanosecondDate() -> TypedDate<(Year, Month, Day, Hour, Minute, Second, Nanosecond)> {
116 | TypedDate(year, month, day, hour, minute, second, nanosecond, calendar: calendar)
117 | }
118 |
119 | func generateTypedSecondDate() -> TypedDate<(Year, Month, Day, Hour, Minute, Second)> {
120 | TypedDate(year, month, day, hour, minute, second, calendar: calendar)
121 | }
122 |
123 | func generateTypedMinuteDate() -> TypedDate<(Year, Month, Day, Hour, Minute)> {
124 | TypedDate(year, month, day, hour, minute, calendar: calendar)
125 | }
126 |
127 | func generateTypedHourDate() -> TypedDate<(Year, Month, Day, Hour)> {
128 | TypedDate(year, month, day, hour, calendar: calendar)
129 | }
130 |
131 | func generateTypedDayDate() -> TypedDate<(Year, Month, Day)> {
132 | TypedDate(year, month, day, calendar: calendar)
133 | }
134 |
135 | func generateTypedMonthDate() -> TypedDate<(Year, Month)> {
136 | TypedDate(year, month, calendar: calendar)
137 | }
138 |
139 | func generateTypedYearDate() -> TypedDate<(Year)> {
140 | TypedDate(year, calendar: calendar)
141 | }
142 |
143 | func generateDate() -> Date? {
144 | generateDate(
145 | calendar: calendar,
146 | year: year.value,
147 | month: month.value,
148 | day: day.value,
149 | hour: hour.value,
150 | minute: minute.value,
151 | second: second.value,
152 | nanosecond: nanosecond.value
153 | )
154 | }
155 |
156 | func generateDate(
157 | calendar: Calendar,
158 | year: Int? = nil,
159 | month: Int? = nil,
160 | day: Int? = nil,
161 | hour: Int? = nil,
162 | minute: Int? = nil,
163 | second: Int? = nil,
164 | nanosecond: Int? = nil
165 | ) -> Date? {
166 | DateComponents(
167 | calendar: .current,
168 | year: year,
169 | month: month,
170 | day: day,
171 | hour: hour,
172 | minute: minute,
173 | second: second,
174 | nanosecond: nanosecond
175 | ).date
176 | }
177 | }
178 |
179 | extension Calendar.Identifier {
180 | static var allCases: Set {
181 | [
182 | .buddhist, .chinese, .coptic, .ethiopicAmeteAlem, .ethiopicAmeteMihret, .gregorian, .hebrew, .indian,
183 | .islamic, .islamicCivil, .islamicTabular, .islamicUmmAlQura, .iso8601, .japanese, .persian, .republicOfChina
184 | ]
185 | }
186 | }
187 |
--------------------------------------------------------------------------------