@@ -64,15 +64,20 @@ enum ResolvedDateComponents {
6464 // Pick the year field between yearForWeekOfYear and year and resovles era
6565 static func yearMonth( forDateComponent components: DateComponents ) -> ( year: Int , month: Int ) {
6666 var rawYear : Int
67+ // Don't adjust for era if week is also specified
68+ var adjustEra = true
6769 if let yearForWeekOfYear = components. yearForWeekOfYear {
70+ if components. weekOfYear != nil {
71+ adjustEra = false
72+ }
6873 rawYear = yearForWeekOfYear
6974 } else if let year = components. year {
7075 rawYear = year
7176 } else {
7277 rawYear = 1
7378 }
7479
75- if components. era == 0 /* BC */{
80+ if adjustEra && components. era == 0 /* BC */{
7681 rawYear = 1 - rawYear
7782 }
7883
@@ -153,6 +158,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
153158 let gregorianStartYear : Int
154159 let gregorianStartDate : Date
155160
161+ let inf_ti : TimeInterval = 4398046511104.0
162+
156163 // Only respects Gregorian identifier
157164 init ( identifier: Calendar . Identifier , timeZone: TimeZone ? , locale: Locale ? , firstWeekday: Int ? , minimumDaysInFirstWeek: Int ? , gregorianStartDate: Date ? ) {
158165
@@ -503,7 +510,6 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
503510 func start( of unit: Calendar . Component , at: Date ) -> Date ? {
504511 let capped = at. capped
505512
506- let inf_ti : TimeInterval = 4398046511104.0
507513 let time = capped. timeIntervalSinceReferenceDate
508514
509515 var effectiveUnit = unit
@@ -1047,7 +1053,89 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
10471053 }
10481054
10491055 func dateInterval( of component: Calendar . Component , for date: Date ) -> DateInterval ? {
1050- fatalError ( )
1056+
1057+ let capped = date. capped
1058+ let time = capped. timeIntervalSinceReferenceDate
1059+ var effectiveUnit = component
1060+ switch effectiveUnit {
1061+ case . calendar, . timeZone, . isLeapMonth:
1062+ return nil
1063+ case . era:
1064+ if time < - 63113904000.0 {
1065+ return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: - 63113904000.0 - inf_ti) , duration: inf_ti)
1066+ } else {
1067+ return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: - 63113904000.0 ) , duration: inf_ti)
1068+ }
1069+
1070+ case . hour:
1071+ let ti = Double ( timeZone. secondsFromGMT ( for: capped) )
1072+ var fixedTime = time + ti // compute local time
1073+ fixedTime = floor ( fixedTime / 3600.0 ) * 3600.0
1074+ fixedTime = fixedTime - ti // compute GMT
1075+ return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: fixedTime) , duration: 3600.0 )
1076+ case . minute:
1077+ return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: floor ( time / 60.0 ) * 60.0 ) , duration: 60.0 )
1078+ case . second:
1079+ return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: floor ( time) ) , duration: 1.0 )
1080+ case . nanosecond:
1081+ return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: floor ( time * 1.0e+9 ) * 1.0e-9 ) , duration: 1.0e-9 )
1082+ case . year, . yearForWeekOfYear, . quarter, . month, . day, . weekOfMonth, . weekOfYear:
1083+ // Continue to below
1084+ break
1085+ case . weekdayOrdinal, . weekday:
1086+ // Continue to below, after changing the unit
1087+ effectiveUnit = . day
1088+ break
1089+ }
1090+
1091+ let start = firstInstant ( of: effectiveUnit, at: capped)
1092+
1093+ var upperBound : Date
1094+ switch effectiveUnit {
1095+ case . era:
1096+ let newUDate = add ( . era, to: start, amount: 1 , inTimeZone: timeZone)
1097+ guard newUDate != start else {
1098+ // Probably because we are at the limit of era.
1099+ return DateInterval ( start: start, duration: inf_ti)
1100+ }
1101+ upperBound = start
1102+
1103+ case . year:
1104+ upperBound = add ( . year, to: start, amount: 1 , inTimeZone: timeZone)
1105+
1106+ case . yearForWeekOfYear:
1107+ upperBound = add ( . yearForWeekOfYear, to: start, amount: 1 , inTimeZone: timeZone)
1108+
1109+ case . quarter:
1110+ upperBound = add ( . month, to: start, amount: 3 , inTimeZone: timeZone)
1111+
1112+ case . month:
1113+ upperBound = add ( . month, to: start, amount: 1 , inTimeZone: timeZone)
1114+
1115+ case . weekOfYear: /* kCFCalendarUnitWeek_Deprecated */
1116+ upperBound = add ( . weekOfYear, to: start, amount: 1 , inTimeZone: timeZone)
1117+
1118+ case . weekOfMonth:
1119+ upperBound = add ( . weekOfMonth, to: start, amount: 1 , inTimeZone: timeZone)
1120+
1121+ case . day:
1122+ upperBound = add ( . day, to: start, amount: 1 , inTimeZone: timeZone)
1123+
1124+ default :
1125+ upperBound = start
1126+ }
1127+
1128+ // move back to 0h0m0s, in case the start of the unit wasn't at 0h0m0s
1129+ upperBound = firstInstant ( of: . day, at: upperBound)
1130+
1131+ if let tzTransition = timeZoneTransitionInterval ( at: upperBound, timeZone: timeZone) {
1132+ return DateInterval ( start: start, end: upperBound - tzTransition. duration)
1133+ } else if upperBound > start {
1134+ return DateInterval ( start: start, end: upperBound)
1135+ } else {
1136+ // Out of range
1137+ return nil
1138+ }
10511139 }
10521140
10531141 func isDateInWeekend( _ date: Date ) -> Bool {
0 commit comments