Skip to content

Commit be9695a

Browse files
authored
Merge pull request #7 from 1stdibs/update-date-property-options
New Date properties
2 parents 8acd1fd + f8c02c3 commit be9695a

File tree

5 files changed

+315
-10
lines changed

5 files changed

+315
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
Changes in reverse chronological order
66
<hr>
77

8+
### Version 1.x - xxx 2020
9+
10+
- Added two new date property types for more flexible handling of dates:
11+
- `WPDate8601` - specify `ISO8601DateFormatter.Options` flags for ISO8601 date variants that don't match the default handling
12+
- `WPDateFmt` - specify your own `DateFormatter` format string to handle pretty much any date
13+
814
### Version 1.2 - 17 Jan 2020
915

1016
- Added property wrappers specific to most of the property types provided for less redundant property declarations.

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ let modelStr =
2323
// A model is defined like this - Objective C compatible with property wrappers (requires Swift 5.1)
2424
class Customer: WrapModel {
2525

26-
@StrProperty("last-name") var lastName: String
27-
@StrProperty("first-name") var firstName: String
28-
@DateProperty("most-recent-purchase", dateType: .mdySlashes) var lastPurchase: Date?
29-
@IntProperty("cust-no") var custNumber: Int
26+
@StrProperty("last-name") var lastName
27+
@StrProperty("first-name") var firstName
28+
@DateProperty("most-recent-purchase", dateType: .mdySlashes) var lastPurchase
29+
@IntProperty("cust-no") var custNumber
3030
}
3131

3232
// It's possible to use WrapModel with Swift 4.2, but you must declare private properties
@@ -345,11 +345,11 @@ for `Wrapmodel` subclass types either alone or in a collection
345345

346346
<a name="pt-dates"></a>**Dates**
347347

348-
WPDate is initialized with an enum describing the date encoding type.
349-
350348
| Short name | Data type | Long name | Default value | Property Wrapper |
351349
|---|---|---|---|---|
352350
| `WPDate` | Date? | WrapPropertyDate | nil | [Mut]DateProperty |
351+
| `WPDateFmt` | Date? | WrapPropertyDateFormatted | nil | [Mut]DateFmtProperty |
352+
| `WPDate8601` | Date? | WrapPropertyDateISO8601 | nil | [Mut]Date8601Property |
353353

354354
<a name="pt-arrays"></a>**Arrays**
355355

@@ -396,9 +396,9 @@ There are three different property wrappers for `WrapConvertibleEnum` conforming
396396

397397
### <a name="dates"></a>Date properties
398398

399-
`WrapPropertyDate` (`WPDate`) handles several different formats of dates specified via an enum. Incoming translation from string attempts to decode from the specified date type first, but then also tries all the other types it knows about. Conversion back to string always uses the specified date type.
399+
`WrapPropertyDate` (`WPDate`) handles several common formats of dates specified via an enum. Incoming translation from string attempts to decode from the specified date type first, but then also tries all the other types it knows about. Conversion back to string always uses the specified date type. This is the most forgiving date property type.
400400

401-
Date types currently supported are:
401+
Date types currently supported by `WPDate` are:
402402
```
403403
dibs // 2017-02-05T17:03:13.000-03:00
404404
secondary // Tue Jun 3 2008 11:05:30 GMT
@@ -412,6 +412,10 @@ Date types currently supported are:
412412
dmyDashes // 30-02-2017
413413
```
414414

415+
`WrapPropertyDateFormatted` (`WPDateFmt`) is initialized with a `DateFormatter` format string, so it can handle almost any date formatted as a string in a consistent way, but this also makes it fairly inflexible since the date string must closely match the date format string.
416+
417+
`WPDate8601` is initialized with `ISO8601DateFormatter.Options` flags. The options used along with `ISO8601DateFormatter` cover most of the variations used when working with ISO 8601 formatted date strings.
418+
415419
### <a name="embedded-submodel-arrays"></a>Arrays of Embedded Models
416420

417421
In some cases, submodels in an array are buried inside one or more subdictionaries whose only purpose is to wrap the submodel. This often happens when using GraphQL, where models can come wrapped in a "node" dictionary like this:

Tests/WrapModelTests.swift

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ class WrapModelTests: XCTestCase {
5858
@MutDateProperty("creationDate", dateType: .iso8601) var creationDate: Date?
5959
@MutDateProperty("modDate", dateType: .secondary) var modificationDate: Date?
6060
@MutDateProperty("releaseDate", dateType: .dibs) var releaseDate: Date?
61+
@Date8601Property("isoDateStandard1", options:[.withInternetDateTime, .withFractionalSeconds]) var isoDate1
62+
@Date8601Property("isoDateStandard2", options:[.withFullDate, .withSpaceBetweenDateAndTime, .withFullTime, .withTimeZone]) var isoDate2
63+
@Date8601Property("isoDateStandard3", options:[.withInternetDateTime]) var isoDate3
64+
@Date8601Property("isoDateStandard4", options:[.withYear, .withMonth, .withDay, .withSpaceBetweenDateAndTime, .withTime]) var isoDate4
65+
@Date8601Property("isoDateStandard5", options:[.withFullDate]) var isoDate5
66+
@Date8601Property("isoDateStandard6", options:[.withYear, .withMonth, .withDay]) var isoDate6
67+
@DateFmtProperty("fmtDate1", dateFormatString: "yyyyMMdd") var fmtDate1
68+
@DateFmtProperty("fmtDate2", dateFormatString: "yyyy-MM-dd") var fmtDate2
69+
@DateFmtProperty("fmtDate3", dateFormatString: "MM/dd/yyyy") var fmtDate3
70+
@DateFmtProperty("fmtDate4", dateFormatString: "MM/dd/yyyy HHmm") var fmtDate4
71+
@DateFmtProperty("fmtDate5", dateFormatString: "MM/dd/yyyy HH:mm:ss") var fmtDate5
72+
@DateFmtProperty("fmtDate6", dateFormatString: "EEE MMM dd yyyy HH:mm:ss z") var fmtDate6
6173
@EnumProperty("rewardLevel", defaultEnum: .bronze) var rewardLevel: RewardLevel
6274
@EnumProperty("oldRewardLevel", defaultEnum: .bronze) var oldRewardLevel: RewardLevel
6375
@OptEnumProperty("prevRewardLevel") var prevRewardLevel: RewardLevel?
@@ -113,6 +125,18 @@ class WrapModelTests: XCTestCase {
113125
"creationDate": "2016-11-01T21:14:33Z",
114126
"modDate": "Tue Jun 3 2008 11:05:30 GMT",
115127
"releaseDate": "2017-02-05T17:03:13.000-03:00",
128+
"isoDateStandard1": "2020-04-24T16:18:53.000-5",
129+
"isoDateStandard2": "2020-04-24 16:18:53-5",
130+
"isoDateStandard3": "2020-04-24T21:18:53Z",
131+
"isoDateStandard4": "20200424 211853",
132+
"isoDateStandard5": "2020-04-24",
133+
"isoDateStandard6": "20200424",
134+
"fmtDate1": "20200424",
135+
"fmtDate2": "2020-04-24",
136+
"fmtDate3": "04/24/2020",
137+
"fmtDate4": "04/24/2020 1618",
138+
"fmtDate5": "04/24/2020 16:18:53",
139+
"fmtDate6": "Fri Apr 24 2020 11:05:30 GMT",
116140
"rewardLevel": "Gold",
117141
"tempRewardLevel": "Platinum",
118142
"commInterval": 4,
@@ -388,6 +412,76 @@ class WrapModelTests: XCTestCase {
388412
}
389413
}
390414

415+
func testIOS8601Dates() throws {
416+
417+
let iso1 = mWyatt.isoDate1
418+
let iso2 = mWyatt.isoDate2
419+
let iso3 = mWyatt.isoDate3
420+
let iso4 = mWyatt.isoDate4
421+
let iso5 = mWyatt.isoDate5
422+
let iso6 = mWyatt.isoDate6
423+
424+
XCTAssertNotNil(iso1)
425+
XCTAssertNotNil(iso2)
426+
XCTAssertNotNil(iso3)
427+
XCTAssertNotNil(iso4)
428+
XCTAssertNotNil(iso5)
429+
XCTAssertNotNil(iso6)
430+
431+
let s1 = iso1?.ymdStr()
432+
let s2 = iso2?.ymdStr()
433+
let s3 = iso3?.ymdStr()
434+
let s4 = iso4?.ymdStr()
435+
let s5 = iso5?.ymdStr()
436+
let s6 = iso6?.ymdStr()
437+
438+
// Specified with no time, so should be equal
439+
XCTAssertEqual(iso1, iso2)
440+
XCTAssertEqual(iso2, iso3)
441+
XCTAssertEqual(iso3, iso4)
442+
443+
// ymd strings should all be equal
444+
XCTAssertEqual(s1, s2)
445+
XCTAssertEqual(s2, s3)
446+
XCTAssertEqual(s3, s4)
447+
XCTAssertEqual(s4, s5)
448+
XCTAssertEqual(s5, s6)
449+
}
450+
451+
func testFormattedDates() throws {
452+
let fd1 = mWyatt.fmtDate1
453+
let fd2 = mWyatt.fmtDate2
454+
let fd3 = mWyatt.fmtDate3
455+
let fd4 = mWyatt.fmtDate4
456+
let fd5 = mWyatt.fmtDate5
457+
let fd6 = mWyatt.fmtDate6
458+
459+
XCTAssertNotNil(fd1)
460+
XCTAssertNotNil(fd2)
461+
XCTAssertNotNil(fd3)
462+
XCTAssertNotNil(fd4)
463+
XCTAssertNotNil(fd5)
464+
XCTAssertNotNil(fd6)
465+
466+
let s1 = fd1?.ymdStr()
467+
let s2 = fd2?.ymdStr()
468+
let s3 = fd3?.ymdStr()
469+
let s4 = fd4?.ymdStr()
470+
let s5 = fd5?.ymdStr()
471+
let s6 = fd6?.ymdStr()
472+
473+
// Specified with no time, so should be equal
474+
XCTAssertEqual(fd1, fd2)
475+
XCTAssertEqual(fd2, fd3)
476+
477+
// ymd strings should all be equal
478+
XCTAssertEqual(s1, s2)
479+
XCTAssertEqual(s2, s3)
480+
XCTAssertEqual(s3, s4)
481+
XCTAssertEqual(s4, s5)
482+
XCTAssertEqual(s5, s6)
483+
}
484+
391485
func testSerializationMode() throws {
392486

393487
// Test initial value
@@ -1103,3 +1197,14 @@ func stringFromKey(_ key:String, in dict:[AnyHashable:Any], file: StaticString =
11031197
return valFromKey(key, in:dict, file: file, line: line) ?? ""
11041198
}
11051199

1200+
extension Date {
1201+
fileprivate func ymdStr() -> String {
1202+
let utc = TimeZone(secondsFromGMT: 0)!
1203+
let dc = Calendar(identifier: .gregorian).dateComponents(in: utc, from: self)
1204+
let y = dc.year ?? 2000
1205+
let m = dc.month ?? 1
1206+
let d = dc.day ?? 1
1207+
let asInt = y*10_000 + m * 100 + d
1208+
return "\(asInt)"
1209+
}
1210+
}

0 commit comments

Comments
 (0)