@@ -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
@@ -344,6 +348,15 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
344
348
return 1 ..< max + 1
345
349
case . month:
346
350
return 1 ..< 13
351
+ case . dayOfYear:
352
+ guard let year = dateComponent. year else {
353
+ return nil
354
+ }
355
+ if gregorianYearIsLeap ( year) {
356
+ return 1 ..< 367
357
+ } else {
358
+ return 1 ..< 366
359
+ }
347
360
case . day: // day in month
348
361
guard let month = dateComponent. month, let year = dateComponent. year else {
349
362
return nil
@@ -465,7 +478,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
465
478
dc. day = minMaxRange ( of: . day, in: dc) ? . lowerBound
466
479
fallthrough
467
480
468
- case . weekdayOrdinal, . weekday, . day:
481
+ case . weekdayOrdinal, . weekday, . day, . dayOfYear :
469
482
dc. hour = minMaxRange ( of: . hour, in: dc) ? . lowerBound
470
483
fallthrough
471
484
@@ -539,7 +552,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
539
552
return Date ( timeIntervalSinceReferenceDate: floor ( time) )
540
553
case . nanosecond:
541
554
return Date ( timeIntervalSinceReferenceDate: floor ( time * 1.0e+9 ) * 1.0e-9 )
542
- case . year, . yearForWeekOfYear, . quarter, . month, . day, . weekOfMonth, . weekOfYear:
555
+ case . year, . yearForWeekOfYear, . quarter, . month, . day, . dayOfYear , . weekOfMonth, . weekOfYear:
543
556
// Continue to below
544
557
break
545
558
case . weekdayOrdinal, . weekday:
@@ -1083,7 +1096,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1083
1096
return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: floor ( time) ) , duration: 1.0 )
1084
1097
case . nanosecond:
1085
1098
return DateInterval ( start: Date ( timeIntervalSinceReferenceDate: floor ( time * 1.0e+9 ) * 1.0e-9 ) , duration: 1.0e-9 )
1086
- case . year, . yearForWeekOfYear, . quarter, . month, . day, . weekOfMonth, . weekOfYear:
1099
+ case . year, . yearForWeekOfYear, . quarter, . month, . day, . dayOfYear , . weekOfMonth, . weekOfYear:
1087
1100
// Continue to below
1088
1101
break
1089
1102
case . weekdayOrdinal, . weekday:
@@ -1122,7 +1135,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1122
1135
case . weekOfMonth:
1123
1136
upperBound = add ( . weekOfMonth, to: start, amount: 1 , inTimeZone: timeZone)
1124
1137
1125
- case . day:
1138
+ case . day, . dayOfYear :
1126
1139
upperBound = add ( . day, to: start, amount: 1 , inTimeZone: timeZone)
1127
1140
1128
1141
default :
@@ -1247,6 +1260,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1247
1260
1248
1261
var rawYear : Int
1249
1262
switch resolvedComponents {
1263
+ case . dayOfYear( _, _) :
1264
+ fatalError ( " TODO - day of year calculation " )
1250
1265
case . day( let year, let month, _, _) :
1251
1266
rawMonth = month
1252
1267
rawYear = year
@@ -1270,6 +1285,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1270
1285
1271
1286
let julianDay : Int
1272
1287
switch resolvedComponents {
1288
+ case . dayOfYear( _, _) :
1289
+ fatalError ( " TODO - day of year calculation " )
1273
1290
case . day( _, _, let day, _) :
1274
1291
julianDay = julianDayAtBeginningOfYear + ( day ?? 1 )
1275
1292
case . weekdayOrdinal( _, _, let weekdayOrdinal, let weekday) :
@@ -1337,6 +1354,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1337
1354
useJulianReference = year == gregorianStartYear
1338
1355
case . weekOfMonth( _, _, _, _) : break
1339
1356
case . day( _, _, _, _) : break
1357
+ case . dayOfYear( _, _) : break
1340
1358
case . weekdayOrdinal( _, _, _, _) : break
1341
1359
}
1342
1360
@@ -1589,6 +1607,9 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1589
1607
}
1590
1608
if components. contains ( . month) { dc. month = month }
1591
1609
if components. contains ( . day) { dc. day = day }
1610
+ if #available( FoundationPreview 0 . 4 , * ) {
1611
+ if components. contains ( . dayOfYear) { dc. dayOfYear = dayOfYear }
1612
+ }
1592
1613
if components. contains ( . hour) { dc. hour = hour }
1593
1614
if components. contains ( . minute) { dc. minute = minute }
1594
1615
if components. contains ( . second) { dc. second = second }
@@ -1764,7 +1785,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1764
1785
// nothing to do for the below fields
1765
1786
case . calendar, . timeZone, . isLeapMonth:
1766
1787
return date
1767
- case . day, . hour, . minute, . second, . weekday, . weekdayOrdinal, . weekOfMonth, . weekOfYear, . nanosecond:
1788
+ case . day, . dayOfYear , . hour, . minute, . second, . weekday, . weekdayOrdinal, . weekOfMonth, . weekOfYear, . nanosecond:
1768
1789
// Handle below
1769
1790
break
1770
1791
}
@@ -1794,11 +1815,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1794
1815
amountInSeconds = kSecondsInWeek * amount
1795
1816
keepWallTime = true
1796
1817
1797
- case . day:
1798
- amountInSeconds = amount * kSecondsInDay
1799
- keepWallTime = true
1800
-
1801
- case . weekday:
1818
+ case . day, . dayOfYear, . weekday:
1802
1819
amountInSeconds = amount * kSecondsInDay
1803
1820
keepWallTime = true
1804
1821
@@ -1866,7 +1883,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1866
1883
1867
1884
// month-based calculations uses .day, .month, and .year, while week-based uses .weekday, .weekOfYear and .yearForWeekOfYear.
1868
1885
// 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.
1869
- let monthBasedComponents : Calendar . ComponentSet = [ . era, . year, . month, . day, . hour, . minute, . second, . nanosecond]
1886
+ let monthBasedComponents : Calendar . ComponentSet = [ . era, . year, . month, . day, . dayOfYear , . hour, . minute, . second, . nanosecond]
1870
1887
let weekBasedComponents : Calendar . ComponentSet = [ . era, . weekday, . weekOfYear, . yearForWeekOfYear, . hour, . minute, . second, . nanosecond ]
1871
1888
1872
1889
var result : Date
@@ -1908,6 +1925,33 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
1908
1925
capDay ( in: & dc) // adding 1 month to Jan 31 should return Feb 29, not Feb 31
1909
1926
result = self . date ( from: dc, inTimeZone: timeZone) !
1910
1927
1928
+ case . dayOfYear:
1929
+ if #available( FoundationPreview 0 . 4 , * ) {
1930
+ let dc = dateComponents ( monthBasedComponents, from: date, in: timeZone)
1931
+ guard let year = dc. year, let dayOfYear = dc. dayOfYear else {
1932
+ preconditionFailure ( " dateComponents(:from:in:) unexpectedly returns nil for requested component " )
1933
+ }
1934
+
1935
+ let range : Range < Int >
1936
+ if gregorianYearIsLeap ( year) {
1937
+ // max is 366
1938
+ range = 1 ..< 367
1939
+ } else {
1940
+ // max is 365
1941
+ range = 1 ..< 366
1942
+ }
1943
+
1944
+ let newDayOfYear = add ( amount: amount, to: dayOfYear, wrappingTo: range)
1945
+ // Clear the month and day from the date components. Keep the era, year, and time values (hour, min, etc.)
1946
+ var adjustedDateComponents = dc
1947
+ adjustedDateComponents. month = nil
1948
+ adjustedDateComponents. day = nil
1949
+ adjustedDateComponents. dayOfYear = newDayOfYear
1950
+ result = self . date ( from: adjustedDateComponents, inTimeZone: timeZone) !
1951
+ } else {
1952
+ result = date
1953
+ }
1954
+
1911
1955
case . day:
1912
1956
let ( _, monthStart, daysInMonth, inGregorianCutoverMonth) = dayOfMonthConsideringGregorianCutover ( date, inTimeZone: timeZone)
1913
1957
0 commit comments