@@ -1946,12 +1946,14 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
19461946 }
19471947
19481948 func dayOfYear( fromYear year: Int , month: Int , day: Int ) throws ( GregorianCalendarError) -> Int {
1949+ precondition ( month > 0 && month < 13 )
19491950 let daysBeforeMonthNonLeap = [ 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 ]
19501951 let daysBeforeMonthLeap = [ 0 , 31 , 60 , 91 , 121 , 152 , 182 , 213 , 244 , 274 , 305 , 335 ]
19511952
19521953 let julianDay = try Self . julianDay ( ofDay: day, month: month, year: year)
19531954 let useJulianCalendar = julianDay < julianCutoverDay
19541955 let isLeapYear = gregorianYearIsLeap ( year)
1956+
19551957 var dayOfYear = ( isLeapYear ? daysBeforeMonthLeap : daysBeforeMonthNonLeap) [ month - 1 ] + day
19561958 if !useJulianCalendar && year == gregorianStartYear {
19571959 // Use julian's week number for 1582, so recalculate day of year
@@ -1985,96 +1987,145 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
19851987 }
19861988
19871989 func dateComponents( _ components: Calendar . ComponentSet , from d: Date , in timeZone: TimeZone ) -> DateComponents {
1990+ guard !components. isEmpty else {
1991+ return DateComponents ( )
1992+ }
1993+
19881994 let timezoneOffset = timeZone. secondsFromGMT ( for: d)
19891995 let localDate = d + Double( timezoneOffset)
19901996
19911997 let dateOffsetInSeconds = localDate. timeIntervalSinceReferenceDate. rounded ( . down)
19921998 let date = Date ( timeIntervalSinceReferenceDate: dateOffsetInSeconds) // Round down the given date to seconds
19931999
1994- let totalSeconds = Int ( dateOffsetInSeconds)
1995- let secondsInDay = ( totalSeconds % 86400 + 86400 ) % 86400
1996-
1997- let hour = secondsInDay / 3600
1998- let minute = ( secondsInDay % 3600 ) / 60
1999- let second = secondsInDay % 60
2000- let nanosecond = Int ( ( localDate. timeIntervalSinceReferenceDate - dateOffsetInSeconds) * 1_000_000_000 )
2000+ let hour : Int ?
2001+ let second : Int ?
2002+ let minute : Int ?
2003+ let nanosecond : Int ?
2004+ var dayOfYear : Int ?
2005+ var weekday : Int ?
2006+ var weekOfMonth : Int ?
2007+ var yearForWeekOfYear : Int ?
2008+ var weekdayOrdinal : Int ?
2009+ var weekOfYear : Int ?
2010+ var year : Int ?
2011+ var month : Int ?
2012+ var day : Int ?
2013+
2014+ let timeComponents : Calendar . ComponentSet = [ . hour, . minute, . second, . nanosecond]
2015+ if !components. isDisjoint ( with: timeComponents) {
2016+ let totalSeconds = Int ( dateOffsetInSeconds)
2017+ let secondsInDay = ( totalSeconds % 86400 + 86400 ) % 86400
2018+
2019+ hour = secondsInDay / 3600
2020+ minute = ( secondsInDay % 3600 ) / 60
2021+ second = secondsInDay % 60
2022+ nanosecond = Int ( ( localDate. timeIntervalSinceReferenceDate - dateOffsetInSeconds) * 1_000_000_000 )
2023+ } else {
2024+ hour = nil
2025+ minute = nil
2026+ second = nil
2027+ nanosecond = nil
2028+ }
20012029
2002- if components. containsOnlyTimeComponents {
2030+ if components. isSubset ( of : timeComponents ) {
20032031 var dcHour : Int ?
20042032 var dcMinute : Int ?
20052033 var dcSecond : Int ?
20062034 var dcNano : Int ?
2035+
20072036 if components. contains ( . hour) { dcHour = hour }
20082037 if components. contains ( . minute) { dcMinute = minute }
20092038 if components. contains ( . second) { dcSecond = second }
20102039 if components. contains ( . nanosecond) { dcNano = nanosecond }
2011- return DateComponents ( hour: dcHour, minute: dcMinute, second: dcSecond, nanosecond: dcNano)
2012- }
2013-
2014- let dayOfYear : Int
2015- let weekday : Int
2016- let weekOfMonth : Int
2017- var yearForWeekOfYear : Int
2018- var weekdayOrdinal : Int
2019- var weekOfYear : Int
2020- var isLeapYear : Bool
2021- var year : Int
2022- var month : Int
2023- var day : Int
2040+ return DateComponents ( rawHour: dcHour, rawMinute: dcMinute, rawSecond: dcSecond, rawNanosecond: dcNano)
2041+ }
2042+
20242043 do {
20252044 let useJulianRef = useJulianReference ( date)
20262045 let julianDay = try date. julianDay ( )
2027- ( year, month, day) = Self . yearMonthDayFromJulianDay ( julianDay, useJulianRef: useJulianRef)
2028- isLeapYear = gregorianYearIsLeap ( year)
2029-
2030- // To calculate day of year, work backwards with month/day
2031- dayOfYear = try self . dayOfYear ( fromYear: year, month: month, day: day)
2032- func remainder( numerator: Int , denominator: Int ) -> Int {
2033- let r = numerator % denominator
2034- return r >= 0 ? r : r + denominator
2035- }
2036- // Week of year calculation, from ICU calendar.cpp :: computeWeekFields
2037- // 1-based: 1...7
2038- weekday = remainder ( numerator: julianDay + 1 , denominator: 7 ) + 1
2039-
2040- // 0-based 0...6
2041- let relativeWeekday = ( weekday + 7 - firstWeekday) % 7
2042- let relativeWeekdayForJan1 = ( weekday - dayOfYear + 7001 - firstWeekday) % 7
2043- weekOfYear = ( dayOfYear - 1 + relativeWeekdayForJan1) / 7 // 0...53
2044- if ( 7 - relativeWeekdayForJan1) >= minimumDaysInFirstWeek {
2045- weekOfYear += 1
2046- }
2047-
2048- yearForWeekOfYear = year
2049- // Adjust for weeks at end of the year that overlap into previous or next calendar year
2050- if weekOfYear == 0 {
2051- let previousDayOfYear = dayOfYear + ( gregorianYearIsLeap ( year - 1 ) ? 366 : 365 )
2052- weekOfYear = weekNumber ( desiredDay: previousDayOfYear, dayOfPeriod: previousDayOfYear, weekday: weekday)
2053- yearForWeekOfYear -= 1
2046+ let julianDayYMD = Self . yearMonthDayFromJulianDay ( julianDay, useJulianRef: useJulianRef)
2047+
2048+ year = julianDayYMD. year
2049+ month = julianDayYMD. month
2050+ day = julianDayYMD. day
2051+
2052+ guard let year, let month, let day else {
2053+ preconditionFailure ( )
2054+ }
2055+
2056+ if !components. isDisjoint ( with: [ . dayOfYear, . quarter] ) || !components. isDisjoint ( with: [ . weekOfYear, . yearForWeekOfYear] ) {
2057+ dayOfYear = try self . dayOfYear ( fromYear: year, month: month, day: day)
20542058 } else {
2055- let lastDayOfYear = ( gregorianYearIsLeap ( year) ? 366 : 365 )
2056- // Fast check: For it to be week 1 of the next year, the DOY
2057- // must be on or after L-5, where L is yearLength(), then it
2058- // cannot possibly be week 1 of the next year:
2059- // L-5 L
2060- // doy: 359 360 361 362 363 364 365 001
2061- // dow: 1 2 3 4 5 6 7
2062- if dayOfYear >= lastDayOfYear - 5 {
2063- var lastRelativeDayOfWeek = ( relativeWeekday + lastDayOfYear - dayOfYear) % 7
2064- if lastRelativeDayOfWeek < 0 {
2065- lastRelativeDayOfWeek += 7
2059+ dayOfYear = nil
2060+ }
2061+
2062+ // Calculate weekday-related fields
2063+ if !components. isDisjoint ( with: [ . weekday, . weekdayOrdinal, . weekOfMonth, . weekOfYear, . yearForWeekOfYear] ) {
2064+ func remainder( numerator: Int , denominator: Int ) -> Int {
2065+ let r = numerator % denominator
2066+ return r >= 0 ? r : r + denominator
2067+ }
2068+ // Week of year calculation, from ICU calendar.cpp :: computeWeekFields
2069+ // 1-based: 1...7
2070+ weekday = remainder ( numerator: julianDay + 1 , denominator: 7 ) + 1
2071+
2072+ if !components. isDisjoint ( with: [ . weekOfYear, . yearForWeekOfYear] ) {
2073+ guard let dayOfYear, let weekday else {
2074+ preconditionFailure ( )
2075+ }
2076+
2077+ // 0-based 0...6
2078+ let relativeWeekday = ( weekday + 7 - firstWeekday) % 7
2079+ let relativeWeekdayForJan1 = ( weekday - dayOfYear + 7001 - firstWeekday) % 7
2080+ var calculatedWeekOfYear = ( dayOfYear - 1 + relativeWeekdayForJan1) / 7 // 0...53
2081+ if ( 7 - relativeWeekdayForJan1) >= minimumDaysInFirstWeek {
2082+ calculatedWeekOfYear += 1
2083+ }
2084+
2085+ var calculatedYearForWeekOfYear = year
2086+ // Adjust for weeks at end of the year that overlap into previous or next calendar year
2087+ if calculatedWeekOfYear == 0 {
2088+ let previousDayOfYear = dayOfYear + ( gregorianYearIsLeap ( year - 1 ) ? 366 : 365 )
2089+ calculatedWeekOfYear = weekNumber ( desiredDay: previousDayOfYear, dayOfPeriod: previousDayOfYear, weekday: weekday)
2090+ calculatedYearForWeekOfYear -= 1
2091+ } else {
2092+ let lastDayOfYear = ( gregorianYearIsLeap ( year) ? 366 : 365 )
2093+ // Fast check: For it to be week 1 of the next year, the DOY
2094+ // must be on or after L-5, where L is yearLength(), then it
2095+ // cannot possibly be week 1 of the next year:
2096+ // L-5 L
2097+ // doy: 359 360 361 362 363 364 365 001
2098+ // dow: 1 2 3 4 5 6 7
2099+ if dayOfYear >= lastDayOfYear - 5 {
2100+ var lastRelativeDayOfWeek = ( relativeWeekday + lastDayOfYear - dayOfYear) % 7
2101+ if lastRelativeDayOfWeek < 0 {
2102+ lastRelativeDayOfWeek += 7
2103+ }
2104+
2105+ if ( ( 6 - lastRelativeDayOfWeek) >= minimumDaysInFirstWeek) && ( ( dayOfYear + 7 - relativeWeekday) > lastDayOfYear) {
2106+ calculatedWeekOfYear = 1
2107+ calculatedYearForWeekOfYear += 1
2108+ }
2109+ }
20662110 }
2111+ yearForWeekOfYear = calculatedYearForWeekOfYear
2112+ weekOfYear = calculatedWeekOfYear
2113+ }
20672114
2068- if ( ( 6 - lastRelativeDayOfWeek ) >= minimumDaysInFirstWeek ) && ( ( dayOfYear + 7 - relativeWeekday ) > lastDayOfYear ) {
2069- weekOfYear = 1
2070- yearForWeekOfYear += 1
2115+ if components . contains ( . weekOfMonth ) {
2116+ guard let weekday else {
2117+ preconditionFailure ( )
20712118 }
2119+ weekOfMonth = weekNumber ( desiredDay: day, dayOfPeriod: day, weekday: weekday)
2120+ }
2121+
2122+ if components. contains ( . weekdayOrdinal) {
2123+ weekdayOrdinal = ( day - 1 ) / 7 + 1
20722124 }
20732125 }
20742126
2075- weekOfMonth = weekNumber ( desiredDay: day, dayOfPeriod: day, weekday: weekday)
2076- weekdayOrdinal = ( day - 1 ) / 7 + 1
20772127 } catch {
2128+ // Set error values when calculations fail
20782129 year = . max
20792130 month = . max
20802131 day = . max
@@ -2084,9 +2135,9 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
20842135 yearForWeekOfYear = . max
20852136 weekdayOrdinal = . max
20862137 weekOfYear = . max
2087- isLeapYear = false
20882138 }
20892139
2140+
20902141 var dcCalendar : Calendar ?
20912142 var dcTimeZone : TimeZone ?
20922143 var dcEra : Int ?
@@ -2110,17 +2161,21 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
21102161 if components. contains ( . calendar) { dcCalendar = Calendar ( identifier: identifier) }
21112162 if components. contains ( . timeZone) { dcTimeZone = timeZone }
21122163 if components. contains ( . era) {
2113- if year < 1 {
2164+ if let year = year , year < 1 {
21142165 dcEra = 0
21152166 } else {
21162167 dcEra = 1
21172168 }
21182169 }
21192170 if components. contains ( . year) {
2120- if year < 1 {
2121- year = 1 - year
2171+ if var year = year {
2172+ if year < 1 {
2173+ year = 1 - year
2174+ }
2175+ dcYear = year
2176+ } else {
2177+ dcYear = . max
21222178 }
2123- dcYear = year
21242179 }
21252180 if components. contains ( . month) { dcMonth = month }
21262181 if components. contains ( . day) { dcDay = day }
@@ -2131,18 +2186,24 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
21312186 if components. contains ( . weekday) { dcWeekday = weekday }
21322187 if components. contains ( . weekdayOrdinal) { dcWeekdayOrdinal = weekdayOrdinal }
21332188 if components. contains ( . quarter) {
2134- let quarter = if !isLeapYear {
2135- if dayOfYear < 90 { 1 }
2136- else if dayOfYear < 181 { 2 }
2137- else if dayOfYear < 273 { 3 }
2138- else if dayOfYear < 366 { 4 }
2139- else { fatalError ( ) }
2189+ let quarter : Int
2190+ if let dayOfYear = dayOfYear, let year {
2191+ let isLeapYear = gregorianYearIsLeap ( year)
2192+ quarter = if !isLeapYear {
2193+ if dayOfYear < 90 { 1 }
2194+ else if dayOfYear < 181 { 2 }
2195+ else if dayOfYear < 273 { 3 }
2196+ else if dayOfYear < 366 { 4 }
2197+ else { preconditionFailure ( " Invalid day of year " ) }
2198+ } else {
2199+ if dayOfYear < 91 { 1 }
2200+ else if dayOfYear < 182 { 2 }
2201+ else if dayOfYear < 274 { 3 }
2202+ else if dayOfYear < 367 { 4 }
2203+ else { preconditionFailure ( " Invalid day of year " ) }
2204+ }
21402205 } else {
2141- if dayOfYear < 91 { 1 }
2142- else if dayOfYear < 182 { 2 }
2143- else if dayOfYear < 274 { 3 }
2144- else if dayOfYear < 367 { 4 }
2145- else { fatalError ( ) }
2206+ quarter = 1 // fallback if calculations weren't performed
21462207 }
21472208
21482209 dcQuarter = quarter
0 commit comments