[Review] SF-0027: UTCClock and Epochs

Adding UTCClock in Foundation is included in the now-accepted 0329 Clock, Instant, and Duration.

I can see the desire of adding a Date like type and hence a UTCClock-like type in some other lower level libraries, but that should be a separate discussion. I'd really like us to keep the discussion relevant to this proposal. Having UTCClock in Foundation doesn't preclude us from adding another clock there when we have that assumed type available.

1 Like

SE-0329 already having specified this API is a surprising wrinkle, but I think "where should this type live" is directly relevant to this proposal, and I think the existence of UTCClock in Foundation will directly affect our ability to have such a thing in the stdlib (if nothing else, we couldn't reasonably reuse the name, that would be immensely confusing). And I think that the fact that SF-0027 exists at all (as opposed to simply having been implemented in Foundation a few years ago when the rest of the clocks were added to the stdlib), implies that people don't think that SE-0329 was definitive on the matter.

But I guess to "keep the discussion relevant to the proposal", I have to say:

-1 from me.

  • This functionality is basic & fundamental, and belongs in the stdlib, not Foundation.
  • Date as it exists makes for a poor Instant type; we should use a struct which wraps an integer.
  • The obvious conclusion from those two thoughts is that the stdlib should get UTCClock and UTCClock.Instant, and Date should get init(nearest: UTCClock.Instant) and var nearestUTCInstant: UTCClock.Instant { get }
6 Likes

We went over these points in detail in the 3 pitch threads and 3 associated review threads for SE-0329.

This proposal is about introducing UTCClock as described there, not rearchitecting Date.

This one fundamentally has to use Date - that's the point, that it represents time that works with calendrical calculations.

On the positive side, though, Date is not frozen. @scanon was recently looking into possibly representing it with a 128-bit integer instead of the current Double. That has its own set of tradeoffs, of course, like obviously using twice the storage. It's worth considering, separately.

edit: I should clarify that Date itself is not "calendrical", of course, representing a point in time. But all calendrical API in Foundation uses Date as the primitive type.

1 Like

Well
as far as mainline Evolution goes, the approach we’ve taken is that unimplemented portions of accepted proposals expire after a year and what is considered approved is “shrunk to fit.” (Foundation is of course free to take whatever approach it prefers for its own process.)

2 Likes

The review period is technically over, but since there's been no acknowledgement of that nor an announced decision, here's mine:

-1 we should not accept this, for the reasons that @KeithBauerANZ stated, plus some others:

  1. We should stop propagating Date.

    I agree with the calls to make UTCClock.Instant its own type (and be in the standard library!), and not a typealias for Date. Yes, the vast majority of immediate use-cases would be to turn it into a Date for use with the calendaring APIs, but by tying those APIs together, we restrict ourselves from ever improving those APIs by introducing a better type in the future (and I think we all have agreed that Date is a bad API).

  2. The example is anti-compelling

    This is the best proposed example of why a developer would want to use a UTCClock:

    if let tomorrowMorning8AM = calendar.date(from: when) {
      try await UTCClock().sleep(until: tomorrowMorning8AM)
      playAlarmSound()
    }
    

    But
 no one who actually wants to write an alarm feature into a product would ever write it this way. There is no resilience in the face of anything. If the process crashes, the alarm is lost. If the user changes timezones overnight, the alarm is wrong.

    With such an utter absence of serious examples and rationale, I'm left wondering if this is a serious proposal at all. If this is the best rationale that can be mustered for introducing the feature... do we need this at all?

  3. The motivation is wrong

    The motivation section says:

    ... 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.

    Except
 a UTCClock is independent of calendars. A UTCClock has no notion of "time of day" or calendars. It, like ContinuousClock and SuspendingClock, only returns instantaneous values; it returns the temporal equivalents of latitude and longitudinal coordinates from a different frame of reference.

    If we want to build a Clock-conforming type that performs calendar-based time manipulations, then that Clock should have a Calendar. A UTCClock does not, and so this motivation is not only wrong, but it perpetuates misinformation about temporal types and how developers should use them.

13 Likes

We should stop propagating Date .

This is not a goal of the Foundation project, nor of the Swift project.

With such an utter absence of serious examples and rationale, I'm left wondering if this is a serious proposal at all.

I know that you're passionate about date and time API. We've had productive and polite conversations in threads on these forums about past APIs on Calendar. I hope, though, that we can have a discussion here that does not attack the personal motivation of the author.

Except
 a UTCClock is independent of calendars. A UTCClock has no notion of "time of day" or calendars.

This is pedantically correct, which of course the best kind of correct, but I think the intention is clear. The UTCClock is of course based on a fixed point in time on a certain calendar, which defines the epoch of Date. The difference with Continuous and Suspending clock is that the epoch is fixed across reboots. That is a clearly useful property. Given that we have a type which represents this concept exactly, which is Date, and we are not replacing it (see point 1), the rest of the logic in this proposal flows pretty clearly.

I'm not attacking the personal motivation of the author. I'm attacking the fact the proposal itself does a poor job of communicating why anyone would want to use it. There's another long thread going on this site about the lack of documentation on evolution proposals, and a key point in there is how often developers get dumped into the proposal documents themselves when trying to glean information.

Someone wondering about UTCClock is going to get dumped into this proposal and be utterly baffled as to what they're actually supposed to do with it.

Where is the discussion on how relying on Date() or Date.now is relying on implicit global state? Where is the discussion on dependency injection? Where is the same level of thought that went into discussion around APIs like Task.sleep(...), which go out of their way to inject a clock so that developers can have more control?

Coming across a proposal with an unserious example leads people to think the API itself is unserious. This is not a critique of the personal motivations of the author. This is a critique of the document as it is currently written.

The "perpetuation of misinformation" is the inclusion of calendar-specific nomenclature in a type that has no notion of the calendar, leading adopters to continue resorting to error-prone hacks like blindly reaching for Calendar.current and assuming that it's "correct".

The point that should be getting communicated about UTCClock is that it provides the instantaneous point-in-time values that closely follow what the device might consider to be its current perception of "now". From there those values can then be mapped into appropriately user-facing concepts by using the Calendaring APIs.

Note that UTCClock is not providing values that match what the user thinks "now" is
 unless there's an NTP clock hiding in its implementation?


These criticisms of the proposal itself aside, I still believe that this proposal (like the Progress proposal) is defining a concept that belongs in the standard library, and not in Foundation.

9 Likes

:thought_balloon:
I wonder if Ecosystem Steering Group ought to take the lead in this kind of discussion that is controversial whether the new API should belong to Foundation or another library. Whereas the Foundation workgroup has, of course, the authority over swift-foundation, it is indeed covered by Ecosystem Steering Group now.

You do get pretty judgy in your feedback. Calling the proposal “unserious” is certainly crossing a line of sorts; I don’t know what it means if it’s not an attempt to cast aspersions on the author.

It’d be nice if you could dial it back and focus on just making technical arguments without always looking for ways to underline how unhappy such-and-such proposal makes you.

8 Likes

The proposal claims that Date offers nanosecond-resolution (and it did in 2001), but it seems like it offers ~0.1”s resolution today (30 bits to get from 2001 to 2025, and another 29 for nanosecond resolution, is a bit more than the 53 available in Double):

 12> Date().timeIntervalSinceReferenceDate
$R3: TimeInterval = 773198503.145594
 13> 773198503.145594 + 0.000000001
$R4: Double = 773198503.145594
 14> 773198503.145594 + 0.000000010
$R5: Double = 773198503.145594
 15> 773198503.145594 + 0.000000100
$R6: Double = 773198503.14559412

And obviously this gets worse every year; in another 25 years it won't even be microsecond-resolution.

Obviously the minimumResolution of Clock wasn't really intended to support a floating-point Instant, but is "nanosecond" the right thing to say?

6 Likes

We shouldn't start discussing the Ecosystem Steering Groups role as part of this review thread. I'm happy to discuss this in a separate dedicated thread or if you wish to share your opinion more privately then feel free to send a direct message to the ESG on the forums. However, I just want to briefly reply here on behalf of the Ecosystem Steering Group. The ESG doesn't have authority over the evolution of Foundation. Our charter contains a section around workgroup authority where we explain this in more detail. Quoting the relevant bits here:

These workgroups are the domain experts in their respective areas; hence, the authority is with the respective workgroups. Currently, the Foundation workgroup has the authority over swift-foundation and swift-corelibs-foundation.

During the ESGs bring-up we had various discussion around this topic and we feel that the experts should be in-control of evolution. This is important to enable the individual workgroups to have ownership over their respective area but also to avoid creating a bottleneck through the ESG. The ESG, is focused on ensuring that the various workgroups under us are all pulling into the same direction by providing them with the necessary resources, tools and governance to build out a flourishing ecosystem.

4 Likes

Thank you for the reply.
I had such a question because I didn't personally see the actual activities of the ESG yet, sorry. I just thought the ESG could advise on which workgroup should take the evolution (not discussing the evolution itself).
I understand unnecessary bottleneck should be avoided though...:pensive_face:

I share the concerns about Date. I find the floating point precision to be such an oddity, and an increasingly "real" problem when interacting with other systems or components.

In my opinion, Date simply is no longer up to the task of representing a "point-in-time" properly in today's world where ”s or ns precision are increasingly common. I, for one, am tired of nervously double-checking if some rounding shenanigans might break me whenever I see Date in some API.

To me, bringing the modern-ish API for a UTCClock but basing it on this antiquated (and fundamentally flawed) Date type feels almost like a wasted effort. It will have a low shelf-life and probably not be used in cases where precision matters.

I get it. I also get the sentiment of "this is the foundation evolution, and whatever the stdlib might do in the future is totally out of scope for this review".

However, I think a "proper" type for utc timestamps and durations in stdlib (or similar) are inevitable. (If anything, they should be worked on rather sooner than later). Acting like this UTCClock has nothing to do with it feels a bit silly.

In summary, I think the UTCClock as pitched gives us very little and ultimately rides a dying horse. We should start ripping the bandaid off.

8 Likes

Largely agree, we have extensive need for UTC timestamps with nanosecond resolution and rolled an internal timestamp type for that, which seems like a hole in the stdlib/foundation - a proper high resolution point in time currency type that is unified for the whole language would be good and we would much rather use that.

Whole can of worms of course with the desired supported time range and accuracy, but to move away from FP seems like a no-brainer for something new.

3 Likes

I would love for Date to be a part of stdlib, that would solve SO many dependencies on Foundation for me, enabling users to slim their binary size or use Date and libraries relying on Date in environments where Foundation isn't available.

3 Likes

There seems to be a few issues that deserve additional responses:

Per the question posed by @al45tair about the proposal about executors. Since Clock is a generalized protocol that many folks could be implementing it makes little difference if the scheduler handles a Foundation defined UTCClock or a Swift Concurrency defined SuspendingClock or an app defined AnimationClock; however the proposed implementation detail will be that UTCClock will function almost identically to the runtime funnel points as ContinuousClock and SuspendingClock. As it currently stands there is technically a third enumeration point of the clock runtime function that does exist today that just needs a touch of extra code to enable. This is the proposed codepath it will use.

The question of "Does leapSeconds(from:to:) allow for a negative leap second?"; yep - if you pass the interval in reverse it will give the negation of the value.

There is a final question, or perhaps more confusion about Date. That type is not a civil or localized type. In order to have a localized value you must ferry both the point in time but also the system in which to represent that point in time; specifically you need a Date plus a Calendar plus a TimeZone. Calendar does have APIs for dealing with that and if folks want to build a mechanism that sleeps until a given point on a calendar then UTCClock itself is insufficent. It is part of a solution that someone could craft but UTCClock does not "deal with" daylight savings or leap months/years. Those are the role of Calendar to calculate a Date, and it is the pervue of the application to determine how to sleep - is it to the date that represents an inclusion of daylight savings? or is it to the point not including it? A NTP adjustment however will be included in UTCClock since that is the drift between the local machine's representation of UTC and the regarded truth (for as much as the underlying systems on that machine can do so).

I think it was actually @FranzBusch who asked about that.

This is why I wrote in a previous post that I don't think UTCClock is suitable for alarms, as claimed in the proposal. Alarm behaviour is complicated, and the UTC Date at which your program needs to wake could change depending on time zone.

I can imagine we might actually want, in future, to add a separate AlarmClock type that uses calendar dates to implement behaviour more suited to that case. That type might need to watch for system time changes or time zone changes and potentially adjust its wake times somehow.

I'm not sure this answers my question about how this interacts with time adjustments. If someone does UTCClock.sleep(until: <some point in time>) and the system clock is updated, whether by the user or by NTP, or because of a leap second we learned about through some mechanism, what is the behaviour? What guarantees do we make? For instance, if the adjustment means that the point in time has now passed, do we wake immediately? If not, when do we wake? If at time t I ask to sleep until t + 1hr, and someone adjusts the system time to t + 2hrs, is it acceptable that we might not wake up until t + 3hrs? Equally, if the adjustment means that the point in time is now further away, might we accidentally wake early? Is that acceptable?

I think we should define the expected behaviour in the proposal — even though I realise your plan is effectively to pass the timestamp through to Dispatch and let it handle it.

6 Likes

What would be the behaviour on platforms that don't have Dispatch?

3 Likes

The time zone is accounted for (implicitly) in the example. It may help the proposal if it were spelled out, but it is actually incorporated into the current calendar.

let calendar = Calendar.current // this has a time zone
var when = calendar.dateComponents([.day, .month, .year], from: .now)
when.day = when.day.map { $0 + 1 }
when.hour = 8
// ...

For example:

let c = Calendar.current
print(c.timeZone) // America/Los_Angeles (for me)

And this line:

calendar.date(from: when)

uses the calendar's time zone by default, or the one in the components if set.

In general, you are correct that calculating the right Date is fairly complicated and comes with lots of nuance. In Foundation's calendar APIs, we split that responsibility out into the Calendar type, leaving Date to be just a timestamp. There's never one right answer to choose. Sometimes alarms are really meant to be at a specific time of day and sometimes they are meant to adjust +/- an hour if a DST transition happens to occur.

Using your example, a meeting set for 2pm Cupertino time or 11am London time is calculated by finding the right Date first - the Date does not change after the fact if a DST boundary is crossed. Calendar calculates it using the data from time zone and the input to the date(from:) API. It has options to help in DST transitions, like this one:

    /// Determines which result to use when a time is repeated on a day in a calendar (for example, during a daylight saving transition when the times between 2:00am and 3:00am may happen twice).
    public enum RepeatedTimePolicy : Sendable {
        /// If there are two or more matching times (all the components are the same, including isLeapMonth) before the end of the next instance of the next higher component to the highest specified component, then the algorithm will return the first occurrence.
        case first

        /// If there are two or more matching times (all the components are the same, including isLeapMonth) before the end of the next instance of the next higher component to the highest specified component, then the algorithm will return the last occurrence.
        case last
    }

In the end, any kind of "AlarmClock" that attempted to accommodate for all of this using Calendar would probably just end up replicating Calendar's API, using it to calculate a Date, then scheduling it using UTC.

2 Likes