From 1d46517318379c4c7d70b3fed8a598c0d385e9b0 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Mon, 9 Jun 2025 14:46:58 -0700 Subject: [PATCH 01/11] Add a proposal for the definition of a UTCClock --- Proposals/NNNN-UTCClock.md | 108 +++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 Proposals/NNNN-UTCClock.md diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md new file mode 100644 index 000000000..b6d9c5b20 --- /dev/null +++ b/Proposals/NNNN-UTCClock.md @@ -0,0 +1,108 @@ +# UTCClock and Epochs + +* Proposal: +* Authors: [Philippe Hausler](https://github.com/phausler) +* Review Manager: +* Status: +* Implementation: +* Review: + + ## Introduction + +[The proposal for Clock, Instant and Duration](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0329-clock-instant-duration.md) was left with a future direction to address feedback for the need of clocks based upon the time measured by interacting with the user displayed clock, otherwise known as the "wall clock". + +This proposal introduces a new clock type for interacting with the user displayed clock, transacts in instants that are representations of offsets from an epoch and defined by the advancement from a UTC time. + +## Motivation + +Clocks in general can express many different mechanisms for time. That time can be strictly increasing, increasing but paused when the computer is not active, or in a clock in the traditional non-computing sense that one schedules according to a given time of day. The latter one must have some sort of mechanism to interact with calendarical and localized calculations. + +All three of the aforementioned clocks all have a concept of a starting point of reference. This is not a distinct requirement for creating a clock, but all three share this additional convention of a property. + +## Proposed solution and example + +In short, a new clock will be added: `UTCClock`. This clock will have its `Instant` type defined as `Date`. There will also be affordances added for calculations that account for the edge cases of [leap seconds](https://en.wikipedia.org/wiki/Leap_second) (which currently `Date` on its own does not currently offer any sort of mechanism either on itself or via `Calendar`). `Date` has facilities for expressing a starting point from an epoch, however that mechanism is not shared to `ContinuousClock.Instant` or `SuspendingClock.Instant`. All three types will have an added new static property for fetching the `epoch` - and it is suggested that any adopters of `InstantProtocol` should add a new property to their types to match this convention where it fits. + +Usage of this `UTCClock` can be illustrated by awaiting to perform a task at a given time of day. This has a number of interesting wrinkles that the `SuspendingClock` and `ContinousClock` wouldn't be able to handle. Example cases include where the deadline might be beyond a daylight savings time. Since `Date` directly interacts with `Calendar` it then ensures appropriate handling of these edges and is able to respect the user's settings and localization. + +```swift +let calendar = Calendar.current +var when = calendar.dateComponents([.day, .month, .year], from: .now) +when.day = when.day.map { $0 + 1 } +when.hour = 8 + +if let tomorrowMorning8AM = calendar.date(from: when) { + try await UTCClock().sleep(until: tomorrowMorning8AM) + playAlarmSound() +} +``` + +This can be used not only for alarms, but also scheduled maintenance routines or other such chronological tasks. The applications for which span from mobile to desktop to server environments and have a wide but specific set of use cases. It is worth noting that this new type should not be viewed as a replacement since those others have key functionality for representing behavior where the concept of time would be inappropriate to be non-monotonic. + + +## Detailed design + +These additions can be broken down into three categories; the `UTCClock` definition, the conformance of `Date` to `InstantProtocol`, and the extensions for vending epochs. + +The structure of the `UTCClock` is trivially sendable since it houses no specific state and has the defined typealias of its `Instant` as `Date`. The minimum feasible resolution of `Date` is 1 nanosecond (however that may vary from platform to platform where Foundation is implemented). + +```swift +public struct UTCClock: Sendable { + public typealias Instant = Date + public init() +} + +extension UTCClock: Clock { + public func sleep(until deadline: Date, tolerance: Duration? = nil) async throws + public var now: Date { get } + public var minimumResolution: Duration { get } +} +``` + +The extension of `Date` conforms it to `InstantProtocol` and adds one addition "near miss" of the protocol as an additional function that in practice feels like a default parameter. This `duration(to:includingLeapSeconds:)` function provides the calculation of the duration from one point in time to another and calculates if the span between the two points includes a leap second or not. This calculation can be used for historical astronomical data since the irregularity of the rotation causes variation in the observed solar time. Those points are historically fixed and are a known series of events at specific dates (in UTC)[^utclist]. + +```swift +extension Date: InstantProtocol { + public func advanced(by duration: Duration) -> Date + public func duration(to other: Date) -> Duration +} + +extension Date { + public static func leapSeconds(from start: Date, to end: Date) -> Duration +} +``` + +Usage of the `duration(to:)` and `leapSeconds(from:to:)` works as follows to calculate the total number of leap seconds: + +```swift +let start = Calendar.current.date(from: DateComponents(timeZone: .gmt, year: 1971, month: 1, day: 1))! +let end = Calendar.current.date(from: DateComponents(timeZone: .gmt, year: 2017, month: 1, day: 1))! +let leaps = Date.leapSeconds(from: start, to: end) +print(leaps) // prints 27.0 seconds +print(start.duration(to: end) + leaps) // prints 1451692827.0 seconds +``` + +It is worth noting that the usages of leap seconds for a given range is not a common use in most every-day computing; this is intended for special cases where data-sets or historical leap second including durations are strictly needed. The general usages should only require the normal `duration(to:)` api without adding any additional values. Documentation of this function will reflect that more "niche" use case. + +An extension to `UTCClock` will be made in `Foundation` for exposing an `systemEpoch` similarly to the properties proposed for the `SuspendingClock` and `ContinousClock`. This epoch will be defined as the `Date(timeIntervalSinceReferenceDate: 0)` which is Jan 1 2001. + +```swift + +extension UTCClock { + public static var systemEpoch: Date { get } +} +``` + +## Impact on existing code + +This is a purely additive set of changes. + +## Alternatives considered + +It was considered to add a protocol joining the epochs as a "EpochInstant" but that offers no real algorithmic advantage worth introducing a new protocol. Specialization of functions should likely just use where clauses to the specific instant or clock types. + +It was considered to add a new `Instant` type instead of depending on `Date` however this was rejected since it would mean re-implementing a vast swath of the supporting APIs for `Calendar` and such. The advantage of this is minimal and does not counteract the additional API complexity. + +It was considered to add a near-miss overload for `duration(to:)` that had an additional parameter of `includingLeapSeconds` this was dismissed because it was considered to be too confusing and may lead to bugs where that data was not intended to be used. + +[^utclist] If there are any updates to the list that will be considered a data table update and require a change to Foundation. \ No newline at end of file From d77916124fe9600eeeb8e4992a0005ced07d7d98 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:38:22 -0700 Subject: [PATCH 02/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index b6d9c5b20..504c7ecba 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -5,7 +5,7 @@ * Review Manager: * Status: * Implementation: -* Review: +* Review: [Pitch](https://forums.swift.org/t/pitch-utcclock/78018) ## Introduction From f277dfd6b873e5de9a1257b548274c54d1e1d5a4 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:38:30 -0700 Subject: [PATCH 03/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index 504c7ecba..728f47d30 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -3,7 +3,7 @@ * Proposal: * Authors: [Philippe Hausler](https://github.com/phausler) * Review Manager: -* Status: +* Status: Review: Jun 10...Jun 17, 2025 * Implementation: * Review: [Pitch](https://forums.swift.org/t/pitch-utcclock/78018) From 9822ca04184fb45be8f7e0231829c9585bb251dc Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:38:35 -0700 Subject: [PATCH 04/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index 728f47d30..870fc7e2b 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -2,7 +2,7 @@ * Proposal: * Authors: [Philippe Hausler](https://github.com/phausler) -* Review Manager: +* Review Manager: [Tina L](https://github.com/itingliu) * Status: Review: Jun 10...Jun 17, 2025 * Implementation: * Review: [Pitch](https://forums.swift.org/t/pitch-utcclock/78018) From c1c78091fda11ecaaf7429a8e786a07d9a90c15f Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:38:44 -0700 Subject: [PATCH 05/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index 870fc7e2b..f6a8c3715 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -1,6 +1,6 @@ # UTCClock and Epochs -* Proposal: +* Proposal: [SF-0027](0027-UTCClock.md) * Authors: [Philippe Hausler](https://github.com/phausler) * Review Manager: [Tina L](https://github.com/itingliu) * Status: Review: Jun 10...Jun 17, 2025 From 6f66bb6b585199b17755bd72a9b443daf3c62f17 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:38:50 -0700 Subject: [PATCH 06/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index f6a8c3715..533d490b8 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -4,7 +4,7 @@ * Authors: [Philippe Hausler](https://github.com/phausler) * Review Manager: [Tina L](https://github.com/itingliu) * Status: Review: Jun 10...Jun 17, 2025 -* Implementation: +* Implementation: Pending * Review: [Pitch](https://forums.swift.org/t/pitch-utcclock/78018) ## Introduction From 4d03c6ebc0129f3feb9728cab6a76b3984eaf84a Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:39:13 -0700 Subject: [PATCH 07/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index 533d490b8..78e1d77eb 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -47,6 +47,7 @@ These additions can be broken down into three categories; the `UTCClock` definit The structure of the `UTCClock` is trivially sendable since it houses no specific state and has the defined typealias of its `Instant` as `Date`. The minimum feasible resolution of `Date` is 1 nanosecond (however that may vary from platform to platform where Foundation is implemented). ```swift +@available(FoundationPreview 6.2, *) public struct UTCClock: Sendable { public typealias Instant = Date public init() From 2077abe909a60a0c1234c039b980379fabbbf037 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:39:18 -0700 Subject: [PATCH 08/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index 78e1d77eb..fd41a1af1 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -53,6 +53,7 @@ public struct UTCClock: Sendable { public init() } +@available(FoundationPreview 6.2, *) extension UTCClock: Clock { public func sleep(until deadline: Date, tolerance: Duration? = nil) async throws public var now: Date { get } From 4dc91bfaee42647a00d2ce59ad0c4d72d5550138 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:39:24 -0700 Subject: [PATCH 09/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index fd41a1af1..f6df3a05d 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -64,6 +64,7 @@ extension UTCClock: Clock { The extension of `Date` conforms it to `InstantProtocol` and adds one addition "near miss" of the protocol as an additional function that in practice feels like a default parameter. This `duration(to:includingLeapSeconds:)` function provides the calculation of the duration from one point in time to another and calculates if the span between the two points includes a leap second or not. This calculation can be used for historical astronomical data since the irregularity of the rotation causes variation in the observed solar time. Those points are historically fixed and are a known series of events at specific dates (in UTC)[^utclist]. ```swift +@available(FoundationPreview 6.2, *) extension Date: InstantProtocol { public func advanced(by duration: Duration) -> Date public func duration(to other: Date) -> Duration From 202c564d88fb6673912b1f2e0ccefa260a86cc3b Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:39:31 -0700 Subject: [PATCH 10/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index f6df3a05d..4569aff8e 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -70,6 +70,7 @@ extension Date: InstantProtocol { public func duration(to other: Date) -> Duration } +@available(FoundationPreview 6.2, *) extension Date { public static func leapSeconds(from start: Date, to end: Date) -> Duration } From b493c85fc80c9e2cc9456277aa134487450ed85d Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Tue, 10 Jun 2025 09:39:36 -0700 Subject: [PATCH 11/11] Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md index 4569aff8e..b98ec8182 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/NNNN-UTCClock.md @@ -92,6 +92,7 @@ An extension to `UTCClock` will be made in `Foundation` for exposing an `systemE ```swift +@available(FoundationPreview 6.2, *) extension UTCClock { public static var systemEpoch: Date { get } }