├── .gitignore
├── .swift-version
├── .swiftlint.yml
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── DateHelper
│ └── DateHelper.swift
├── Tests
└── DateHelperTests
│ ├── DateAdjustingTests.swift
│ ├── DateComparisonTests.swift
│ ├── DateFromStringTests.swift
│ ├── MiscelaniousTests.swift
│ ├── StringFromDateTests.swift
│ └── TimeSinceTests.swift
├── logo.png
└── logo.sketch
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .build/
3 | .swiftpm/
4 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules: # rule identifiers to exclude from running
2 | - statement_position
3 | - valid_ibinspectable
4 | - fallthrough
5 | - explicit_enum_raw_value
6 | - nesting
7 | - closure_parameter_position
8 | opt_in_rules:
9 | - private_action
10 | - private_outlet
11 | - redundant_nil_coalescing
12 | - attributes
13 | - closure_end_indentation
14 | - closure_spacing
15 | - empty_count
16 | - explicit_enum_raw_value
17 | - force_unwrapping
18 | - implicit_return
19 | - literal_expression_end_indentation
20 | - unneeded_parentheses_in_closure_argument
21 | - explicit_init
22 | - redundant_type_annotation
23 | - strong_iboutlet
24 | - unused_import
25 |
26 | identifier_name:
27 | min_length: 1
28 |
29 | line_length: 180
30 |
31 | custom_rules:
32 | no_bylines:
33 | name: "Bylines" # rule name. optional.
34 | regex: "([Cc]reated by)" # matching pattern
35 | match_kinds: # SyntaxKinds to match. optional.
36 | - comment
37 | message: "No bylines allowed in headers" # violation message. optional.
38 | severity: warning # violation severity. optional.
39 |
40 | excluded: # paths to ignore during linting. Takes precedence over `included`.
41 | - Carthage
42 | - NewsTests
43 | - Pods
44 | - News/cpc-dependencies
45 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Melvin Rivera
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "DateHelper",
8 | platforms: [
9 | .iOS(.v11),
10 | .macOS(.v10_13),
11 | .tvOS(.v12),
12 | .watchOS(.v4)
13 | ],
14 | products: [
15 | .library(
16 | name: "DateHelper",
17 | targets: ["DateHelper"]),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
22 | .target(
23 | name: "DateHelper",
24 | dependencies: []),
25 | .testTarget(
26 | name: "DateHelperTests",
27 | dependencies: ["DateHelper"]),
28 | ],
29 | swiftLanguageVersions: [.v5]
30 | )
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DateHelper 5.0.1
2 |
3 | [](https://github.com/melvitax/DateHelper/blob/master/LICENSE)
4 | [](https://github.com/melvitax/DateHelper)
5 | [](https://github.com/Carthage/Carthage)
6 | [](https://swift.org/package-manager/)
7 |
8 | 
9 |
10 | A high performant Swift Date Extension for creating, comparing, or modifying dates.
11 |
12 |
13 | ## Capabilities
14 | - **Date from String**
15 | - Using date detection i.e. `"Tomorrow at 5:30 PM"`
16 | - With predefined format: i.e. `.isoDateTime`
17 | - With custom format: i.e. `"dd MMM yyyy HH:mm:ss"`
18 | - **String from Date**
19 | - With predefined format: i.e. `.rss`
20 | - With custom format: i.e. `""MMM d, yyyy""`
21 | - With combined date and time style: i.e. `.medium`
22 | - With individual date and time style: i.e. `.medium, .short`
23 | - **Modify Date**
24 | - Offset date component: i.e. `.offset(.second, value: 110)`
25 | - Adjust date component: i.e. `.adjust(hour: 12, minute: 0, second: 0)`
26 | - Adjust date to a predefined time: i.e. `.adjust(for: .startOfDay)`
27 | - **Compare Date**
28 | - Compare against relative date in predefined format: i.e. `.isToday, .isThisWeek`
29 | - Compare againnst target date: i.e. `firstDate.compare(.isSameMonth(as: secondDate))`
30 | - **Time Since**
31 | - Time since target date in component: i.e. `Date().since(secondDate, in: .second)`
32 | - **Extras**
33 | - Extract date and time components: i.e. `.hour, .minute, .day`
34 | - Conveniance methods: i.e. `numberOfDaysInMonth(), firstDayOfWeek(), .lastDayOfWeek()`
35 |
36 | ## Date From String
37 | Provides initializers to create a Date from a String
38 |
39 |
40 | ### Detect a Date from natural language in a String
41 |
42 | ```swift
43 | Date(detectFromString: "Tomorrow at 5:30 PM")
44 | ```
45 | Uses NSDataDetector to detect a date from natural language in a string. It works similar to how Apple Mail detects dates. This initializer is not as efficient as **fromString:format:** and should not be used in collections or lists.
46 |
47 |
48 | ### Date from a string with predefined format
49 |
50 | ```swift
51 | Date(fromString: "2009", format: .isoYear)
52 | Date(fromString: "2009-08", format: .isoYearMonth)
53 | Date(fromString: "2009-08-11", format: .isoDate)
54 | Date(fromString: "2009-08-11T06:00:00-07:00", format: .isoDateTime)
55 | Date(fromString: "2009-08-11T06:00:00.000-07:00", format: .isoDateTimeFull)
56 | Date(fromString: "/Date(1260123281843)/", format: .dotNet)
57 | Date(fromString: "Fri, 09 Sep 2011 15:26:08 +0200", format: .rss)
58 | Date(fromString: "09 Sep 2011 15:26:08 +0200", format: .altRSS)
59 | Date(fromString: "Wed, 01 03 2017 06:43:19 -0500", format: .httpHeader)
60 | ```
61 | Highly performant, cached and thread safe. Can optionally receive timeZone, locale or isLenient flag.
62 |
63 | ### Date from a string with custom format
64 |
65 | ```swift
66 | Date(fromString: "16 July 1972 6:12:00", format: .custom("dd MMM yyyy HH:mm:ss"))
67 | ```
68 | Highly performant, cached and thread safe. Can optionally receive timeZone, locale or isLenient flag.
69 |
70 | ## String From Date
71 | Provides three ways to convert a Date object to a String
72 |
73 |
74 | ### Convert Date to String using predefined format
75 |
76 | ```swift
77 | Date().toString(format: .isoYear)
78 | "2017"
79 | Date().toString(format: .isoYearMonth)
80 | "2017-03"
81 | Date().toString(format: .isoDate)
82 | "2017-03-01"
83 | Date().toString(format: .isoDateTime)
84 | "2017-03-01T06:43:19-05:00"
85 | Date().toString(format: .isoDateTimeFull)
86 | "2017-03-01T06:43:19.000-05:00"
87 | Date().toString(format: .dotNet)
88 | "/Date(-51488368599000.000000)/"
89 | Date().toString(format: .rss)
90 | "Wed, 1 Mar 2017 06:43:19 -0500"
91 | Date().toString(format: .altRSS)
92 | "1 Mar 2017 06:43:19 -0500"
93 | Date().toString(format: .httpHeader)
94 | "Wed, 01 03 2017 06:43:19 -0500"
95 | ```
96 | Highly performant, cached and thread safe. Can optionally receive timeZone and locale.
97 |
98 | ### Convert Date to String using custom format
99 |
100 | ```swift
101 | Date().toString(format: .custom("MMM d, yyyy"))
102 | "Mar 1, 2017"
103 | Date().toString(format: .custom("h:mm a"))
104 | "6:43 AM"
105 | Date().toString(format: .custom("MMM d"))
106 | "Wed Mar 1"
107 | ```
108 | Highly performant, cached and thread safe. Can optionally receive timeZone and locale.
109 |
110 | ### Convert Date to String using predefined combined date and time styles
111 | ```swift
112 | Date().toString(style: .short)
113 | "3/1/17, 6:43 AM"
114 | Date().toString(style: .medium)
115 | "Mar 1, 2017 at 6:43:19 AM"
116 | Date().toString(style: .long)
117 | "March 1, 2017 at 6:43:19 AM EST"
118 | Date().toString(style: .full)
119 | "Wednesday, March 1, 2017 at 6:43:19 AM Eastern Standard Time"
120 | Date().toString(style: .ordinalDay)
121 | "1st"
122 | Date().toString(style: .weekday)
123 | "Wednesday"
124 | Date().toString(style: .shortWeekday)
125 | "Wed"
126 | Date().toString(style: .veryShortWeekday)
127 | "W"
128 | Date().toString(style: .month)
129 | "April"
130 | Date().toString(style: .shortMonth)
131 | "Apr"
132 | Date().toString(style: .veryShortMonth)
133 | "A"
134 | ```
135 |
136 | ### Convert Date to String using predefined individual date and time styles
137 |
138 | ```swift
139 | Date().toString(dateStyle: .none, timeStyle: .short)
140 | "6:43 AM"
141 | Date().toString(dateStyle: .short, timeStyle: .none)
142 | "3/1/17"
143 | Date().toString(dateStyle: .short, timeStyle: .short)
144 | "3/1/17, 6:43 AM"
145 | Date().toString(dateStyle: .medium, timeStyle: .medium)
146 | "Mar 1, 2017 at 6:43:19 AM"
147 | Date().toString(dateStyle: .long, timeStyle: .long)
148 | "March 1, 2017 at 6:43:19 AM EST"
149 | Date().toString(dateStyle: .full, timeStyle: .full)
150 | "Wednesday, March 1, 2017 at 6:43:19 AM Eastern Standard Time"
151 | ```
152 |
153 | ## Modifying dates
154 | Provides functions for adjusting or shifting dates
155 |
156 | ### Offset components
157 |
158 | ```swift
159 | Date().offset(.second, value: 10)
160 | "18:14:41" -> "18:14:51"
161 | Date().offset(.minute, value: 10)
162 | "18:14:41" -> "18:24:41"
163 | Date().offset(.hour, value: 2)
164 | "18:14:41" -> "20:14:41"
165 | Date().offset(.day, value: 1)
166 | "2009-12-06" -> "2009-12-07"
167 | Date().offset(.weekday, value: 2)
168 | "2009-12-06" -> "2009-16-06"
169 | Date().offset(.weekdayOrdinal, value: 1)
170 | "2009-12-06" -> "2009-12-20"
171 | Date().offset(.week, value: -2)
172 | "2009-12-06" -> "2009-11-22"
173 | Date().offset(.month, value: 2)
174 | "2009-12-06" -> "2010-02-06"
175 | Date().offset(.year, value: -2)
176 | "2009-12-06" -> "2007-12-06"
177 | ```
178 |
179 | ### Adjust components
180 | Modifies date with the specified date component
181 |
182 | ```swift
183 | Date().adjust(hour: 1, minute: 10, second: 30, day: 15, month: 1)
184 | "2009-12-06 18:14:41" -> "2009-01-15 06:10:30"
185 | Date().adjust(minute: 59)
186 | "2009-12-06 18:14:41" -> "2009-12-06 18:59:30"
187 | ```
188 |
189 | ### Adjust date
190 | Modifies date with predefined times like endOfDay, startOfDay startOfWeek etc.
191 |
192 | ```swift
193 | Date().adjust(for: .startOfDay)
194 | "2009-12-06 18:14:41" -> "2009-12-06 00:00:00"
195 | Date().adjust(for: .endOfDay)
196 | "2009-12-06 18:14:41" -> "2009-12-06 23-59-59"
197 | Date().adjust(for: .startOfWeek)
198 | "2009-12-08 18:14:41" -> "2009-12-06 18:14:41"
199 | Date().adjust(for: .endOfWeek)
200 | "2009-12-06 18:14:41" -> "2009-12-12 18:14:41"
201 | Date().adjust(for: .startOfMonth)
202 | "2009-12-06 18:14:41" -> "2009-12-01 18:14:41"
203 | Date().adjust(for: .endOfMonth)
204 | "2009-12-06 18:14:41" -> "2009-12-31 18:14:41"
205 | Date().adjust(for: .tomorrow)
206 | "2009-12-06 18:14:41" -> "tomorrow at 18:14:41"
207 | Date().adjust(for: .yesterday)
208 | "2009-12-06 18:14:41" -> "yesterday at 18:14:41"
209 | Date().adjust(for: .nearestMinute(minute:30))
210 | "2009-12-07 18-14-00" -> "2009-12-07 18-00-00"
211 | "2009-12-07 18-40-00" -> "2009-12-07 18-30-00"
212 | "2009-12-07 18-50-00" -> "2009-12-07 19-00-00"
213 | Date().adjust(for: .nearestHour(hour:2))
214 | "2009-12-07 18-00-00" -> "2009-12-08 00-00-00"
215 | "2009-12-07 07-00-00" -> "2009-12-07 12-00-00"
216 | "2009-12-07 03-00-00" -> "2009-12-07 00-00-00"
217 | Date().adjust(for: .startOfYear)
218 | "2009-12-06 18:14:41" -> "2009-01-01 00-00-00"
219 | Date().adjust(for: .endOfYear)
220 | "2009-12-06 18:14:41" -> "2009-12-31 23-59-59"
221 | ```
222 |
223 | ## Compare Dates
224 | Compares dates using predefined times like today, tomorrow, this year, next year etc. Returns true if it matches.
225 |
226 | ### Compare against relative date
227 | ```swift
228 | Date().compare(.isToday)
229 | Date().compare(.isTomorrow)
230 | Date().compare(.isYesterday)
231 | Date().compare(.isThisWeek)
232 | Date().compare(.isNextWeek)
233 | Date().compare(.isLastWeek)
234 | Date().compare(.isThisYear)
235 | Date().compare(.isNextYear)
236 | Date().compare(.isLastYear)
237 | Date().compare(.isInTheFuture)
238 | Date().compare(.isInThePast)
239 | Date().compare(.isWeekend)
240 | "2021-12-15" != weekend
241 | "2021-12-18" == weekend
242 | ```
243 |
244 | ### Compare against another date
245 | ```swift
246 | firstDate.compare(.isSameDay(as: secondDate))
247 | "2022-01-08" != "2022-01-07"
248 | "2022-01-06" != "2022-01-07"
249 | "2022-01-07" == "2022-01-07"
250 | firstDate.compare(.isSameWeek(as: secondDate))
251 | "2022-01-14" != "2022-01-07"
252 | "2021-12-31" != "2022-01-07"
253 | "2022-01-07" == "2022-01-07"
254 | firstDate.compare(.isSameMonth(as: secondDate))
255 | "2022-02-07" != "2022-01-07"
256 | "2021-12-07" != "2022-01-07"
257 | "2022-01-07" == "2022-01-07"
258 | firstDate.compare(.isSameYear(as: secondDate))
259 | "2023-01-07" != "2022-01-07"
260 | "2021-01-07" != "2022-01-07"
261 | "2022-01-07" == "2022-01-07"
262 | firstDate.compare(.isEarlier(than: secondDate))
263 | "2022-01-07 19:26:53" != "2022-01-07 19:25:53"
264 | "2022-01-07 19:24:53" == "2022-01-07 19:25:53"
265 | firstDate.compare(.isLater(than: secondDate))
266 | "2022-01-07 19:28:49" == "2022-01-07 19:27:49"
267 | "2022-01-07 19:26:49" != "2022-01-07 19:27:49"
268 | ```
269 |
270 | ## Time since...
271 | Returns a number in the specified unit of measure since the secondary date.
272 |
273 | ```swift
274 | Date().since(secondDate, in: .second)
275 | "2009-12-06 06-14-11" since "2009-12-06 06-13-41" in .second == 30
276 | Date().since(secondDate, in: .minute)
277 | "2009-12-06 06-14-11" since "2009-12-06 04-14-11" in .minute == 120
278 | Date().since(secondDate, in: .hour)
279 | "2009-12-06 06-14-11" since "2009-12-06 04-14-11" in .hour == 2
280 | Date().since(secondDate, in: .day)
281 | "2009-12-06" since "2009-12-05" in .day == 1
282 | Date().since(secondDate, in: .week)
283 | "2009-12-06" since "2009-11-29" in .week == 1
284 | "2009-12-06" since "2009-12-13" in .week == -1
285 | Date().since(secondDate, in: .weekdayOrdinal)
286 | "2009-12-06" since "2009-11-22" in .weekdayOrdinal == 2
287 | Date().since(secondDate, in: .month)
288 | "2009-12-06" since "2009-11-06" in .month == 2
289 | Date().since(secondDate, in: .year)
290 | "2009-12-06" since "2008-12-06" in .year == 1
291 | ```
292 |
293 | ## Miscellaneous
294 |
295 | ### Extracting components from a date
296 |
297 | ```swift
298 | Date().component(.second)
299 | "2009-12-06 18:14:11" .second == "11"
300 | Date().component(.minute)
301 | "2009-12-06 18:14:11" .minute == "14"
302 | Date().component(.hour)
303 | "2009-12-06 18:14:11" .hour == "18"
304 | Date().component(.day)
305 | "2009-12-06 18:14:11" .day == "6"
306 | Date().component(.weekday)
307 | "2009-12-06 18:14:11" .weekday == "1"
308 | Date().component(.weekdayOrdinal)
309 | "2009-12-06 18:14:11" .weekdayOrdinal == "1"
310 | Date().component(.month)
311 | "2009-12-06 18:14:11" .month == "12"
312 | Date().component(.year)
313 | "2009-12-06 18:14:11" .year == "2009"
314 | ```
315 |
316 | ### Conveneience methods
317 |
318 | ```swift
319 | Date().numberOfDaysInMonth()
320 | "2021-12-17" numberOfDaysInMonth() == 31
321 | Date().firstDayOfWeek()
322 | "2021-12-17" firstDayOfWeek() == 12
323 | Date().lastDayOfWeek()
324 | "2021-12-17" lastDayOfWeek() == 19
325 | ```
326 |
327 | ### Custom start day of the week
328 |
329 | ```swift
330 | var calendar = Calendar(identifier: .gregorian)
331 | calendar.firstWeekday = 2 // sets the week to start on Monday
332 | Date().dateFor(.startOfWeek, calendar: calendar)
333 | ```
334 |
335 | ## Custom Component guide
336 |
337 | **Unicode Date Field Symbol Guide**
338 |
339 | | Format | Description | Example |
340 | | ------------- | ------------- | ------------- |
341 | | "y" | 1 digit min year | 1, 42, 2017 |
342 | | "yy" | 2 digit year | 01, 42, 17 |
343 | | "yyy" | 3 digit min year | 001, 042, 2017 |
344 | | "yyyy" | 4 digit min year | 0001, 0042, 2017 |
345 | | "M" | 1 digit min month | 7, 12 |
346 | | "MM" | 2 digit month | 07, 12 |
347 | | "MMM" | 3 letter month abbr. | Jul, Dec |
348 | | "MMMM" | Full month | July, December |
349 | | "MMMMM" | 1 letter month abbr. | J, D |
350 | | "d" | 1 digit min day | 4, 25 |
351 | | "dd" | 2 digit day | 04, 25 |
352 | | "E", "EE", "EEE" | 3 letter day name abbr. | Wed, Thu |
353 | | "EEEE" | full day name | Wednesday, Thursday |
354 | | "EEEEE" | 1 letter day name abbr. | W, T |
355 | | "EEEEEE" | 2 letter day name abbr. | We, Th |
356 | | "a" | Period of day | AM, PM |
357 | | "h" | AM/PM 1 digit min hour | 5, 7 |
358 | | "hh" | AM/PM 2 digit hour | 05, 07 |
359 | | "H" | 24 hr 1 digit min hour | 17, 7 |
360 | | "HH" | 24 hr 2 digit hour | 17, 07 |
361 | | "m" | 1 digit min minute | 1, 40 |
362 | | "mm" | 2 digit minute | 01, 40 |
363 | | "s" | 1 digit min second | 1, 40 |
364 | | "ss" | 2 digit second | 01, 40 |
365 | | "S" | 10th's place of fractional second | 123ms -> 1, 7ms -> 0 |
366 | | "SS" | 10th's & 100th's place of fractional second | 123ms -> 12, 7ms -> 00 |
367 | | "SSS" | 10th's & 100th's & 1,000's place of fractional second | 123ms -> 123, 7ms -> 007 |
368 |
369 | ## Requirements
370 |
371 | Language: Swift 5.0
372 | Supports: iOS, tvOS, watchOS, macOS
373 |
374 |
375 | ## Installation
376 |
377 | **Swift Package Manager** https://github.com/melvitax/DateHelper.git
378 | **Carthage** github "melvitax/DateHelper"
379 | **Manually** Include DateHelper.swift in your project
380 |
381 |
382 | ## Author
383 |
384 | Melvin Rivera
385 |
386 | ## License
387 |
388 | DateHelper is available under the MIT license. See the LICENSE file for more info.
389 |
--------------------------------------------------------------------------------
/Sources/DateHelper/DateHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateHelper.swift
3 | // Version: 5.0.1
4 | // https://github.com/melvitax/DateHelper
5 | //
6 |
7 | import Foundation
8 |
9 | // swiftlint:disable file_length
10 | public extension Date {
11 |
12 | // MARK: - Date from String
13 |
14 | /// Creates a new Date from on a string with predefined or custom string format. Supports optional timezone and locale.
15 | init?(fromString string: String, format: DateFormatType, timeZone: TimeZoneType = .local, locale: Locale = Foundation.Locale.current, isLenient: Bool = true) {
16 |
17 | guard !string.isEmpty else { return nil }
18 |
19 | var string = string
20 |
21 | switch format {
22 | case .dotNet:
23 | let pattern = "\\\\?/Date\\((\\d+)(([+-]\\d{2})(\\d{2}))?\\)\\\\?/"
24 | let regex = try? NSRegularExpression(pattern: pattern)
25 | let range = NSRange(location: 0, length: string.utf16.count)
26 | if let match = regex?.firstMatch(in: string, range: range) {
27 | let dateString = (string as NSString).substring(with: match.range(at: 1))
28 | if let dateNumber = Double(dateString) {
29 | let interval = dateNumber / 1000.0
30 | self.init(timeIntervalSince1970: interval)
31 | return
32 | }
33 | }
34 | return nil
35 | case .rss, .altRSS:
36 | if string.hasSuffix("Z") {
37 | string = string[.. String? {
76 | switch style {
77 | case .short, .medium, .long, .full:
78 | return self.toString(dateStyle: style.formatterStyle, timeStyle: style.formatterStyle, isRelative: false)
79 | case .ordinalDay:
80 | let formatter = Date.cachedDateFormatters.cachedNumberFormatter()
81 | formatter?.numberStyle = .ordinal
82 | if let day = component(.day) as NSNumber? {
83 | return formatter?.string(from: day)
84 | }
85 | default:
86 | if let symbols = Date.cachedDateFormatters.cachedFormatter()?.symbols(for: style),
87 | let weekday = component(.weekday) {
88 | return symbols[weekday-1] as String
89 | }
90 | }
91 | return nil
92 | }
93 |
94 | /// Converts the date to string based on a date format, optional timezone and optional locale.
95 | func toString(format: DateFormatType, timeZone: TimeZoneType = .local, locale: Locale = Locale.current) -> String? {
96 | switch format {
97 | case .dotNet:
98 | let offset = Foundation.NSTimeZone.default.secondsFromGMT() / 3600
99 | let nowMillis = 1000 * self.timeIntervalSince1970
100 | return String(format: format.stringFormat, nowMillis, offset)
101 | case .isoDateTimeFull, .isoDateTime, .isoYear, .isoDate, .isoYearMonth:
102 | let formatter = Date.cachedDateFormatters.cachedISOFormatter(format, timeZone: timeZone, locale: locale)
103 | return formatter?.string(from: self)
104 | default:
105 | break
106 | }
107 | if let zone = timeZone.timeZone {
108 | let formatter = Date.cachedDateFormatters.cachedFormatter(format.stringFormat, timeZone: zone, locale: locale)
109 | return formatter?.string(from: self)
110 | }
111 | return nil
112 | }
113 |
114 | /// Converts the date to string based on a date style and time style with optional relative date formatting, optional time zone and optional locale.
115 | // swiftlint:disable:next line_length
116 | func toString(dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style, isRelative: Bool = false, timeZone: Foundation.TimeZone = Foundation.NSTimeZone.local, locale: Locale = Locale.current) -> String? {
117 | let formatter = Date.cachedDateFormatters.cachedFormatter(dateStyle, timeStyle: timeStyle, doesRelativeDateFormatting: isRelative, timeZone: timeZone, locale: locale)
118 | return formatter?.string(from: self)
119 | }
120 |
121 | /// Converts date to string based on a relative time language. i.e. just now, 1 minute ago etc...
122 | // swiftlint:disable:next cyclomatic_complexity
123 | func toStringWithRelativeTime(customStrings: [RelativeTimeType: String]? = nil) -> String? {
124 | let time = self.timeIntervalSince1970
125 | let now = Date().timeIntervalSince1970
126 | let seconds: Double = abs(now - time)
127 | let minutes: Double = round(seconds/60)
128 | let hours: Double = round(minutes/60)
129 | let days: Double = round(hours/24)
130 | let relativeTimeType = toRelativeTime()
131 | let format = customStrings?[relativeTimeType] ?? relativeTimeType.defaultFormat
132 | switch relativeTimeType {
133 | case .secondsPast, .secondsFuture:
134 | return String(format: format, seconds)
135 | case .minutesPast, .minutesFuture:
136 | return String(format: format, minutes)
137 | case .hoursPast, .hoursFuture:
138 | return String(format: format, hours)
139 | case .daysPast, .daysFuture:
140 | return String(format: format, days)
141 | case .weeksPast, .weeksFuture:
142 | if let since = since(Date(), in: .week) {
143 | let w = Double(abs(since))
144 | return String(format: format, w)
145 | }
146 | case .monthsPast, .monthsFuture:
147 | if let since = since(Date(), in: .month) {
148 | let m = Double(abs(since))
149 | return String(format: format, m)
150 | }
151 | case .yearsPast, .yearsFuture:
152 | if let since = since(Date(), in: .year) {
153 | let y = Double(abs(since))
154 | return String(format: format, y)
155 | }
156 | default:
157 | return format
158 | }
159 | return nil
160 | }
161 |
162 | /// Converts the date to a relative time language. i.e. .nowPast, .minutesFuture
163 | // swiftlint:disable:next cyclomatic_complexity
164 | private func toRelativeTime() -> RelativeTimeType {
165 | let time = self.timeIntervalSince1970
166 | let now = Date().timeIntervalSince1970
167 | let isPast = now - time > 0
168 | let seconds: Double = abs(now - time)
169 |
170 | switch seconds {
171 | case let s where s <= DateComponentType.second.inSeconds*10:
172 | return isPast ? .nowPast : .nowFuture
173 | case let s where s < DateComponentType.minute.inSeconds:
174 | return isPast ? .secondsPast : .secondsFuture
175 | case let s where s < DateComponentType.minute.inSeconds*2:
176 | return isPast ? .oneMinutePast : .oneMinuteFuture
177 | case let s where s < DateComponentType.hour.inSeconds:
178 | return isPast ? .minutesPast : .minutesFuture
179 | case let s where s < DateComponentType.hour.inSeconds*2:
180 | return isPast ? .oneHourPast : .oneHourFuture
181 | case let s where s < DateComponentType.day.inSeconds:
182 | return isPast ? .hoursPast : .hoursFuture
183 | case let s where s < DateComponentType.day.inSeconds*2:
184 | return isPast ? .oneDayPast : .oneDayFuture
185 | case let s where s < DateComponentType.day.inSeconds*7:
186 | return isPast ? .daysPast : .daysFuture
187 | case let s where s < DateComponentType.day.inSeconds*14:
188 | return isPast ? .oneWeekPast : .oneWeekFuture
189 | case let s where s < DateComponentType.month.inSeconds:
190 | if isPast {
191 | return compare(.isLastWeek) ? .oneWeekPast : .weeksPast
192 | } else {
193 | return compare(.isNextWeek) ? .oneWeekFuture : .weeksFuture
194 | }
195 | default:
196 | if compare(.isThisYear) {
197 | if isPast {
198 | return compare(.isLastMonth) ? .oneMonthPast : .monthsPast
199 | } else {
200 | return compare(.isNextMonth) ? .oneMonthFuture : .monthsFuture
201 | }
202 | } else {
203 | if isPast {
204 | return compare(.isLastYear) ? .oneYearPast : .yearsPast
205 | } else {
206 | return compare(.isNextYear) ? .oneYearFuture : .yearsFuture
207 | }
208 | }
209 | }
210 | }
211 |
212 | // MARK: - Compare Dates
213 |
214 | /// Compares dates to see if they are equal while ignoring time.
215 | // swiftlint:disable:next cyclomatic_complexity function_body_length
216 | func compare(_ comparison: DateComparisonType) -> Bool {
217 | switch comparison {
218 | case .isToday:
219 | return compare(.isSameDay(as: Date()))
220 | case .isYesterday:
221 | if let comparison = Date().offset(.day, value: -1) {
222 | return compare(.isSameDay(as: comparison))
223 | }
224 | case .isTomorrow:
225 | if let comparison = Date().offset(.day, value: 1) {
226 | return compare(.isSameDay(as: comparison))
227 | }
228 | case .isSameDay(let date):
229 | return component(.year) == date.component(.year) && component(.month) == date.component(.month) && component(.day) == date.component(.day)
230 | case .isThisWeek:
231 | return self.compare(.isSameWeek(as: Date()))
232 | case .isLastWeek:
233 | if let comparison = Date().offset(.week, value: -1) {
234 | return compare(.isSameWeek(as: comparison))
235 | }
236 | case .isNextWeek:
237 | if let comparison = Date().offset(.week, value: 1) {
238 | return compare(.isSameWeek(as: comparison))
239 | }
240 | case .isSameWeek(let date):
241 | if component(.week) != date.component(.week) {
242 | return false
243 | }
244 | // Ensure time interval is under 1 week
245 | return abs(self.timeIntervalSince(date)) < DateComponentType.week.inSeconds
246 | case .isThisMonth:
247 | return self.compare(.isSameMonth(as: Date()))
248 | case .isLastMonth:
249 | if let comparison = Date().offset(.month, value: -1) {
250 | return compare(.isSameMonth(as: comparison))
251 | }
252 | case .isNextMonth:
253 | if let comparison = Date().offset(.month, value: 1) {
254 | return compare(.isSameMonth(as: comparison))
255 | }
256 | case .isSameMonth(let date):
257 | return component(.year) == date.component(.year) && component(.month) == date.component(.month)
258 | case .isThisYear:
259 | return self.compare(.isSameYear(as: Date()))
260 | case .isLastYear:
261 | if let comparison = Date().offset(.year, value: -1) {
262 | return compare(.isSameYear(as: comparison))
263 | }
264 | case .isNextYear:
265 | if let comparison = Date().offset(.year, value: 1) {
266 | return compare(.isSameYear(as: comparison))
267 | }
268 | case .isSameYear(let date):
269 | return component(.year) == date.component(.year)
270 | case .isInTheFuture:
271 | return self.compare(.isLater(than: Date()))
272 | case .isInThePast:
273 | return self.compare(.isEarlier(than: Date()))
274 | case .isEarlier(let date):
275 | return timeIntervalSince1970 < date.timeIntervalSince1970
276 | case .isLater(let date):
277 | return timeIntervalSince1970 > date.timeIntervalSince1970
278 | case .isWeekday:
279 | return !compare(.isWeekend)
280 | case .isWeekend:
281 | if let range = Calendar.current.maximumRange(of: Calendar.Component.weekday) {
282 | return (component(.weekday) == range.lowerBound || component(.weekday) == range.upperBound - range.lowerBound)
283 | }
284 | }
285 | return false
286 | }
287 |
288 | // MARK: - Adjust dates
289 |
290 | /// Offsets a date component with a +/- value
291 | func offset(_ component: DateComponentType, value: Int) -> Date? {
292 | var dateComp = DateComponents()
293 | switch component {
294 | case .second:
295 | dateComp.second = value
296 | case .minute:
297 | dateComp.minute = value
298 | case .hour:
299 | dateComp.hour = value
300 | case .day:
301 | dateComp.day = value
302 | case .weekday:
303 | dateComp.weekday = value
304 | case .weekdayOrdinal:
305 | dateComp.weekdayOrdinal = value
306 | case .week:
307 | dateComp.weekOfYear = value
308 | case .month:
309 | dateComp.month = value
310 | case .year:
311 | dateComp.year = value
312 | }
313 | return Calendar.current.date(byAdding: dateComp, to: self)
314 | }
315 |
316 | /// Sets a new value to the specified component and returns as a new date
317 | func adjust(hour: Int? = nil, minute: Int? = nil, second: Int? = nil, nanosecond: Int? = nil, day: Int? = nil, month: Int? = nil) -> Date? {
318 | var comp = Date.components(self)
319 | comp.month = month ?? comp.month
320 | comp.day = day ?? comp.day
321 | comp.hour = hour ?? comp.hour
322 | comp.minute = minute ?? comp.minute
323 | comp.second = second ?? comp.second
324 | comp.nanosecond = nanosecond ?? comp.nanosecond
325 | return Calendar.current.date(from: comp)
326 | }
327 |
328 | // MARK: - Date for...
329 |
330 | // swiftlint:disable:next cyclomatic_complexity
331 | func adjust(for type: DateAdjustmentType, calendar: Calendar = Calendar.current) -> Date? {
332 | switch type {
333 | case .startOfDay:
334 | return adjust(hour: 0, minute: 0, second: 0)
335 | case .endOfDay:
336 | return adjust(hour: 23, minute: 59, second: 59, nanosecond: 999_000_000) // 23:59:59:999
337 | case .startOfWeek:
338 | return calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear, .hour, .minute, .second, .nanosecond], from: self))
339 | case .endOfWeek:
340 | let weekStart = self.adjust(for: .startOfWeek, calendar: calendar)
341 | return weekStart?.offset(.day, value: 6)
342 | case .startOfMonth:
343 | return adjust(day: 1)
344 | case .endOfMonth:
345 | return adjust(day: numberOfDaysInMonth())
346 | case .tomorrow:
347 | let tomorrow = Date().offset(.day, value: 1)
348 | var components = Date.components(self)
349 | components.year = tomorrow?.component(.year)
350 | components.month = tomorrow?.component(.month)
351 | components.day = tomorrow?.component(.day)
352 | return Calendar.current.date(from: components)
353 | case .yesterday:
354 | let tomorrow = Date().offset(.day, value: -1)
355 | var components = Date.components(self)
356 | components.year = tomorrow?.component(.year)
357 | components.month = tomorrow?.component(.month)
358 | components.day = tomorrow?.component(.day)
359 | return Calendar.current.date(from: components)
360 | case .nearestMinute(let minute):
361 | if let component = component(.minute) {
362 | let value = max(min(minute, 60), 0)
363 | let minutes = (component + value/2) / value * value
364 | return adjust(hour: nil, minute: minutes, second: nil)
365 | }
366 | case .nearestHour(let hour):
367 | if let component = component(.hour) {
368 | let value = max(min(hour, 24), 0)
369 | let hours = (component + value/2) / value * value
370 | return adjust(hour: hours, minute: 0, second: nil)
371 | }
372 | case .startOfYear:
373 | if let component = component(.year) {
374 | let date = Date(fromString: "\(component)-01-01", format: .isoDate)
375 | return date?.adjust(hour: 0, minute: 0, second: 0)
376 | }
377 | case .endOfYear:
378 | if let component = component(.year) {
379 | let date = Date(fromString: "\(component)-12-31", format: .isoDate)
380 | return date?.adjust(hour: 23, minute: 59, second: 59)
381 | }
382 | }
383 | return nil
384 | }
385 |
386 | // MARK: - Time since...
387 |
388 | func since(_ date: Date, in component: DateComponentType) -> Int64? {
389 | let calendar = Calendar.current
390 | switch component {
391 | case .second, .minute, .hour:
392 | let interval = timeIntervalSince(date)
393 | return Int64(interval / component.inSeconds)
394 | case .day, .weekday, .weekdayOrdinal, .week, .month, .year:
395 | if let end = calendar.ordinality(of: component.calendarComponent, in: .era, for: self),
396 | let start = calendar.ordinality(of: component.calendarComponent, in: .era, for: date) {
397 | return Int64(end - start)
398 | }
399 | }
400 | return nil
401 | }
402 |
403 | // MARK: - Extracting components
404 |
405 | func component(_ component: DateComponentType) -> Int? {
406 | let components = Date.components(self)
407 | switch component {
408 | case .second:
409 | return components.second
410 | case .minute:
411 | return components.minute
412 | case .hour:
413 | return components.hour
414 | case .day:
415 | return components.day
416 | case .weekday:
417 | return components.weekday
418 | case .weekdayOrdinal:
419 | return components.weekdayOrdinal
420 | case .week:
421 | return components.weekOfYear
422 | case .month:
423 | return components.month
424 | case .year:
425 | return components.year
426 | }
427 | }
428 |
429 | func numberOfDaysInMonth() -> Int? {
430 | let day = Calendar.Component.day
431 | let month = Calendar.Component.month
432 | if let range = Calendar.current.range(of: day, in: month, for: self) {
433 | return range.upperBound - range.lowerBound
434 | }
435 | return nil
436 | }
437 |
438 | func firstDayOfWeek() -> Int? {
439 | guard let component = component(.weekday) else { return nil }
440 | let aDay = DateComponentType.day.inSeconds
441 | let startOfWeekOffset = aDay * Double(component - 1)
442 | let interval: TimeInterval = timeIntervalSinceReferenceDate - startOfWeekOffset
443 | return Date(timeIntervalSinceReferenceDate: interval).component(.day)
444 | }
445 |
446 | func lastDayOfWeek() -> Int? {
447 | guard let component = component(.weekday) else { return nil }
448 | let aDay = DateComponentType.day.inSeconds
449 | let tartOfWeekOffset = aDay * Double(component - 1)
450 | let endOfWeekOffset = aDay * Double(7)
451 | let interval: TimeInterval = timeIntervalSinceReferenceDate - tartOfWeekOffset + endOfWeekOffset
452 | return Date(timeIntervalSinceReferenceDate: interval).component(.day)
453 | }
454 |
455 | // MARK: - Internal Components
456 |
457 | internal static func componentFlags() -> Set {
458 | [.year, .month, .day, .weekOfYear, .hour, .minute, .second, .weekday, .weekdayOrdinal, .weekOfYear]
459 | }
460 | internal static func components(_ fromDate: Date) -> DateComponents {
461 | Calendar.current.dateComponents(Date.componentFlags(), from: fromDate)
462 | }
463 |
464 | // MARK: Formatter
465 |
466 | private class ConcurrentFormatterCache {
467 |
468 | private static let cachedISODateFormattersQueue = DispatchQueue(
469 | label: "iso-date-formatter-queue",
470 | attributes: .concurrent
471 | )
472 | private static let cachedDateFormattersQueue = DispatchQueue(
473 | label: "date-formatter-queue",
474 | attributes: .concurrent
475 | )
476 |
477 | private static let cachedNumberFormatterQueue = DispatchQueue(
478 | label: "number-formatter-queue",
479 | attributes: .concurrent
480 | )
481 |
482 | private static var cachedISODateFormatters = [String: ISO8601DateFormatter]()
483 | private static var cachedDateFormatters = [String: DateFormatter]()
484 | private static var cachedNumberFormatter = NumberFormatter()
485 |
486 | private func register(hashKey: String, formatter: ISO8601DateFormatter) {
487 | ConcurrentFormatterCache.cachedISODateFormattersQueue.async(flags: .barrier) {
488 | ConcurrentFormatterCache.cachedISODateFormatters.updateValue(formatter, forKey: hashKey)
489 | }
490 | }
491 |
492 | private func register(hashKey: String, formatter: DateFormatter) {
493 | ConcurrentFormatterCache.cachedDateFormattersQueue.async(flags: .barrier) {
494 | ConcurrentFormatterCache.cachedDateFormatters.updateValue(formatter, forKey: hashKey)
495 | }
496 | }
497 |
498 | private func retrieve(hashKeyForISO hashKey: String) -> ISO8601DateFormatter? {
499 | let dateFormatter = ConcurrentFormatterCache.cachedISODateFormattersQueue.sync { () -> ISO8601DateFormatter? in
500 | guard let result = ConcurrentFormatterCache.cachedISODateFormatters[hashKey] else { return nil }
501 | return result.copy() as? ISO8601DateFormatter
502 | }
503 | return dateFormatter
504 | }
505 |
506 | private func retrieve(hashKey: String) -> DateFormatter? {
507 | let dateFormatter = ConcurrentFormatterCache.cachedDateFormattersQueue.sync { () -> DateFormatter? in
508 | guard let result = ConcurrentFormatterCache.cachedDateFormatters[hashKey] else { return nil }
509 | return result.copy() as? DateFormatter
510 | }
511 | return dateFormatter
512 | }
513 |
514 | private func retrieve() -> NumberFormatter? {
515 | let numberFormatter = ConcurrentFormatterCache.cachedNumberFormatterQueue.sync { () -> NumberFormatter? in
516 | if let formatter = ConcurrentFormatterCache.cachedNumberFormatter.copy() as? NumberFormatter {
517 | return formatter
518 | }
519 | return nil
520 | }
521 | return numberFormatter
522 | }
523 |
524 | public func cachedISOFormatter(_ format: DateFormatType, timeZone: TimeZoneType, locale: Locale) -> ISO8601DateFormatter? {
525 |
526 | let hashKey = "\(format.stringFormat.hashValue)\(timeZone.timeZone.hashValue)\(locale.hashValue)"
527 |
528 | if Date.cachedDateFormatters.retrieve(hashKeyForISO: hashKey) == nil {
529 | let formatter = ISO8601DateFormatter()
530 | formatter.timeZone = timeZone.timeZone
531 |
532 | var options: ISO8601DateFormatter.Options = []
533 | switch format {
534 | case .isoYear:
535 | options = [.withYear, .withFractionalSeconds]
536 | case .isoYearMonth:
537 | options = [.withYear, .withMonth, .withDashSeparatorInDate]
538 | case .isoDate:
539 | options = [.withFullDate]
540 | case .isoDateTime:
541 | options = [.withInternetDateTime]
542 | case .isoDateTimeFull:
543 | options = [.withInternetDateTime, .withFractionalSeconds]
544 | default:
545 | fatalError("Unimplemented format \(format)")
546 | }
547 | formatter.formatOptions = options
548 | Date.cachedDateFormatters.register(hashKey: hashKey, formatter: formatter)
549 | }
550 | return Date.cachedDateFormatters.retrieve(hashKeyForISO: hashKey)
551 | }
552 |
553 | public func cachedFormatter(_ format: String = DateFormatType.standard.stringFormat,
554 | timeZone: Foundation.TimeZone = Foundation.TimeZone.current,
555 | locale: Locale = Locale.current, isLenient: Bool = true) -> DateFormatter? {
556 |
557 | let hashKey = "\(format.hashValue)\(timeZone.hashValue)\(locale.hashValue)"
558 |
559 | if Date.cachedDateFormatters.retrieve(hashKey: hashKey) == nil {
560 | let formatter = DateFormatter()
561 | formatter.dateFormat = format
562 | formatter.timeZone = timeZone
563 | formatter.locale = locale
564 | formatter.isLenient = isLenient
565 | Date.cachedDateFormatters.register(hashKey: hashKey, formatter: formatter)
566 | }
567 | return Date.cachedDateFormatters.retrieve(hashKey: hashKey)
568 | }
569 |
570 | /// Generates a cached formatter based on the provided date style, time style and relative date.
571 | /// Formatters are cached in a singleton array using hashkeys.
572 | // swiftlint:disable:next line_length
573 | public func cachedFormatter(_ dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style, doesRelativeDateFormatting: Bool, timeZone: Foundation.TimeZone = Foundation.NSTimeZone.local, locale: Locale = Locale.current, isLenient: Bool = true) -> DateFormatter? {
574 | let hashKey = "\(dateStyle.hashValue)\(timeStyle.hashValue)\(doesRelativeDateFormatting.hashValue)\(timeZone.hashValue)\(locale.hashValue)"
575 | if Date.cachedDateFormatters.retrieve(hashKey: hashKey) == nil {
576 | let formatter = DateFormatter()
577 | formatter.dateStyle = dateStyle
578 | formatter.timeStyle = timeStyle
579 | formatter.doesRelativeDateFormatting = doesRelativeDateFormatting
580 | formatter.timeZone = timeZone
581 | formatter.locale = locale
582 | formatter.isLenient = isLenient
583 | Date.cachedDateFormatters.register(hashKey: hashKey, formatter: formatter)
584 | }
585 | return Date.cachedDateFormatters.retrieve(hashKey: hashKey)
586 | }
587 |
588 | public func cachedNumberFormatter() -> NumberFormatter? {
589 | Date.cachedDateFormatters.retrieve()
590 | }
591 | }
592 |
593 | /// A cached static array of DateFormatters so that thy are only created once.
594 | private static var cachedDateFormatters = Self.ConcurrentFormatterCache()
595 |
596 | // MARK: - Enums
597 |
598 | /// The date format type used for string conversion.
599 | enum DateFormatType {
600 | /// The ISO8601 formatted year "yyyy" i.e. 1997
601 | case isoYear
602 | /// The ISO8601 formatted year and month "yyyy-MM" i.e. 1997-07
603 | case isoYearMonth
604 | /// The ISO8601 formatted date "yyyy-MM-dd" i.e. 1997-07-16
605 | case isoDate
606 | /// The ISO8601 formatted date, time and sec "yyyy-MM-dd'T'HH:mm:ssZ" i.e. 1997-07-16T19:20:30+01:00
607 | case isoDateTime
608 | /// The ISO8601 formatted date, time and millisec "yyyy-MM-dd'T'HH:mm:ss.SSSZ" i.e. 1997-07-16T19:20:30.45+01:00
609 | case isoDateTimeFull
610 | /// The dotNet formatted date "/Date(%d%d)/" i.e. "/Date(1268123281843)/"
611 | case dotNet
612 | /// The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200"
613 | case rss
614 | /// The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200"
615 | case altRSS
616 | /// The http header formatted date "EEE, dd MM yyyy HH:mm:ss ZZZ" i.e. "Tue, 15 Nov 1994 12:45:26 GMT"
617 | case httpHeader
618 | /// A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy"
619 | case standard
620 | /// A custom date format string
621 | case custom(String)
622 | var stringFormat: String {
623 | switch self {
624 | case .isoYear: return "yyyy"
625 | case .isoYearMonth: return "yyyy-MM"
626 | case .isoDate: return "yyyy-MM-dd"
627 | case .isoDateTime: return "yyyy-MM-dd'T'HH:mm:ssZ"
628 | case .isoDateTimeFull: return "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
629 | case .dotNet: return "/Date(%d%f)/"
630 | case .rss: return "EEE, d MMM yyyy HH:mm:ss ZZZ"
631 | case .altRSS: return "d MMM yyyy HH:mm:ss ZZZ"
632 | case .httpHeader: return "EEE, dd MM yyyy HH:mm:ss ZZZ"
633 | case .standard: return "EEE MMM dd HH:mm:ss Z yyyy"
634 | case .custom(let customFormat): return customFormat
635 | }
636 | }
637 | }
638 |
639 | /// The time zone to be used for date conversion
640 | enum TimeZoneType {
641 | case local, `default`, utc, custom(Int)
642 | var timeZone: TimeZone? {
643 | switch self {
644 | case .local: return NSTimeZone.local
645 | case .default: return NSTimeZone.default
646 | case .utc: return TimeZone(secondsFromGMT: 0)
647 | case let .custom(gmt): return TimeZone(secondsFromGMT: gmt)
648 | }
649 | }
650 | }
651 |
652 | /// The string keys to modify the strings in relative format
653 | enum RelativeTimeType {
654 | // swiftlint:disable:next line_length
655 | /// A string representing the past tense of something recent in less than 10 seconds: "just now"
656 | case nowPast
657 | /// A string representing the future tense of something soon in less than 10 seconds: "in a few seconds"
658 | case nowFuture
659 | /// A formatted string representing the past tense of something less than a minute in seconds: "%.f seconds ago"
660 | case secondsPast
661 | /// A formatted string representing the future tense of something less than a minute in seconds: "in %.f seconds"
662 | case secondsFuture
663 | /// A string representing the past tense of less than 2 minutes: "one minute ago"
664 | case oneMinutePast
665 | /// A string representing the future tense of less than 2 minutes: "in one minute"
666 | case oneMinuteFuture
667 | /// A formatted string representing the past tense of something less than an hour: "%.f minutes ago"
668 | case minutesPast
669 | /// A formatted string representing the future tense of something in less than an hour: "in %.f minutes"
670 | case minutesFuture
671 | /// A string representing the past tense of less than 2 hours: "one hour ago"
672 | case oneHourPast
673 | /// A string representing the future tense of less than 2 hours: "in one hour"
674 | case oneHourFuture
675 | /// A formatted string representing the past tense of something less than a day: "%.f hours ago"
676 | case hoursPast
677 | /// A formatted string representing the future tense of something in less than a day: "in %.f hours"
678 | case hoursFuture
679 | /// A string representing the past tense of less than 2 days: "yesterday"
680 | case oneDayPast
681 | /// A string representing the future tense of less than 2 days: "tomorrow"
682 | case oneDayFuture
683 | /// A formatted string representing the past tense of something less than a week: "%.f days ago"
684 | case daysPast
685 | /// A formatted string representing the future tense of something in less than a week: "in %.f days"
686 | case daysFuture
687 | /// A string representing the past tense of less than 2 weeks: "last week"
688 | case oneWeekPast
689 | /// A string representing the future tense of less than 2 weeks: "next week"
690 | case oneWeekFuture
691 | /// A formatted string representing the past tense of something less than a month: "%.f weeks ago"
692 | case weeksPast
693 | /// A formatted string representing the future tense of something in less than a month: "in %.f weeks"
694 | case weeksFuture
695 | /// A string representing the past tense of less than 2 months: "last month"
696 | case oneMonthPast
697 | /// A string representing the future tense of less than 2 months: "next month"
698 | case oneMonthFuture
699 | /// A formatted string representing the past tense of something less than a year: "%.f months ago"
700 | case monthsPast
701 | /// A formatted string representing the future tense of something in less than a year: "in %.f months"
702 | case monthsFuture
703 | /// A string representing the past tense of less than 2 years: "last year"
704 | case oneYearPast
705 | /// A string representing the future tense of less than 2 years: "next year"
706 | case oneYearFuture
707 | /// A formatted string representing past tense of years: "%.f years ago"
708 | case yearsPast
709 | /// A formatted string representing the future tense of years: "in %.f years"
710 | case yearsFuture
711 | var defaultFormat: String {
712 | switch self {
713 | case .nowPast:
714 | return NSLocalizedString("just now", comment: "Date format")
715 | case .nowFuture:
716 | return NSLocalizedString("in a few seconds", comment: "Date format")
717 | case .secondsPast:
718 | return NSLocalizedString("%.f seconds ago", comment: "Date format")
719 | case .secondsFuture:
720 | return NSLocalizedString("in %.f seconds", comment: "Date format")
721 | case .oneMinutePast:
722 | return NSLocalizedString("1 minute ago", comment: "Date format")
723 | case .oneMinuteFuture:
724 | return NSLocalizedString("in 1 minute", comment: "Date format")
725 | case .minutesPast:
726 | return NSLocalizedString("%.f minutes ago", comment: "Date format")
727 | case .minutesFuture:
728 | return NSLocalizedString("in %.f minutes", comment: "Date format")
729 | case .oneHourPast:
730 | return NSLocalizedString("last hour", comment: "Date format")
731 | case .oneHourFuture:
732 | return NSLocalizedString("next hour", comment: "Date format")
733 | case .hoursPast:
734 | return NSLocalizedString("%.f hours ago", comment: "Date format")
735 | case .hoursFuture:
736 | return NSLocalizedString("in %.f hours", comment: "Date format")
737 | case .oneDayPast:
738 | return NSLocalizedString("yesterday", comment: "Date format")
739 | case .oneDayFuture:
740 | return NSLocalizedString("tomorrow", comment: "Date format")
741 | case .daysPast:
742 | return NSLocalizedString("%.f days ago", comment: "Date format")
743 | case .daysFuture:
744 | return NSLocalizedString("in %.f days", comment: "Date format")
745 | case .oneWeekPast:
746 | return NSLocalizedString("last week", comment: "Date format")
747 | case .oneWeekFuture:
748 | return NSLocalizedString("next week", comment: "Date format")
749 | case .weeksPast:
750 | return NSLocalizedString("%.f weeks ago", comment: "Date format")
751 | case .weeksFuture:
752 | return NSLocalizedString("in %.f weeks", comment: "Date format")
753 | case .oneMonthPast:
754 | return NSLocalizedString("last month", comment: "Date format")
755 | case .oneMonthFuture:
756 | return NSLocalizedString("next month", comment: "Date format")
757 | case .monthsPast:
758 | return NSLocalizedString("%.f months ago", comment: "Date format")
759 | case .monthsFuture:
760 | return NSLocalizedString("in %.f months", comment: "Date format")
761 | case .oneYearPast:
762 | return NSLocalizedString("last year", comment: "Date format")
763 | case .oneYearFuture:
764 | return NSLocalizedString("next year", comment: "Date format")
765 | case .yearsPast:
766 | return NSLocalizedString("%.f years ago", comment: "Date format")
767 | case .yearsFuture:
768 | return NSLocalizedString("in %.f years", comment: "Date format")
769 | }
770 | }
771 | }
772 |
773 | /// The type of comparison to do against today's date or with the suplied date.
774 | enum DateComparisonType {
775 | // Days
776 | /// Checks if date today.
777 | case isToday
778 | /// Checks if date is tomorrow.
779 | case isTomorrow
780 | /// Checks if date is yesterday.
781 | case isYesterday
782 | /// Compares date days
783 | case isSameDay(as: Date)
784 | // Weeks
785 | /// Checks if date is in this week.
786 | case isThisWeek
787 | /// Checks if date is in next week.
788 | case isNextWeek
789 | /// Checks if date is in last week.
790 | case isLastWeek
791 | /// Compares date weeks
792 | case isSameWeek(as: Date)
793 | // Months
794 | /// Checks if date is in this month.
795 | case isThisMonth
796 | /// Checks if date is in next month.
797 | case isNextMonth
798 | /// Checks if date is in last month.
799 | case isLastMonth
800 | /// Compares date months
801 | case isSameMonth(as: Date)
802 | // Years
803 | /// Checks if date is in this year.
804 | case isThisYear
805 | /// Checks if date is in next year.
806 | case isNextYear
807 | /// Checks if date is in last year.
808 | case isLastYear
809 | /// Compare date years
810 | case isSameYear(as: Date)
811 | // Relative Time
812 | /// Checks if it's a future date
813 | case isInTheFuture
814 | /// Checks if the date has passed
815 | case isInThePast
816 | /// Checks if earlier than date
817 | case isEarlier(than: Date)
818 | /// Checks if later than date
819 | case isLater(than: Date)
820 | /// Checks if it's a weekday
821 | case isWeekday
822 | /// Checks if it's a weekend
823 | case isWeekend
824 | }
825 |
826 | /// The date components available to be retrieved or modifed
827 | enum DateComponentType {
828 | case second, minute, hour, day, weekday, weekdayOrdinal, week, month, year
829 | var inSeconds: Double {
830 | switch self {
831 | case .second: return 1
832 | case .minute: return 60
833 | case .hour: return 3600
834 | case .day: return 86400
835 | case .week: return 604800
836 | case .month: return 2419200 // 28 days
837 | case .year: return 31556926
838 | default: return 0
839 | }
840 | }
841 | var calendarComponent: Calendar.Component {
842 | switch self {
843 | case .second: return .second
844 | case .minute: return .minute
845 | case .hour: return .hour
846 | case .day: return .day
847 | case .weekday: return .weekday
848 | case .weekdayOrdinal: return .weekdayOrdinal
849 | case .week: return .weekOfYear
850 | case .month: return .month
851 | case .year: return .year
852 | }
853 | }
854 | }
855 |
856 | /// The type of date that can be used for the dateFor function
857 | enum DateAdjustmentType {
858 | // Adjusts the date to the begining of the day at 00:00:01
859 | case startOfDay
860 | // Adjusts the date to the end of the day at 11:59:59
861 | case endOfDay
862 | // Adjusts the day to the start of the week
863 | case startOfWeek
864 | // Adjusts the day to the end of the week
865 | case endOfWeek
866 | // Adjusts the day to the start of the month
867 | case startOfMonth
868 | // Adjusts the day to the end of the month
869 | case endOfMonth
870 | // Adjusts the day to tomorrow
871 | case tomorrow
872 | // Adjusts the day to yestersay
873 | case yesterday
874 | // Adjusts the date to the nearest minute. ie: with nearestMinute(minute: 30) 10:17 becomes 10:30 and 10:05 becomes 10:00
875 | case nearestMinute(minute: Int)
876 | // Adjusts the date to the nearest hours. ie: with nearestHour(hour: 6) 10:17 becomes 12:17 and 07:017 becomes 06:17
877 | case nearestHour(hour: Int)
878 | // Adjusts the day to the start of the year
879 | case startOfYear
880 | // Adjusts the day to the end of the year
881 | case endOfYear
882 | }
883 |
884 | /// Convenience types for date to string conversion
885 | enum DateStyleType: String {
886 | /// Short style: "2/27/17, 2:22 PM"
887 | case short
888 | /// Medium style: "Feb 27, 2017, 2:22:06 PM"
889 | case medium
890 | /// Long style: "February 27, 2017 at 2:22:06 PM EST"
891 | case long
892 | /// Full style: "Monday, February 27, 2017 at 2:22:06 PM Eastern Standard Time"
893 | case full
894 | /// Ordinal day: "27th"
895 | case ordinalDay
896 | /// Weekday: "Monday"
897 | case weekday
898 | /// Short week day: "Mon"
899 | case shortWeekday
900 | /// Very short weekday: "M"
901 | case veryShortWeekday
902 | /// Month: "February"
903 | case month
904 | /// Short month: "Feb"
905 | case shortMonth
906 | /// Very short month: "F"
907 | case veryShortMonth
908 | var formatterStyle: DateFormatter.Style {
909 | switch self {
910 | case .short: return .short
911 | case .medium: return .medium
912 | case .long: return .long
913 | case .full: return .full
914 | default: return .none
915 | }
916 | }
917 | }
918 | }
919 |
920 | extension Date.DateFormatType: Equatable {
921 | // swiftlint:disable:next operator_whitespace
922 | public static func ==(lhs: Date.DateFormatType, rhs: Date.DateFormatType) -> Bool {
923 | switch (lhs, rhs) {
924 | case (.custom(let lhsString), .custom(let rhsString)):
925 | return lhsString == rhsString
926 | default:
927 | return lhs == rhs
928 | }
929 | }
930 | }
931 |
932 | extension DateFormatter {
933 | fileprivate func symbols(for dateStyleType: Date.DateStyleType) -> [String] {
934 | switch dateStyleType {
935 | case .weekday: return weekdaySymbols
936 | case .shortWeekday: return shortWeekdaySymbols
937 | case .veryShortWeekday: return veryShortWeekdaySymbols
938 | case .month: return monthSymbols
939 | case .shortMonth: return shortMonthSymbols
940 | case .veryShortMonth: return veryShortMonthSymbols
941 | default: return [String]()
942 | }
943 | }
944 | }
945 |
--------------------------------------------------------------------------------
/Tests/DateHelperTests/DateAdjustingTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DateHelper
3 |
4 | final class DateHelperAdjustingTests: XCTestCase {
5 | // adjust date and time
6 | func testDateAdjustDateAndTime() throws {
7 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
8 | let adjusted = original.adjust(hour: 1, minute: 10, second: 30, day: 15, month: 1),
9 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 01, day: 15, hour: 1, minute: 10, second: 30)) else {
10 | return
11 | }
12 | XCTAssertEqual(adjusted, comparison)
13 | }
14 | // offset second
15 | func testDateOffsetSecond() throws {
16 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
17 | let adjusted = original.offset(.second, value: 10),
18 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 51)) else {
19 | return
20 | }
21 | XCTAssertEqual(adjusted, comparison)
22 | }
23 | // offset minute
24 | func testDateOffsetMinute() throws {
25 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14)),
26 | let adjusted = original.offset(.minute, value: 10),
27 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 24)) else {
28 | return
29 | }
30 | XCTAssertEqual(adjusted, comparison)
31 | }
32 | // offset hour
33 | func testDateOffsetHour() throws {
34 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18)),
35 | let adjusted = original.offset(.hour, value: 2),
36 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 20)) else {
37 | return
38 | }
39 | XCTAssertEqual(adjusted, comparison)
40 | }
41 | // offset day
42 | func testDateOffsetDay() throws {
43 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06)),
44 | let adjusted = original.offset(.day, value: 10),
45 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 16)) else {
46 | return
47 | }
48 | XCTAssertEqual(adjusted, comparison)
49 | }
50 | // offset weekday
51 | func testDateOffsetWeekday() throws {
52 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06)),
53 | let adjusted = original.offset(.weekday, value: 10),
54 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 16)) else {
55 | return
56 | }
57 | XCTAssertEqual(adjusted, comparison)
58 | }
59 | // offset ordinal weekday
60 | func testDateOffsetWeekdayOrdinal() throws {
61 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06)),
62 | let adjusted = original.offset(.weekdayOrdinal, value: 2),
63 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 20)) else {
64 | return
65 | }
66 | XCTAssertEqual(adjusted, comparison)
67 | }
68 | // offset week
69 | func testDateOffsetWeek() throws {
70 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06)),
71 | let adjusted = original.offset(.week, value: -2),
72 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 11, day: 22)) else {
73 | return
74 | }
75 | XCTAssertEqual(adjusted, comparison)
76 | }
77 | // offset month
78 | func testDateOffsetMonth() throws {
79 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06)),
80 | let adjusted = original.offset(.month, value: 2),
81 | let comparison = Calendar.current.date(from: DateComponents(year: 2010, month: 02, day: 06)) else {
82 | return
83 | }
84 | XCTAssertEqual(adjusted, comparison)
85 | }
86 | // offset week
87 | func testDateOffsetYear() throws {
88 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06)),
89 | let adjusted = original.offset(.year, value: -2),
90 | let comparison = Calendar.current.date(from: DateComponents(year: 2007, month: 12, day: 06)) else {
91 | return
92 | }
93 | XCTAssertEqual(adjusted, comparison)
94 | }
95 | // adjust .startOfDay
96 | func testDateAdjustStartOfDay() throws {
97 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
98 | let adjusted = original.adjust(for: .startOfDay),
99 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 0, minute: 0, second: 0)) else {
100 | return
101 | }
102 | XCTAssertEqual(adjusted, comparison)
103 | }
104 | // adjust .endOfDay
105 | func testDateAdjustEndOfDay() throws {
106 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
107 | let adjusted = original.adjust(for: .endOfDay),
108 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 23, minute: 59, second: 59, nanosecond: 999_000_000)) else {
109 | return
110 | }
111 | XCTAssertEqual(adjusted, comparison)
112 | }
113 | // adjust .startOfWeek
114 | func testDateAdjustStartOfWeek() throws {
115 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 08, hour: 18, minute: 14, second: 41)),
116 | let adjusted = original.adjust(for: .startOfWeek),
117 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)) else {
118 | return
119 | }
120 | XCTAssertEqual(adjusted, comparison)
121 | }
122 | // adjust .endOfWeek
123 | func testDateAdjustEndOfWeek() throws {
124 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
125 | let adjusted = original.adjust(for: .endOfWeek),
126 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 12, hour: 18, minute: 14, second: 41)) else {
127 | return
128 | }
129 | XCTAssertEqual(adjusted, comparison)
130 | }
131 | // adjust .startOfMonth
132 | func testDateAdjustStartOfMonth() throws {
133 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
134 | let adjusted = original.adjust(for: .startOfMonth),
135 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 01, hour: 18, minute: 14, second: 41)) else {
136 | return
137 | }
138 | XCTAssertEqual(adjusted, comparison)
139 | }
140 | // adjust .endOfMonth
141 | func testDateAdjustEndOfMonth() throws {
142 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
143 | let adjusted = original.adjust(for: .endOfMonth),
144 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 31, hour: 18, minute: 14, second: 41)) else {
145 | return
146 | }
147 | XCTAssertEqual(adjusted, comparison)
148 | }
149 | // adjust .tomorrow
150 | func testDateAdjustTomorrow() throws {
151 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
152 | let adjusted = original.adjust(for: .tomorrow),
153 | let tomorrow = Date().offset(.day, value: 1) else {
154 | return
155 | }
156 | let components = Date.components(tomorrow)
157 | guard let comparison = Calendar.current.date(from: DateComponents(year: components.year, month: components.month, day: components.day, hour: 18, minute: 14, second: 41)) else {
158 | return
159 | }
160 | XCTAssertEqual(adjusted, comparison)
161 | }
162 | // adjust .yesterday
163 | func testDateAdjustYesterday() throws {
164 | guard let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
165 | let adjusted = original.adjust(for: .yesterday),
166 | let yesterday = Date().offset(.day, value: -1) else {
167 | return
168 | }
169 | let components = Date.components(yesterday)
170 | guard let comparison = Calendar.current.date(from: DateComponents(year: components.year, month: components.month, day: components.day, hour: 18, minute: 14, second: 41)) else {
171 | return
172 | }
173 | XCTAssertEqual(adjusted, comparison)
174 | }
175 | // adjust .nearestMinute
176 | func testDateAdjustNearestMinute() throws {
177 | guard
178 | let original1 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 18, minute: 14)),
179 | let adjusted1 = original1.adjust(for: .nearestMinute(minute: 30)),
180 | let comparison1 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 18, minute: 00)),
181 | let original2 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 18, minute: 40)),
182 | let adjusted2 = original2.adjust(for: .nearestMinute(minute: 30)),
183 | let comparison2 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 18, minute: 30)),
184 | let original3 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 18, minute: 50)),
185 | let adjusted3 = original3.adjust(for: .nearestMinute(minute: 30)),
186 | let comparison3 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 19, minute: 00)) else {
187 | return
188 | }
189 | XCTAssertEqual(adjusted1, comparison1)
190 | XCTAssertEqual(adjusted2, comparison2)
191 | XCTAssertEqual(adjusted3, comparison3)
192 | }
193 | // adjust .nearestHour
194 | func testDateAdjustNearestHour() throws {
195 | guard
196 | let original1 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 18)),
197 | let adjusted1 = original1.adjust(for: .nearestHour(hour: 12)),
198 | let comparison1 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 08, hour: 00)),
199 | let original2 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 07)),
200 | let adjusted2 = original2.adjust(for: .nearestHour(hour: 12)),
201 | let comparison2 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 12)),
202 | let original3 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 03)),
203 | let adjusted3 = original3.adjust(for: .nearestHour(hour: 12)),
204 | let comparison3 = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 07, hour: 00)) else {
205 | return
206 | }
207 | XCTAssertEqual(adjusted1, comparison1)
208 | XCTAssertEqual(adjusted2, comparison2)
209 | XCTAssertEqual(adjusted3, comparison3)
210 | }
211 | // adjust .startOfYear
212 | func testDateAdjustStartOfYear() throws {
213 | guard
214 | let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41)),
215 | let adjusted = original.adjust(for: .startOfYear),
216 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 01, day: 01, hour: 0)) else {
217 | return
218 | }
219 | XCTAssertEqual(adjusted, comparison)
220 | }
221 | // adjust .endOfYear
222 | func testDateAdjustEndOfYear() throws {
223 | guard
224 | let original = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 00)),
225 | let adjusted = original.adjust(for: .endOfYear),
226 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 31, hour: 23, minute: 59, second: 59)) else {
227 | return
228 | }
229 | XCTAssertEqual(adjusted, comparison)
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/Tests/DateHelperTests/DateComparisonTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DateHelper
3 |
4 | final class DateHelperComparisonTests: XCTestCase {
5 | let now = Date()
6 | // .isToday
7 | func testDateTodayComparison() throws {
8 | guard let comparison = now.offset(.day, value: 1) else {
9 | return
10 | }
11 | XCTAssertTrue(!comparison.compare(.isToday))
12 | XCTAssertTrue(now.compare(.isToday))
13 | }
14 | // .isTomorrow
15 | func testDateTomorrowComparison() throws {
16 | guard let comparison = now.offset(.day, value: 1) else {
17 | return
18 | }
19 | XCTAssertTrue(comparison.compare(.isTomorrow))
20 | XCTAssertTrue(!now.compare(.isTomorrow))
21 | }
22 | // .isYesterday
23 | func testDateYesterdayComparison() throws {
24 | guard let comparison = now.offset(.day, value: -1) else {
25 | return
26 | }
27 | XCTAssertTrue(comparison.compare(.isYesterday))
28 | XCTAssertTrue(!now.compare(.isYesterday))
29 | }
30 | // .isThisWeek
31 | func testDateThisWeekComparison() throws {
32 | guard let comparison = now.offset(.week, value: -1) else {
33 | return
34 | }
35 | XCTAssertTrue(!comparison.compare(.isThisWeek))
36 | XCTAssertTrue(now.compare(.isThisWeek))
37 | }
38 | // .isNextWeek
39 | func testDateNextWeekComparison() throws {
40 | guard let comparison = now.offset(.week, value: 1) else {
41 | return
42 | }
43 | XCTAssertTrue(comparison.compare(.isNextWeek))
44 | XCTAssertTrue(!now.compare(.isNextWeek))
45 | }
46 | // .isLastWeek
47 | func testDateLastWeekComparison() throws {
48 | guard let comparison = now.offset(.week, value: -1) else {
49 | return
50 | }
51 | XCTAssertTrue(comparison.compare(.isLastWeek))
52 | XCTAssertTrue(!now.compare(.isLastWeek))
53 | }
54 | // .isThisYear
55 | func testDateThisYearComparison() throws {
56 | guard let comparison = now.offset(.year, value: -1) else {
57 | return
58 | }
59 | XCTAssertTrue(!comparison.compare(.isThisYear))
60 | XCTAssertTrue(now.compare(.isThisYear))
61 | }
62 | // .isNextYear
63 | func testDateNextYearComparison() throws {
64 | guard let comparison = now.offset(.year, value: 1) else {
65 | return
66 | }
67 | XCTAssertTrue(comparison.compare(.isNextYear))
68 | XCTAssertTrue(!now.compare(.isNextYear))
69 | }
70 | // .isLastYear
71 | func testDateLastYearComparison() throws {
72 | guard let comparison = now.offset(.year, value: -1) else {
73 | return
74 | }
75 | XCTAssertTrue(comparison.compare(.isLastYear))
76 | XCTAssertTrue(!now.compare(.isLastYear))
77 | }
78 | // .isInTheFuture
79 | func testDateFutureComparison() throws {
80 | guard let future = now.offset(.week, value: 1),
81 | let past = now.offset(.week, value: -1) else {
82 | return
83 | }
84 | XCTAssertTrue(future.compare(.isInTheFuture))
85 | XCTAssertTrue(!past.compare(.isInTheFuture))
86 | }
87 | // .isInThePast
88 | func testDatePastComparison() throws {
89 | guard let future = now.offset(.week, value: 1),
90 | let past = now.offset(.week, value: -1) else {
91 | return
92 | }
93 | XCTAssertTrue(!future.compare(.isInThePast))
94 | XCTAssertTrue(past.compare(.isInThePast))
95 | }
96 | // .isSameDay
97 | func testDateSameDayComparison() throws {
98 | guard let future = now.offset(.day, value: 1),
99 | let past = now.offset(.day, value: -1),
100 | let today = now.adjust(for: .startOfDay) else {
101 | return
102 | }
103 | XCTAssertTrue(!future.compare(.isSameDay(as: now)))
104 | XCTAssertTrue(!past.compare(.isSameDay(as: now)))
105 | XCTAssertTrue(today.compare(.isSameDay(as: now)))
106 | }
107 | // .isSameWeek
108 | func testDateSameWeekComparison() throws {
109 | guard let future = now.offset(.week, value: 1),
110 | let past = now.offset(.week, value: -1),
111 | let today = now.adjust(for: .startOfDay) else {
112 | return
113 | }
114 | XCTAssertTrue(!future.compare(.isSameWeek(as: now)))
115 | XCTAssertTrue(!past.compare(.isSameWeek(as: now)))
116 | XCTAssertTrue(today.compare(.isSameWeek(as: now)))
117 | }
118 | // .isSameMonth
119 | func testDateSameMonthComparison() throws {
120 | guard let future = now.offset(.month, value: 1),
121 | let past = now.offset(.month, value: -1),
122 | let today = now.adjust(for: .startOfDay) else {
123 | return
124 | }
125 | XCTAssertTrue(!future.compare(.isSameMonth(as: now)))
126 | XCTAssertTrue(!past.compare(.isSameMonth(as: now)))
127 | XCTAssertTrue(today.compare(.isSameMonth(as: now)))
128 |
129 | }
130 | // .isSameYear
131 | func testDateSameYearComparison() throws {
132 | guard let future = now.offset(.year, value: 1),
133 | let past = now.offset(.year, value: -1),
134 | let today = now.adjust(for: .startOfDay) else {
135 | return
136 | }
137 | XCTAssertTrue(!future.compare(.isSameYear(as: now)))
138 | XCTAssertTrue(!past.compare(.isSameYear(as: now)))
139 | XCTAssertTrue(today.compare(.isSameYear(as: now)))
140 | }
141 | // .isEarlierThan
142 | func testDateEarlierThanComparison() throws {
143 | guard let future = now.offset(.minute, value: 1),
144 | let past = now.offset(.minute, value: -1) else {
145 | return
146 | }
147 | XCTAssertTrue(!future.compare(.isEarlier(than: now)))
148 | XCTAssertTrue(past.compare(.isEarlier(than: now)))
149 | }
150 | // .isLaterThan
151 | func testDateLaterThanComparison() throws {
152 | guard let future = now.offset(.minute, value: 1),
153 | let past = now.offset(.minute, value: -1) else {
154 | return
155 | }
156 | XCTAssertTrue(future.compare(.isLater(than: now)))
157 | XCTAssertTrue(!past.compare(.isLater(than: now)))
158 | }
159 | // .isWeekend
160 | func testDateIsWeekendComparison() throws {
161 | guard let weekday = Calendar.current.date(from: DateComponents(year: 2021, month: 12, day: 15)),
162 | let weekend = Calendar.current.date(from: DateComponents(year: 2021, month: 12, day: 18)) else {
163 | return
164 | }
165 | XCTAssertTrue(!weekday.compare(.isWeekend))
166 | XCTAssertTrue(weekend.compare(.isWeekend))
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/Tests/DateHelperTests/DateFromStringTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DateHelper
3 |
4 | final class DateHelperFromStringTests: XCTestCase {
5 |
6 | // date from isoYear string
7 | func testDateFromIsoYearString() throws {
8 | let date = Date(fromString: "2009", format: .isoYear)
9 | let comparison = Calendar.current.date(from: DateComponents(year: 2009))
10 | XCTAssertEqual(date, comparison)
11 | }
12 |
13 | // date from isoYearMonth string
14 | func testDateFromIsoYearMonthString() throws {
15 | let date = Date(fromString: "2009-08", format: .isoYearMonth)
16 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 08))
17 | XCTAssertEqual(date, comparison)
18 | }
19 |
20 | // date from isoYearMonth string
21 | func testDateFromIsoDateString() throws {
22 | let date = Date(fromString: "2009-08-11", format: .isoDate)
23 | let comparison = Calendar.current.date(from: DateComponents(year: 2009, month: 08, day: 11))
24 | XCTAssertEqual(date, comparison)
25 | }
26 |
27 | // date from isoDateTime string
28 | func testDateFromIsoDateTimeString() throws {
29 | let date = Date(fromString: "2009-08-11T06:30:03-05:00", format: .isoDateTime)
30 | let offset = -5
31 | let timeZone = TimeZone(secondsFromGMT: 60 * 60 * offset)
32 | let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2009, month: 08, day: 11, hour: 06, minute: 30, second: 03))
33 | XCTAssertEqual(date, comparison)
34 | }
35 |
36 | // date from isoDateTimeFull string
37 | func testDateFromIsoDateTimeFullString() throws {
38 | let date = Date(fromString: "2009-08-11T06:30:03.161-05:00", format: .isoDateTimeFull)
39 | let offset = -5
40 | let nanoseconds = 161*1000000
41 | let timeZone = TimeZone(secondsFromGMT: 60 * 60 * offset)
42 | let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2009, month: 08, day: 11, hour: 06, minute: 30, second: 03, nanosecond: nanoseconds))
43 | XCTAssertEqual(date, comparison)
44 | }
45 |
46 | // date from dotNet string
47 | func testDateFromDotNetString() throws {
48 | let date = Date(fromString: "/Date(1260123281843)/", format: .dotNet)
49 | let timeZone = TimeZone(secondsFromGMT: 0)
50 | let nanoseconds = 843*1000000
51 | let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 41, nanosecond: nanoseconds))
52 | XCTAssertEqual(date?.timeIntervalSince1970, comparison?.timeIntervalSince1970)
53 | }
54 |
55 | // date from rss string
56 | func testDateFromRSSString() throws {
57 | guard let date = Date(fromString: "Fri, 09 Sep 2011 15:26:08 +0200", format: .rss),
58 | let timeZone = TimeZone(secondsFromGMT: 60 * 60 * 2),
59 | let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2011, month: 09, day: 09, hour: 15, minute: 26, second: 08)) else {
60 | return
61 | }
62 | XCTAssertEqual(date, comparison)
63 | }
64 |
65 | // date from altRSS string
66 | func testDateFromAltRSSString() throws {
67 | guard let date = Date(fromString: "09 Sep 2011 15:26:08 +0200", format: .altRSS),
68 | let timeZone = TimeZone(secondsFromGMT: 60 * 60 * 2),
69 | let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2011, month: 09, day: 09, hour: 15, minute: 26, second: 08)) else {
70 | return
71 | }
72 | XCTAssertEqual(date, comparison)
73 | }
74 |
75 | // date from httpHeader string
76 | func testDateFromHttpHeaderString() throws {
77 | guard let date = Date(fromString: "Wed, 01 03 2017 06:43:19 -0500", format: .httpHeader),
78 | let timeZone = TimeZone(secondsFromGMT: 60 * 60 * (-5)),
79 | let comparison = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2017, month: 03, day: 01, hour: 06, minute: 43, second: 19)) else {
80 | return
81 | }
82 | XCTAssertEqual(date, comparison)
83 | }
84 |
85 | // date from custom format string
86 | func testDateFromCustomFormatString() throws {
87 | guard let date = Date(fromString: "16 July 1972 6:12:00", format: .custom("dd MMM yyyy HH:mm:ss")),
88 | let comparison = Calendar.current.date(from: DateComponents(year: 1972, month: 07, day: 16, hour: 06, minute: 12, second: 00)) else {
89 | return
90 | }
91 | XCTAssertEqual(date, comparison)
92 | }
93 |
94 | // date from string using date detector
95 | func testDateFromDetectorString() throws {
96 | let date = Date(detectFromString: "Tomorrow at 5:30 PM")!
97 | let calendar = Calendar.current
98 | let today = Date()
99 | let tomorrow = calendar.date(byAdding: .day, value: 1, to: today)!
100 | let components = calendar.dateComponents(Date.componentFlags(), from: tomorrow)
101 | let comparison = calendar.date(from: DateComponents(timeZone: TimeZone.current, year: components.year, month: components.month, day: components.day, hour: 17, minute: 30, second: 0))
102 | XCTAssertEqual(date, comparison)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Tests/DateHelperTests/MiscelaniousTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DateHelper
3 |
4 | final class DateHelperMiscelaniousTests: XCTestCase {
5 |
6 | // extract date components
7 | func testDateComponents() throws {
8 | guard
9 | let timeZone = TimeZone(secondsFromGMT: 60 * 60 * (-5)),
10 | let date = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 11)),
11 | let second = date.component(.second),
12 | let minute = date.component(.minute),
13 | let hour = date.component(.hour),
14 | let day = date.component(.day),
15 | let weekday = date.component(.weekday),
16 | let weekdayOrdinal = date.component(.weekdayOrdinal),
17 | let month = date.component(.month),
18 | let year = date.component(.year) else { return }
19 | XCTAssertEqual(second, 11)
20 | XCTAssertEqual(minute, 14)
21 | XCTAssertEqual(hour, 18)
22 | XCTAssertEqual(day, 06)
23 | XCTAssertEqual(weekday, 1)
24 | XCTAssertEqual(weekdayOrdinal, 1)
25 | XCTAssertEqual(month, 12)
26 | XCTAssertEqual(year, 2009)
27 | print("date: \(date)")
28 | print("second: \(second)")
29 | print("minute: \(minute)")
30 | print("hour: \(hour)")
31 | print("day: \(day)")
32 | print("weekday: \(weekday)")
33 | print("weekdayOrdinal: \(weekdayOrdinal)")
34 | print("month: \(month)")
35 | print("year: \(year)")
36 | }
37 | // extract date items
38 | func testDateItemExtraction() throws {
39 | guard
40 | let date = Calendar.current.date(from: DateComponents(year: 2021, month: 12, day: 17)),
41 | let numberOfDaysInMonth = date.numberOfDaysInMonth(),
42 | let firstDayOfWeek = date.firstDayOfWeek(),
43 | let lastDayOfWeek = date.lastDayOfWeek() else { return }
44 | XCTAssertEqual(numberOfDaysInMonth, 31)
45 | XCTAssertEqual(firstDayOfWeek, 12)
46 | XCTAssertEqual(lastDayOfWeek, 19)
47 | print("date: \(date)")
48 | print("numberOfDaysInMonth: \(numberOfDaysInMonth)")
49 | print("firstDayOfWeek: \(firstDayOfWeek)")
50 | print("lastDayOfWeek: \(lastDayOfWeek)")
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/DateHelperTests/StringFromDateTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DateHelper
3 |
4 | final class DateHelperToStringTests: XCTestCase {
5 |
6 | // date to string with style
7 | func testToStringWithStyle() throws {
8 | guard let timeZone = TimeZone(secondsFromGMT: 60 * 60 * (-5)),
9 | let date = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2017, month: 03, day: 01, hour: 06, minute: 43, second: 19)) else {
10 | return
11 | }
12 | // short
13 | if let string = date.toString(style: .short) {
14 | XCTAssertEqual(string, "3/1/17, 6:43 AM")
15 | }
16 | // medium
17 | if let string = date.toString(style: .medium) {
18 | XCTAssertEqual(string, "Mar 1, 2017 at 6:43:19 AM")
19 | }
20 | // long
21 | if let string = date.toString(style: .long) {
22 | XCTAssertEqual(string, "March 1, 2017 at 6:43:19 AM EST")
23 | }
24 | // full
25 | if let string = date.toString(style: .full) {
26 | XCTAssertEqual(string, "Wednesday, March 1, 2017 at 6:43:19 AM Eastern Standard Time")
27 | }
28 | // ordinalDay
29 | if let string = date.toString(style: .ordinalDay) {
30 | XCTAssertEqual(string, "1st")
31 | }
32 | // weekday
33 | if let string = date.toString(style: .weekday) {
34 | XCTAssertEqual(string, "Wednesday")
35 | }
36 | // shortWeekday
37 | if let string = date.toString(style: .shortWeekday) {
38 | XCTAssertEqual(string, "Wed")
39 | }
40 | // veryShortWeekday
41 | if let string = date.toString(style: .veryShortWeekday) {
42 | XCTAssertEqual(string, "W")
43 | }
44 | // month
45 | if let string = date.toString(style: .month) {
46 | XCTAssertEqual(string, "April")
47 | }
48 | // shortMonth
49 | if let string = date.toString(style: .shortMonth) {
50 | XCTAssertEqual(string, "Apr")
51 | }
52 | // veryShortMonth
53 | if let string = date.toString(style: .veryShortMonth) {
54 | XCTAssertEqual(string, "A")
55 | }
56 | }
57 | // date to string with format
58 | func testToStringWithFormat() throws {
59 | guard let timeZone = TimeZone(secondsFromGMT: 60 * 60 * (-5)),
60 | let date = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2017, month: 03, day: 01, hour: 06, minute: 43, second: 19)),
61 | let isoYear = date.toString(format: .isoYear),
62 | let isoYearMonth = date.toString(format: .isoYearMonth),
63 | let isoDate = date.toString(format: .isoDate),
64 | let isoDateTime = date.toString(format: .isoDateTime),
65 | let isoDateTimeFull = date.toString(format: .isoDateTimeFull),
66 | let dotNet = date.toString(format: .dotNet),
67 | let rss = date.toString(format: .rss),
68 | let altRSS = date.toString(format: .altRSS),
69 | let httpHeader = date.toString(format: .httpHeader),
70 | let custom1 = date.toString(format: .custom("MMM d, yyyy")),
71 | let custom2 = date.toString(format: .custom("h:mm a")),
72 | let custom3 = date.toString(format: .custom("E MMM d"))
73 | else {
74 | return
75 | }
76 | XCTAssertEqual(isoYear, "2017")
77 | XCTAssertEqual(isoYearMonth, "2017-03")
78 | XCTAssertEqual(isoDate, "2017-03-01")
79 | XCTAssertEqual(isoDateTime, "2017-03-01T06:43:19-05:00")
80 | XCTAssertEqual(isoDateTimeFull, "2017-03-01T06:43:19.000-05:00")
81 | XCTAssertEqual(dotNet, "/Date(-51488368599000.000000)/")
82 | XCTAssertEqual(rss, "Wed, 1 Mar 2017 06:43:19 -0500")
83 | XCTAssertEqual(altRSS, "1 Mar 2017 06:43:19 -0500")
84 | XCTAssertEqual(httpHeader, "Wed, 01 03 2017 06:43:19 -0500")
85 | XCTAssertEqual(custom1, "Mar 1, 2017")
86 | XCTAssertEqual(custom2, "6:43 AM")
87 | XCTAssertEqual(custom3, "Wed Mar 1")
88 |
89 | }
90 | // date to string with date and time style
91 | func testToStringWithDateAndTimeStyle() throws {
92 | guard let timeZone = TimeZone(secondsFromGMT: 60 * 60 * (-5)),
93 | let date = Calendar.current.date(from: DateComponents(timeZone: timeZone, year: 2017, month: 03, day: 01, hour: 06, minute: 43, second: 19)) else {
94 | return
95 | }
96 | // dateStyle: .none, timeStyle: .short
97 | if let string = date.toString(dateStyle: .none, timeStyle: .short) {
98 | XCTAssertEqual(string, "6:43 AM")
99 | }
100 | // dateStyle: .short, timeStyle: .none
101 | if let string = date.toString(dateStyle: .short, timeStyle: .none) {
102 | XCTAssertEqual(string, "3/1/17")
103 | }
104 | // dateStyle: dateStyle: .short, timeStyle: .short
105 | if let string = date.toString(dateStyle: .short, timeStyle: .short) {
106 | XCTAssertEqual(string, "3/1/17, 6:43 AM")
107 | }
108 | // dateStyle: .medium, timeStyle: .medium
109 | if let string = date.toString(dateStyle: .medium, timeStyle: .medium) {
110 | XCTAssertEqual(string, "Mar 1, 2017 at 6:43:19 AM")
111 | }
112 | // dateStyle: .long, timeStyle: .long
113 | if let string = date.toString(dateStyle: .long, timeStyle: .long) {
114 | XCTAssertEqual(string, "March 1, 2017 at 6:43:19 AM EST")
115 | }
116 | // dateStyle: .full, timeStyle: .full
117 | if let string = date.toString(dateStyle: .full, timeStyle: .full) {
118 | XCTAssertEqual(string, "Wednesday, March 1, 2017 at 6:43:19 AM Eastern Standard Time")
119 | }
120 | }
121 |
122 | // date to string with date and time style
123 | func testToStringWithRelativeTime() throws {
124 | guard let date4years = Date().offset(.year, value: -4),
125 | let justNow = Date().offset(.second, value: -8) else {
126 | return
127 | }
128 | // toStringWithRelativeTime default behaviour
129 | if let string = date4years.toStringWithRelativeTime() {
130 | XCTAssertEqual(string, "4 years ago")
131 | }
132 | // toStringWithRelativeTime with custom year string
133 | if let string = date4years.toStringWithRelativeTime(customStrings: [.yearsPast: "%.f ages ago in a galaxy far away"]) {
134 | XCTAssertEqual(string, "4 ages ago in a galaxy far away")
135 | }
136 | // toStringWithRelativeTime with custom just now string
137 | if let string = justNow.toStringWithRelativeTime(customStrings: [.nowPast: "just happened"]) {
138 | XCTAssertEqual(string, "just happened")
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Tests/DateHelperTests/TimeSinceTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DateHelper
3 |
4 | final class DateHelperTimeSinceTests: XCTestCase {
5 |
6 | // time since in second
7 | func testDateTimeSinceInSecond() throws {
8 | guard
9 | let date = Calendar.current.date(from: DateComponents(year: 2009, month: 12, day: 06, hour: 18, minute: 14, second: 11)),
10 | let minus30Sec = date.offset(.second, value: -30),
11 | let minus30SecSince = date.since(minus30Sec, in: .second),
12 | let minus2Hr = date.offset(.hour, value: -2),
13 | let minus2HrSinceMin = date.since(minus2Hr, in: .minute),
14 | let minus2HrSinceHr = date.since(minus2Hr, in: .hour),
15 | let minus1Day = date.offset(.day, value: -1),
16 | let minus1DaySince = date.since(minus1Day, in: .day),
17 | let minus1Week = date.offset(.week, value: -1),
18 | let minus1WeekSince = date.since(minus1Week, in: .week),
19 | let plus1Week = date.offset(.week, value: 1),
20 | let plus1WeekSince = date.since(plus1Week, in: .week),
21 | let minus2Week = date.offset(.week, value: -2),
22 | let minus2WeekSince = date.since(minus2Week, in: .weekdayOrdinal),
23 | let minus1Month = date.offset(.month, value: -1),
24 | let minus1MonthSince = date.since(minus1Month, in: .month),
25 | let minus1Year = date.offset(.year, value: -1),
26 | let minus1YearSince = date.since(minus1Year, in: .year)
27 | else {
28 | return
29 | }
30 | print("date: \(date.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
31 | print("minus30SecSince: \(minus30Sec.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
32 | print("minus2Hr: \(minus2Hr.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
33 | print("minus1Day: \(minus1Day.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
34 | print("minus1Week: \(minus1Week.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
35 | print("plus1Week: \(plus1Week.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
36 | print("minus2Week: \(minus2Week.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
37 | print("minus1Month: \(minus1Month.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
38 | print("minus1Year: \(minus1Year.toString(format: .custom("yyyy-MM-dd hh-mm-ss"))!)")
39 | XCTAssertEqual(minus30SecSince, 30)
40 | XCTAssertEqual(minus2HrSinceMin, 120)
41 | XCTAssertEqual(minus2HrSinceHr, 2)
42 | XCTAssertEqual(minus1DaySince, 1)
43 | XCTAssertEqual(minus1WeekSince, 1)
44 | XCTAssertEqual(plus1WeekSince, -1)
45 | XCTAssertEqual(minus2WeekSince, 2)
46 | XCTAssertEqual(minus1MonthSince, 1)
47 | XCTAssertEqual(minus1YearSince, 1)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/melvitax/DateHelper/573c18a6a8a1394da599ff90da966c7afe92dd71/logo.png
--------------------------------------------------------------------------------
/logo.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/melvitax/DateHelper/573c18a6a8a1394da599ff90da966c7afe92dd71/logo.sketch
--------------------------------------------------------------------------------