Skip to content

Commit 1c82c66

Browse files
committed
Always extract new components
When creating a new fixed value from components, always extract the components via the Calendar to use as the Fixed value's model. This way any missing-but-not-required (ie, lenient) units will be filled in. Fixes #82
1 parent badd47e commit 1c82c66

File tree

4 files changed

+26
-18
lines changed

4 files changed

+26
-18
lines changed

Sources/Time/4-Fixed Values/Fixed.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ public struct Fixed<Granularity: Unit & LTOEEra>: Sendable {
8888
/// - Parameter region: The `Region` in which to interpret the date components
8989
/// - Parameter strictDateComponents: The `DateComponents` describing the desired calendrical date
9090
public init(region: Region, strictDateComponents: DateComponents) throws {
91-
let date = try region.calendar.exactDate(from: strictDateComponents,
92-
in: region.timeZone,
93-
matching: Self.representedComponents)
94-
self.init(region: region, instant: Instant(date: date), components: strictDateComponents)
91+
let (date, actualComponents) = try region.calendar.exactDate(from: strictDateComponents,
92+
in: region.timeZone,
93+
matching: Self.representedComponents)
94+
self.init(region: region, instant: Instant(date: date), components: actualComponents)
9595
}
9696

9797
}

Sources/Time/Internals/Time+Calendar.swift

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ extension Calendar {
2525
return [.era]
2626
}
2727

28-
internal func exactDate(from components: DateComponents, in timeZone: TimeZone, matching: Set<Calendar.Component>) throws -> Date {
29-
var restricted = try components.requireAndRestrict(to: matching, lenient: self.lenientUnitsForFixedTimePeriods)
30-
restricted.timeZone = timeZone
28+
internal func exactDate(from components: DateComponents, in timeZone: TimeZone, matching: Set<Calendar.Component>) throws -> (Date, DateComponents) {
29+
var restrictedComponents = try components.requireAndRestrict(to: matching, lenient: self.lenientUnitsForFixedTimePeriods)
30+
restrictedComponents.timeZone = timeZone
3131

32-
guard let proposed = self.date(from: restricted) else {
33-
let r = Region(calendar: self, timeZone: self.timeZone, locale: self.locale ?? .current)
34-
throw TimeError.invalidDateComponents(restricted, in: r)
32+
guard let proposedDate = self.date(from: restrictedComponents) else {
33+
let r = Region(calendar: self, timeZone: timeZone, locale: self.locale ?? .current)
34+
throw TimeError.invalidDateComponents(restrictedComponents, in: r)
3535
}
3636

37-
let proposedComponents = self.dateComponents(in: timeZone, from: proposed)
37+
let proposedComponents = self.dateComponents(in: timeZone, from: proposedDate)
3838

39-
if isEraRelevant == false && restricted.era == nil {
40-
restricted.era = proposedComponents.era
39+
if isEraRelevant == false && restrictedComponents.era == nil {
40+
restrictedComponents.era = proposedComponents.era
4141
}
4242

4343
for unit in matching {
@@ -48,13 +48,15 @@ extension Calendar {
4848
// appears to be restricted to within about 24,000 nanoseconds
4949
if unit == .nanosecond { continue }
5050

51-
guard proposedComponents.value(for: unit) == restricted.value(for: unit) else {
51+
guard proposedComponents.value(for: unit) == restrictedComponents.value(for: unit) else {
5252
let r = Region(calendar: self, timeZone: self.timeZone, locale: self.locale ?? .current)
53-
throw TimeError.invalidDateComponents(restricted, in: r)
53+
throw TimeError.invalidDateComponents(restrictedComponents, in: r)
5454
}
5555
}
5656

57-
return proposed
57+
let actualComponents = try! proposedComponents.requireAndRestrict(to: matching, lenient: [])
58+
59+
return (proposedDate, actualComponents)
5860
}
5961

6062
internal func range(of unit: Calendar.Component, containing date: Date) -> Range<Date> {

Tests/TimeTests/FixedTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ class FixedTests: XCTestCase {
1616
("testAddingComponents", testAddingComponents),
1717
]
1818

19-
func testInitializingGregorianDateWithoutEraSucceeds() {
20-
XCTAssertNoThrow(try Fixed<Day>(region: .posix, year: 1970, month: 4, day: 1))
19+
func testInitializingGregorianDateWithoutEraSucceeds() throws {
20+
let day = try Fixed<Day>(region: .posix, year: 1970, month: 4, day: 1)
21+
XCTAssertEqual(day.era, 1)
2122
}
2223

2324
func testInitializingGregorianDateWithEraSucceeds() {

Tests/TimeTests/ReportedBugs.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,9 @@ class ReportedBugs: XCTestCase {
6464
XCTAssertEqual(formattedGregorian, "1")
6565
XCTAssertEqual(formattedISO8601, "7")
6666
}
67+
68+
func testValuesWithoutErasStillHaveThem_GH82() throws {
69+
let day = try Fixed(region: .posix, year: 2024, month: 4, day: 7)
70+
XCTAssertEqual(day.era, 1)
71+
}
6772
}

0 commit comments

Comments
 (0)