Skip to content

Commit 4044746

Browse files
committed
Add Calendar_Gregorian support and placeholders for dayOfYear
1 parent 78815ab commit 4044746

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
@@ -573,6 +577,15 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
573577
return 1..<max + 1
574578
case .month:
575579
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+
}
576589
case .day: // day in month
577590
guard let month = dateComponent.month, let year = dateComponent.year else {
578591
return nil
@@ -694,7 +707,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
694707
dc.day = minMaxRange(of: .day, in: dc)?.lowerBound
695708
fallthrough
696709

697-
case .weekdayOrdinal, .weekday, .day:
710+
case .weekdayOrdinal, .weekday, .day, .dayOfYear:
698711
dc.hour = minMaxRange(of: .hour, in: dc)?.lowerBound
699712
fallthrough
700713

@@ -768,7 +781,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
768781
return Date(timeIntervalSinceReferenceDate: floor(time))
769782
case .nanosecond:
770783
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:
772785
// Continue to below
773786
break
774787
case .weekdayOrdinal, .weekday:
@@ -1312,7 +1325,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
13121325
return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time)), duration: 1.0)
13131326
case .nanosecond:
13141327
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:
13161329
// Continue to below
13171330
break
13181331
case .weekdayOrdinal, .weekday:
@@ -1351,7 +1364,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
13511364
case .weekOfMonth:
13521365
upperBound = add(.weekOfMonth, to: start, amount: 1, inTimeZone: timeZone)
13531366

1354-
case .day:
1367+
case .day, .dayOfYear:
13551368
upperBound = add(.day, to: start, amount: 1, inTimeZone: timeZone)
13561369

13571370
default:
@@ -1476,6 +1489,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
14761489

14771490
var rawYear: Int
14781491
switch resolvedComponents {
1492+
case .dayOfYear(_, _):
1493+
fatalError("TODO - day of year calculation")
14791494
case .day(let year, let month, _, _):
14801495
rawMonth = month
14811496
rawYear = year
@@ -1499,6 +1514,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
14991514

15001515
let julianDay: Int
15011516
switch resolvedComponents {
1517+
case .dayOfYear(_, _):
1518+
fatalError("TODO - day of year calculation")
15021519
case .day(_, _, let day, _):
15031520
julianDay = julianDayAtBeginningOfYear + (day ?? 1)
15041521
case .weekdayOrdinal(_, _, let weekdayOrdinal, let weekday):
@@ -1566,6 +1583,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
15661583
useJulianReference = year == gregorianStartYear
15671584
case .weekOfMonth(_, _, _, _): break
15681585
case .day(_, _, _, _): break
1586+
case .dayOfYear(_, _): break
15691587
case .weekdayOrdinal(_, _, _, _): break
15701588
}
15711589

@@ -1818,6 +1836,9 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
18181836
}
18191837
if components.contains(.month) { dc.month = month }
18201838
if components.contains(.day) { dc.day = day }
1839+
if #available(FoundationPreview 0.4, *) {
1840+
if components.contains(.dayOfYear) { dc.dayOfYear = dayOfYear }
1841+
}
18211842
if components.contains(.hour) { dc.hour = hour }
18221843
if components.contains(.minute) { dc.minute = minute }
18231844
if components.contains(.second) { dc.second = second }
@@ -1993,7 +2014,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
19932014
// nothing to do for the below fields
19942015
case .calendar, .timeZone, .isLeapMonth:
19952016
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:
19972018
// Handle below
19982019
break
19992020
}
@@ -2023,11 +2044,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
20232044
amountInSeconds = kSecondsInWeek * amount
20242045
keepWallTime = true
20252046

2026-
case .day:
2027-
amountInSeconds = amount * kSecondsInDay
2028-
keepWallTime = true
2029-
2030-
case .weekday:
2047+
case .day, .dayOfYear, .weekday:
20312048
amountInSeconds = amount * kSecondsInDay
20322049
keepWallTime = true
20332050

@@ -2095,7 +2112,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
20952112

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

21012118
var result: Date
@@ -2137,6 +2154,33 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
21372154
capDay(in: &dc) // adding 1 month to Jan 31 should return Feb 29, not Feb 31
21382155
result = self.date(from: dc, inTimeZone: timeZone)!
21392156

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

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)