@@ -56,6 +56,7 @@ extension Date {
56
56
/// This helper records which components should take precedence.
57
57
enum ResolvedDateComponents {
58
58
59
+ case dayOfYear( year: Int , dayOfYear: Int )
59
60
case day( year: Int , month: Int , day: Int ? , weekOfYear: Int ? )
60
61
case weekdayOrdinal( year: Int , month: Int , weekdayOrdinal: Int , weekday: Int ? )
61
62
case weekOfYear( year: Int , weekOfYear: Int ? , weekday: Int ? )
@@ -112,7 +113,10 @@ enum ResolvedDateComponents {
112
113
init ( dateComponents components: DateComponents ) {
113
114
var ( year, month) = Self . yearMonth ( forDateComponent: components)
114
115
let minWeekdayOrdinal = 1
115
- if let d = components. day {
116
+
117
+ if #available( FoundationPreview 0 . 4 , * ) , let doy = components. dayOfYear {
118
+ fatalError ( " TODO - day of year calculation " )
119
+ } else if let d = components. day {
116
120
if components. yearForWeekOfYear != nil , let weekOfYear = components. weekOfYear {
117
121
if components. month == nil && weekOfYear >= 52 {
118
122
year += 1
@@ -573,6 +577,15 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
573
577
return 1 ..< max + 1
574
578
case . month:
575
579
return 1 ..< 13
580
+ case . dayOfYear:
581
+ guard let year = dateComponent. year else {
582
+ return nil
583
+ }
584
+ if gregorianYearIsLeap ( year) {
585
+ return 1 ..< 367
586
+ } else {
587
+ return 1 ..< 366
588
+ }
576
589
case . day: // day in month
577
590
guard let month = dateComponent. month, let year = dateComponent. year else {
578
591
return nil
@@ -694,7 +707,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
694
707
dc. day = minMaxRange ( of: . day, in: dc) ? . lowerBound
695
708
fallthrough
696
709
697
- case . weekdayOrdinal, . weekday, . day:
710
+ case . weekdayOrdinal, . weekday, . day, . dayOfYear :
698
711
dc. hour = minMaxRange ( of: . hour, in: dc) ? . lowerBound
699
712
fallthrough
700
713
@@ -768,7 +781,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
768
781
return Date ( timeIntervalSinceReferenceDate: floor ( time) )
769
782
case . nanosecond:
770
783
return Date ( timeIntervalSinceReferenceDate: floor ( time * 1.0e+9 ) * 1.0e-9 )
771
- case . year, . yearForWeekOfYear, . quarter, . month, . day, . weekOfMonth, . weekOfYear:
784
+ case . year, . yearForWeekOfYear, . quarter, . month, . day, . dayOfYear , . weekOfMonth, . weekOfYear:
772
785
// Continue to below
773
786
break
774
787
case . weekdayOrdinal, . weekday:
@@ -1312,7 +1325,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1312
1325
return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: floor ( time) ) , duration: 1.0 )
1313
1326
case . nanosecond:
1314
1327
return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: floor ( time * 1.0e+9 ) * 1.0e-9 ) , duration: 1.0e-9 )
1315
- case . year, . yearForWeekOfYear, . quarter, . month, . day, . weekOfMonth, . weekOfYear:
1328
+ case . year, . yearForWeekOfYear, . quarter, . month, . day, . dayOfYear , . weekOfMonth, . weekOfYear:
1316
1329
// Continue to below
1317
1330
break
1318
1331
case . weekdayOrdinal, . weekday:
@@ -1351,7 +1364,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1351
1364
case . weekOfMonth:
1352
1365
upperBound = add ( . weekOfMonth, to: start, amount: 1 , inTimeZone: timeZone)
1353
1366
1354
- case . day:
1367
+ case . day, . dayOfYear :
1355
1368
upperBound = add ( . day, to: start, amount: 1 , inTimeZone: timeZone)
1356
1369
1357
1370
default :
@@ -1476,6 +1489,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1476
1489
1477
1490
var rawYear : Int
1478
1491
switch resolvedComponents {
1492
+ case . dayOfYear( _, _) :
1493
+ fatalError ( " TODO - day of year calculation " )
1479
1494
case . day( let year, let month, _, _) :
1480
1495
rawMonth = month
1481
1496
rawYear = year
@@ -1499,6 +1514,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1499
1514
1500
1515
let julianDay : Int
1501
1516
switch resolvedComponents {
1517
+ case . dayOfYear( _, _) :
1518
+ fatalError ( " TODO - day of year calculation " )
1502
1519
case . day( _, _, let day, _) :
1503
1520
julianDay = julianDayAtBeginningOfYear + ( day ?? 1 )
1504
1521
case . weekdayOrdinal( _, _, let weekdayOrdinal, let weekday) :
@@ -1566,6 +1583,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1566
1583
useJulianReference = year == gregorianStartYear
1567
1584
case . weekOfMonth( _, _, _, _) : break
1568
1585
case . day( _, _, _, _) : break
1586
+ case . dayOfYear( _, _) : break
1569
1587
case . weekdayOrdinal( _, _, _, _) : break
1570
1588
}
1571
1589
@@ -1818,6 +1836,9 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1818
1836
}
1819
1837
if components. contains ( . month) { dc. month = month }
1820
1838
if components. contains ( . day) { dc. day = day }
1839
+ if #available( FoundationPreview 0 . 4 , * ) {
1840
+ if components. contains ( . dayOfYear) { dc. dayOfYear = dayOfYear }
1841
+ }
1821
1842
if components. contains ( . hour) { dc. hour = hour }
1822
1843
if components. contains ( . minute) { dc. minute = minute }
1823
1844
if components. contains ( . second) { dc. second = second }
@@ -1993,7 +2014,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1993
2014
// nothing to do for the below fields
1994
2015
case . calendar, . timeZone, . isLeapMonth:
1995
2016
return date
1996
- case . day, . hour, . minute, . second, . weekday, . weekdayOrdinal, . weekOfMonth, . weekOfYear, . nanosecond:
2017
+ case . day, . dayOfYear , . hour, . minute, . second, . weekday, . weekdayOrdinal, . weekOfMonth, . weekOfYear, . nanosecond:
1997
2018
// Handle below
1998
2019
break
1999
2020
}
@@ -2023,11 +2044,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
2023
2044
amountInSeconds = kSecondsInWeek * amount
2024
2045
keepWallTime = true
2025
2046
2026
- case . day:
2027
- amountInSeconds = amount * kSecondsInDay
2028
- keepWallTime = true
2029
-
2030
- case . weekday:
2047
+ case . day, . dayOfYear, . weekday:
2031
2048
amountInSeconds = amount * kSecondsInDay
2032
2049
keepWallTime = true
2033
2050
@@ -2095,7 +2112,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
2095
2112
2096
2113
// month-based calculations uses .day, .month, and .year, while week-based uses .weekday, .weekOfYear and .yearForWeekOfYear.
2097
2114
// When performing date adding calculations, we need to be specific whether it's "month based" or "week based". We do not want existing week-related fields in the DateComponents to conflict with the newly set month-related fields when doing month-based calculation, and vice versa. So it's necessary to only include relevant components rather than all components when performing adding calculation.
2098
- let monthBasedComponents : Calendar . ComponentSet = [ . era, . year, . month, . day, . hour, . minute, . second, . nanosecond]
2115
+ let monthBasedComponents : Calendar . ComponentSet = [ . era, . year, . month, . day, . dayOfYear , . hour, . minute, . second, . nanosecond]
2099
2116
let weekBasedComponents : Calendar . ComponentSet = [ . era, . weekday, . weekOfYear, . yearForWeekOfYear, . hour, . minute, . second, . nanosecond ]
2100
2117
2101
2118
var result : Date
@@ -2137,6 +2154,33 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
2137
2154
capDay ( in: & dc) // adding 1 month to Jan 31 should return Feb 29, not Feb 31
2138
2155
result = self . date ( from: dc, inTimeZone: timeZone) !
2139
2156
2157
+ case . dayOfYear:
2158
+ if #available( FoundationPreview 0 . 4 , * ) {
2159
+ let dc = dateComponents ( monthBasedComponents, from: date, in: timeZone)
2160
+ guard let year = dc. year, let dayOfYear = dc. dayOfYear else {
2161
+ preconditionFailure ( " dateComponents(:from:in:) unexpectedly returns nil for requested component " )
2162
+ }
2163
+
2164
+ let range : Range < Int >
2165
+ if gregorianYearIsLeap ( year) {
2166
+ // max is 366
2167
+ range = 1 ..< 367
2168
+ } else {
2169
+ // max is 365
2170
+ range = 1 ..< 366
2171
+ }
2172
+
2173
+ let newDayOfYear = add ( amount: amount, to: dayOfYear, wrappingTo: range)
2174
+ // Clear the month and day from the date components. Keep the era, year, and time values (hour, min, etc.)
2175
+ var adjustedDateComponents = dc
2176
+ adjustedDateComponents. month = nil
2177
+ adjustedDateComponents. day = nil
2178
+ adjustedDateComponents. dayOfYear = newDayOfYear
2179
+ result = self . date ( from: adjustedDateComponents, inTimeZone: timeZone) !
2180
+ } else {
2181
+ result = date
2182
+ }
2183
+
2140
2184
case . day:
2141
2185
let ( _, monthStart, daysInMonth, inGregorianCutoverMonth) = dayOfMonthConsideringGregorianCutover ( date, inTimeZone: timeZone)
2142
2186
0 commit comments