Semantics of `InstantProtocol`?

i have a Unix-based instant type called BSON.Millisecond. it looks like this:

extension BSON
{
    @frozen public
    struct Millisecond:Hashable, Equatable, Sendable
    {
        /// Milliseconds since the epoch.
        public
        let value:Int64
    }
}

this type isn’t very useful for date computation, so i was looking into ways to fit it into the semantics of InstantProtocol, in the hope that it might reveal some useful API directions.

the problem i’m running into is that InstantProtocol is quite wedded to the concept of a Duration, and i am struggling to reconcile that with the durationless nature of time_t.

naïvely, we might sketch an associated Duration type for BSON.Millisecond like:

extension BSON
{
    @frozen public
    struct Milliseconds:Hashable, Equatable, Comparable, Sendable
    {
        /// Number of milliseconds
        public
        let rawValue:Int64
    }
}

extension BSON.Millisecond
{
    public typealias Duration = BSON.Milliseconds
}

but InstantProtocol has the requirements:

func advanced(by: Self.Duration) -> Self
func duration(to: Self) -> Self.Duration

they are completely undocumented as of 5.9, but based on how Strideable works, it’s plausible to assume InstantProtocol has the semantics

x.advanced(by: d) == y &&
x.duration(to: y) == d

for duration(to:), well that might go through difftime(_:_:) which ought to handle leap seconds correctly. but i’m not aware of any way to implement advanced(by:) in a consistent manner; the “C way” seems to be to just add seconds as an integer (1, 2) which can’t possibly handle leap seconds correctly, because leap seconds don’t exist in Unix dates.

i’m not sure what the solution is here, but some thoughts i have include:

  1. Unix dates don’t have associated Duration types, they merely have “delta” types that must never be interpreted as quantities of time. for example, to advance a date by 2 seconds over a leap discontinuity, you would “advance” it by 1 second, even though the increment is a two second duration.

  2. if we accept that duration(to:) can be inconsistent with advanced(by:), we should probably document that in InstantProtocol.

  3. if not, are Unix dates even good candidates for an InstantProtocol conformance in the first place?

on leap seconds

Isn't difftime just "time2 - time1" (where "-" is an integer subtraction with no fanciness)?

I wonder how do you test leap seconds? If I set the current time to 1972-06-30T23:59:50 (ten seconds before the leap second) and start creating a new folder every second I will not see a folder with the date "1972-06-30T23:59:60" no matter how I try. Observing the pace of a change from 23:59:50 to 00:00:00 on that day (best to do in the "Date & Time" control panel as the menu bar clock is somewhat laggy) I do not see the change from 23:59:59 to 00:00:00 taking any longer than a change from, say, 23:59:58 to 23:59:59 and there is never "23:59:60" on the clock. Is that just macOS and does it work better on other OS-es?

Leap seconds are abolished in 2035, I wonder could we just ignore we ever had them between 1972 and 2016?

Yes.

Most (I would actually hazard approximately all) software effectively ignores them. Most software doesn’t care when the clock rolls backward, which can happen at any time on a machine with ntpd running. So a machine that ignores leap seconds will just repeat an additional second whenever it re-syncs its clock after the leap second occurs.

2 Likes

huh. today i learned difftime ignores leap seconds.

i guess to be fair, we don’t really have a precedent for Duration representing a interval of physical time, since we are already using Swift.Duration as the type witness for SuspendingClock. maybe we can just hand-wave the problem away by saying a hypothetical SystemClock “suspends” itself during leap seconds.