Skip to content

Commit d569316

Browse files
authored
[Gregorian Calendar] Implement weekend-related functions (swiftlang#368)
Calendar's weekend support requires the following functions: 1. `isDateInWeekend`: answers whether a given date is in weekend 2. `weekendRange`: function that returns the days and times of the weekend This implements the first. The second one, weekend range, is in fact related to calendar's locale rather than calendar itself. Move it over to `_LocaleProtocol` and have locale handle it.
1 parent 2d98e90 commit d569316

File tree

14 files changed

+275
-119
lines changed

14 files changed

+275
-119
lines changed

Sources/FoundationEssentials/Calendar/Calendar.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -978,9 +978,7 @@ public struct Calendar : Hashable, Equatable, Sendable {
978978
/// - returns: A `DateInterval`, or nil if weekends do not exist in the specific calendar or locale.
979979
@available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
980980
public func nextWeekend(startingAfter date: Date, direction: SearchDirection = .forward) -> DateInterval? {
981-
let weekend = _calendar.weekendRange()
982-
983-
guard let weekend else {
981+
guard let weekend = locale?.weekendRange else {
984982
return nil
985983
}
986984

@@ -1460,7 +1458,7 @@ extension Calendar : Codable {
14601458
extension Calendar.Identifier : Codable {}
14611459

14621460
/// Internal-use struct for holding the range of a Weekend
1463-
package struct WeekendRange {
1461+
package struct WeekendRange: Equatable, Hashable {
14641462
package var onsetTime: TimeInterval?
14651463
package var ceaseTime: TimeInterval?
14661464
package var start: Int

Sources/FoundationEssentials/Calendar/Calendar_Autoupdating.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ internal final class _CalendarAutoupdating: _CalendarProtocol, @unchecked Sendab
105105
CalendarCache.cache.current.isDateInWeekend(date)
106106
}
107107

108-
func weekendRange() -> WeekendRange? {
109-
CalendarCache.cache.current.weekendRange()
110-
}
111-
112108
func date(from components: DateComponents) -> Date? {
113109
CalendarCache.cache.current.date(from: components)
114110
}

Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,12 +1138,57 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
11381138
}
11391139
}
11401140

1141+
func timeInDay(for date: Date) -> TimeInterval {
1142+
let timeInDay = dateComponents([.hour, .minute, .second], from: date)
1143+
guard let hour = timeInDay.hour, let minute = timeInDay.minute, let second = timeInDay.second else {
1144+
preconditionFailure("Unexpected nil values for hour, minute, or second")
1145+
}
1146+
return TimeInterval(hour * kSecondsInHour + minute * 60 + second)
1147+
}
1148+
11411149
func isDateInWeekend(_ date: Date) -> Bool {
1142-
fatalError()
1150+
let weekendRange: WeekendRange
1151+
if let localeWeekendRange = locale?.weekendRange {
1152+
weekendRange = localeWeekendRange
1153+
} else {
1154+
// Weekend range for 001 region
1155+
weekendRange = WeekendRange(onsetTime: 0, ceaseTime: 86400, start: 7, end: 1)
1156+
}
1157+
1158+
return isDateInWeekend(date, weekendRange: weekendRange)
11431159
}
1144-
1145-
func weekendRange() -> WeekendRange? {
1146-
fatalError()
1160+
1161+
// For testing purpose
1162+
internal func isDateInWeekend(_ date: Date, weekendRange: WeekendRange) -> Bool {
1163+
1164+
// First, compare the day of the week
1165+
let dayOfWeek = dateComponent(.weekday, from: date)
1166+
if weekendRange.start == weekendRange.end && dayOfWeek != weekendRange.start {
1167+
return false
1168+
} else if weekendRange.start < weekendRange.end && (dayOfWeek < weekendRange.start || dayOfWeek > weekendRange.end) {
1169+
return false
1170+
} else if weekendRange.start > weekendRange.end && (dayOfWeek > weekendRange.end && dayOfWeek < weekendRange.start) {
1171+
return false
1172+
}
1173+
1174+
// Then compare the time in the day if the day falls on the start or the end of weekend
1175+
if dayOfWeek == weekendRange.start {
1176+
guard let onsetTime = weekendRange.onsetTime, onsetTime != 0 else {
1177+
return true
1178+
}
1179+
1180+
let timeInDay = timeInDay(for: date)
1181+
return timeInDay >= onsetTime
1182+
} else if dayOfWeek == weekendRange.end {
1183+
guard let ceaseTime = weekendRange.ceaseTime, ceaseTime < 86400 else {
1184+
return true
1185+
}
1186+
1187+
let timeInDay = timeInDay(for: date)
1188+
return timeInDay < ceaseTime
1189+
} else {
1190+
return true
1191+
}
11471192
}
11481193

11491194
// MARK:

Sources/FoundationEssentials/Calendar/Calendar_Protocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ package protocol _CalendarProtocol: AnyObject, Sendable, CustomDebugStringConver
4343
func dateInterval(of component: Calendar.Component, for date: Date) -> DateInterval?
4444

4545
func isDateInWeekend(_ date: Date) -> Bool
46-
func weekendRange() -> WeekendRange?
46+
4747
func date(from components: DateComponents) -> Date?
4848
func dateComponents(_ components: Calendar.ComponentSet, from date: Date, in timeZone: TimeZone) -> DateComponents
4949
func dateComponents(_ components: Calendar.ComponentSet, from date: Date) -> DateComponents

Sources/FoundationEssentials/Locale/Locale.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,10 @@ public struct Locale : Hashable, Equatable, Sendable {
468468
_locale.variant
469469
}
470470

471+
internal var weekendRange: WeekendRange? {
472+
_locale.weekendRange
473+
}
474+
471475
// MARK: - Preferences support (Internal)
472476

473477
package var forceHourCycle: HourCycle? {

Sources/FoundationEssentials/Locale/Locale_Autoupdating.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ internal final class _LocaleAutoupdating : _LocaleProtocol, @unchecked Sendable
175175
LocaleCache.cache.current.firstDayOfWeek
176176
}
177177

178+
var weekendRange: WeekendRange? {
179+
LocaleCache.cache.current.weekendRange
180+
}
181+
178182
var language: Locale.Language {
179183
LocaleCache.cache.current.language
180184
}

Sources/FoundationEssentials/Locale/Locale_Protocol.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ package protocol _LocaleProtocol : AnyObject, Sendable, CustomDebugStringConvert
6363
var numberingSystem: Locale.NumberingSystem { get }
6464
var availableNumberingSystems: [Locale.NumberingSystem] { get }
6565
var firstDayOfWeek: Locale.Weekday { get }
66+
var weekendRange: WeekendRange? { get }
6667
var language: Locale.Language { get }
6768
var hourCycle: Locale.HourCycle { get }
6869
var collation: Locale.Collation { get }

Sources/FoundationEssentials/Locale/Locale_Unlocalized.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,12 @@ internal final class _LocaleUnlocalized : _LocaleProtocol, @unchecked Sendable {
172172
var firstDayOfWeek: Locale.Weekday {
173173
.monday
174174
}
175-
175+
176+
var weekendRange: WeekendRange? {
177+
// Weekend range for 001 region
178+
WeekendRange(onsetTime: 0, ceaseTime: 86400, start: 7, end: 1)
179+
}
180+
176181
var language: Locale.Language {
177182
Locale.Language(components: .init(languageCode: .init("en"), script: nil, region: .init("001")))
178183
}

Sources/FoundationInternationalization/Calendar/Calendar_Bridge.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,6 @@ internal final class _CalendarBridged: _CalendarProtocol, @unchecked Sendable {
215215
func isDateInWeekend(_ date: Date) -> Bool {
216216
_calendar.isDateInWeekend(date)
217217
}
218-
219-
func weekendRange() -> WeekendRange? {
220-
// This has no ObjC equivalent. Get it from a fixed calendar with same identifier.
221-
CalendarCache.cache.fixed(identifier).weekendRange()
222-
}
223218

224219
// MARK: -
225220
//

Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift

Lines changed: 0 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,105 +1152,6 @@ internal final class _CalendarICU: _CalendarProtocol, @unchecked Sendable {
11521152
}
11531153
}
11541154

1155-
func weekendRange() -> WeekendRange? {
1156-
return lock.withLock { () -> WeekendRange? in
1157-
var result = WeekendRange(start: 0, end: 0)
1158-
1159-
var weekdaysIndex : [UInt32] = [0, 0, 0, 0, 0, 0, 0]
1160-
weekdaysIndex[0] = UInt32(_locked_firstWeekday)
1161-
for i in 1..<7 {
1162-
weekdaysIndex[i] = (weekdaysIndex[i - 1] % 7) + 1
1163-
}
1164-
1165-
var weekdayTypes : [UCalendarWeekdayType] = [UCAL_WEEKDAY, UCAL_WEEKDAY, UCAL_WEEKDAY, UCAL_WEEKDAY, UCAL_WEEKDAY, UCAL_WEEKDAY, UCAL_WEEKDAY]
1166-
1167-
var onset: UInt32?
1168-
var cease: UInt32?
1169-
1170-
for i in 0..<7 {
1171-
var status = U_ZERO_ERROR
1172-
weekdayTypes[i] = ucal_getDayOfWeekType(ucalendar, UCalendarDaysOfWeek(CInt(weekdaysIndex[i])), &status)
1173-
if weekdayTypes[i] == UCAL_WEEKEND_ONSET {
1174-
onset = weekdaysIndex[i]
1175-
} else if weekdayTypes[i] == UCAL_WEEKEND_CEASE {
1176-
cease = weekdaysIndex[i]
1177-
}
1178-
}
1179-
1180-
let hasWeekend = weekdayTypes.contains {
1181-
$0 == UCAL_WEEKEND || $0 == UCAL_WEEKEND_ONSET || $0 == UCAL_WEEKEND_CEASE
1182-
}
1183-
1184-
guard hasWeekend else {
1185-
return nil
1186-
}
1187-
1188-
if let onset {
1189-
var status = U_ZERO_ERROR
1190-
// onsetTime is milliseconds after midnight at which the weekend starts. Divide to get to TimeInterval (seconds)
1191-
result.onsetTime = Double(ucal_getWeekendTransition(ucalendar, UCalendarDaysOfWeek(rawValue: onset), &status)) / 1000.0
1192-
}
1193-
1194-
if let cease {
1195-
var status = U_ZERO_ERROR
1196-
// onsetTime is milliseconds after midnight at which the weekend ends. Divide to get to TimeInterval (seconds)
1197-
result.ceaseTime = Double(ucal_getWeekendTransition(ucalendar, UCalendarDaysOfWeek(rawValue: cease), &status)) / 1000.0
1198-
}
1199-
1200-
var weekendStart: UInt32?
1201-
var weekendEnd: UInt32?
1202-
1203-
if let onset {
1204-
weekendStart = onset
1205-
} else {
1206-
if weekdayTypes[0] == UCAL_WEEKEND && weekdayTypes[6] == UCAL_WEEKEND {
1207-
for i in (0...5).reversed() {
1208-
if weekdayTypes[i] != UCAL_WEEKEND {
1209-
weekendStart = weekdaysIndex[i + 1]
1210-
break
1211-
}
1212-
}
1213-
} else {
1214-
for i in 0..<7 {
1215-
if weekdayTypes[i] == UCAL_WEEKEND {
1216-
weekendStart = weekdaysIndex[i]
1217-
break
1218-
}
1219-
}
1220-
}
1221-
}
1222-
1223-
if let cease {
1224-
weekendEnd = cease
1225-
} else {
1226-
if weekdayTypes[0] == UCAL_WEEKEND && weekdayTypes[6] == UCAL_WEEKEND {
1227-
for i in 1..<7 {
1228-
if weekdayTypes[i] != UCAL_WEEKEND {
1229-
weekendEnd = weekdaysIndex[i - 1]
1230-
break
1231-
}
1232-
}
1233-
} else {
1234-
for i in (0...6).reversed() {
1235-
if weekdayTypes[i] == UCAL_WEEKEND {
1236-
weekendEnd = weekdaysIndex[i]
1237-
break
1238-
}
1239-
}
1240-
}
1241-
}
1242-
1243-
// There needs to be a start and end to have a next weekend
1244-
guard let weekendStart, let weekendEnd else {
1245-
return nil
1246-
}
1247-
1248-
result.start = Int(weekendStart)
1249-
result.end = Int(weekendEnd)
1250-
return result
1251-
}
1252-
}
1253-
12541155
// MARK: - Date Creation / Matching Primitives
12551156

12561157
func date(from components: DateComponents) -> Date? {

0 commit comments

Comments
 (0)