Skip to content

Commit 41fa6cd

Browse files
committed
Add Calendar_Gregorian support and placeholders for dayOfYear
1 parent 2bc4f44 commit 41fa6cd

File tree

2 files changed

+57
-13
lines changed

2 files changed

+57
-13
lines changed

Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ extension Date {
5656
/// This helper records which components should take precedence.
5757
enum ResolvedDateComponents {
5858

59+
case dayOfYear(year: Int, dayOfYear: Int)
5960
case day(year: Int, month: Int, day: Int?, weekOfYear: Int?)
6061
case weekdayOrdinal(year: Int, month: Int, weekdayOrdinal: Int, weekday: Int?)
6162
case weekOfYear(year: Int, weekOfYear: Int?, weekday: Int?)
@@ -112,7 +113,10 @@ enum ResolvedDateComponents {
112113
init(dateComponents components: DateComponents) {
113114
var (year, month) = Self.yearMonth(forDateComponent: components)
114115
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 {
116120
if components.yearForWeekOfYear != nil, let weekOfYear = components.weekOfYear {
117121
if components.month == nil && weekOfYear >= 52 {
118122
year += 1
@@ -344,6 +348,15 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
344348
return 1..<max + 1
345349
case .month:
346350
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+
}
347360
case .day: // day in month
348361
guard let month = dateComponent.month, let year = dateComponent.year else {
349362
return nil
@@ -465,7 +478,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
465478
dc.day = minMaxRange(of: .day, in: dc)?.lowerBound
466479
fallthrough
467480

468-
case .weekdayOrdinal, .weekday, .day:
481+
case .weekdayOrdinal, .weekday, .day, .dayOfYear:
469482
dc.hour = minMaxRange(of: .hour, in: dc)?.lowerBound
470483
fallthrough
471484

@@ -539,7 +552,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
539552
return Date(timeIntervalSinceReferenceDate: floor(time))
540553
case .nanosecond:
541554
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:
543556
// Continue to below
544557
break
545558
case .weekdayOrdinal, .weekday:
@@ -1083,7 +1096,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
10831096
return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time)), duration: 1.0)
10841097
case .nanosecond:
10851098
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:
10871100
// Continue to below
10881101
break
10891102
case .weekdayOrdinal, .weekday:
@@ -1122,7 +1135,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
11221135
case .weekOfMonth:
11231136
upperBound = add(.weekOfMonth, to: start, amount: 1, inTimeZone: timeZone)
11241137

1125-
case .day:
1138+
case .day, .dayOfYear:
11261139
upperBound = add(.day, to: start, amount: 1, inTimeZone: timeZone)
11271140

11281141
default:
@@ -1247,6 +1260,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
12471260

12481261
var rawYear: Int
12491262
switch resolvedComponents {
1263+
case .dayOfYear(_, _):
1264+
fatalError("TODO - day of year calculation")
12501265
case .day(let year, let month, _, _):
12511266
rawMonth = month
12521267
rawYear = year
@@ -1270,6 +1285,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
12701285

12711286
let julianDay: Int
12721287
switch resolvedComponents {
1288+
case .dayOfYear(_, _):
1289+
fatalError("TODO - day of year calculation")
12731290
case .day(_, _, let day, _):
12741291
julianDay = julianDayAtBeginningOfYear + (day ?? 1)
12751292
case .weekdayOrdinal(_, _, let weekdayOrdinal, let weekday):
@@ -1337,6 +1354,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
13371354
useJulianReference = year == gregorianStartYear
13381355
case .weekOfMonth(_, _, _, _): break
13391356
case .day(_, _, _, _): break
1357+
case .dayOfYear(_, _): break
13401358
case .weekdayOrdinal(_, _, _, _): break
13411359
}
13421360

@@ -1589,6 +1607,9 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
15891607
}
15901608
if components.contains(.month) { dc.month = month }
15911609
if components.contains(.day) { dc.day = day }
1610+
if #available(FoundationPreview 0.4, *) {
1611+
if components.contains(.dayOfYear) { dc.dayOfYear = dayOfYear }
1612+
}
15921613
if components.contains(.hour) { dc.hour = hour }
15931614
if components.contains(.minute) { dc.minute = minute }
15941615
if components.contains(.second) { dc.second = second }
@@ -1764,7 +1785,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
17641785
// nothing to do for the below fields
17651786
case .calendar, .timeZone, .isLeapMonth:
17661787
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:
17681789
// Handle below
17691790
break
17701791
}
@@ -1794,11 +1815,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
17941815
amountInSeconds = kSecondsInWeek * amount
17951816
keepWallTime = true
17961817

1797-
case .day:
1798-
amountInSeconds = amount * kSecondsInDay
1799-
keepWallTime = true
1800-
1801-
case .weekday:
1818+
case .day, .dayOfYear, .weekday:
18021819
amountInSeconds = amount * kSecondsInDay
18031820
keepWallTime = true
18041821

@@ -1866,7 +1883,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
18661883

18671884
// month-based calculations uses .day, .month, and .year, while week-based uses .weekday, .weekOfYear and .yearForWeekOfYear.
18681885
// 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]
18701887
let weekBasedComponents: Calendar.ComponentSet = [.era, .weekday, .weekOfYear, .yearForWeekOfYear, .hour, .minute, .second, .nanosecond ]
18711888

18721889
var result: Date
@@ -1908,6 +1925,33 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
19081925
capDay(in: &dc) // adding 1 month to Jan 31 should return Feb 29, not Feb 31
19091926
result = self.date(from: dc, inTimeZone: timeZone)!
19101927

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+
19111955
case .day:
19121956
let (_, monthStart, daysInMonth, inGregorianCutoverMonth) = dayOfMonthConsideringGregorianCutover(date, inTimeZone: timeZone)
19131957

Sources/FoundationEssentials/Calendar/DateComponents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public struct DateComponents : Hashable, Equatable, Sendable {
227227
get { _dayOfYear }
228228
set { _dayOfYear = converted(newValue) }
229229
}
230-
230+
231231
/// This exists only for compatibility with NSDateComponents deprecated `week` value.
232232
package var week: Int? {
233233
get { _week }

0 commit comments

Comments
 (0)