Skip to content

.dateInterval(of:for:) returns the wrong range for Gregorian eras #407

Open
@davedelong

Description

@davedelong

Below is a minimal reproduction case:

var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!

let eraComponents = DateComponents(era: 1)

let anyEraDate = calendar.date(from: eraComponents)!
calendar.component(.era, from: anyEraDate) == 1     // TRUE
print(anyEraDate.description)       // 0001-01-01 00:00:00 +0000

let eraRange = calendar.dateInterval(of: .era, for: anyEraDate)!
calendar.component(.era, from: eraRange.start) == 1  // FALSE
print(eraRange.start.description)   // -139365-04-28 21:34:56 +0000
print(eraRange.end.description)     // 0001-01-03 00:00:00 +0000

The fundamental problem is that calendar.date(from: eraComponents) is using a formula based on Julian days for converting it to a timestamp. DateComponents(era: 1) becomes Julian Day 1721423, which is right on the BC/AD boundary, as expected. That Date has a timeIntervalSinceReferenceDate of -63114076800.0.

However, .dateInterval(of:for:) checks for dates that are close to the era boundary, and returns a hard-coded boundary:

    ...
    case .era:
        if time < -63113904000.0 {
            return DateInterval(start: Date(timeIntervalSinceReferenceDate: -63113904000.0 - inf_ti), duration: inf_ti)
        } else {
            ...

That time interval of -63113904000.0 pegs the start of the era on 0001-01-03, two days after the previously-returned Date.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions