[Review] SF-0027: UTCClock and Epochs

Hello community,

The review of SF-0027: UTCClock and Epochs begins now and runs through Jun 17, 2025.

Reviews are an important part of the Swift-Foundation evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by DM. When contacting the review manager directly, please include proposal name in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review
through constructive feedback. When writing your review, here are some questions you might want to answer in your review:

  • How much effort did you put into your review? A glance, a quick
    reading, or an in-depth study?
  • If you have used other languages or libraries with a similar
    feature, how do you feel that this proposal compares to those?
  • What is your evaluation of the proposal?

More information about Swift-Foundation review process is available at

https://github.com/swiftlang/swift-foundation/blob/main/CONTRIBUTING.md

Thank you,

Tina L
Review Manager

2 Likes

Great to see this proposal come up for review and +1 on adding this new clock. My only question is about how we expect Task.sleep with a UTC clock to be handled by executors. @al45tair's proposal to add a new SchedulableExecutor allows executor to handle Task.sleep. How do we expect executors to implement sleeps based on a UTC clock?

Can the authors go into some detail about why UTCClock belongs in Foundation rather than the standard library? Just about any other language I can think of puts wall-clock API in the standard library. Is "Date should be the clock's Instant type" sufficient justification?

Some discussion about why the Mac OS X epoch (2001) was chosen over the UNIX epoch (1970) would be helpful.

9 Likes

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.

What is the signature of this proposed addition to Date? It is not given in the subsequent code block—or is this left over from an earlier edit and no longer actually proposed?


Looking at C++ <chrono> APIs, I'm seeing that leap_second_info gives two pieces of information: is_leap_second and elapsed.

With what's in this proposal, it's pretty easy to compute the equivalent of elapsed—although I'd echo @grynspan here that Date not using the UNIX epoch is a potential footgun (both for this and probably other, more important operations) and it'd be good to discuss that choice.

It seems reasonable and straightforward (from an API design standpoint) to offer a one-shot way to recover the equivalent of is_leap_second though: in the same extension on Date, one could imagine an instance property isLeapSecond: Bool.

Particularly if the accessor is UTCClock.systemEpoch, I would expect this to be the NT Epoch (1/1/1601) not the macOS Epoch (1/1/2001) or Unix Epoch (1/1/1970) on Windows.

5 Likes

Perhaps it would take the pressure off of converging on some semantics for systemEpoch that would have to either differ between systems or be a misnomer for all but one system, if Date also vends a unixEpoch that is fixed for all platforms. I suspect users might want to reach for it more often than the former.

1 Like

I worry about the portability aspect. When interacting with timestamps outside of the library we would need to ensure that the right epoch is used as non-Swift libraries may have expectations of native epochs being used.

Yeah, hard to know how to square this. There is the portability aspect but there is also the on-its-face contradiction of a systemEpoch that isn't the system's actual epoch...

2 Likes

Does leapSeconds(from:to:) allow for a negative leap second (where 23:59:59 is skipped)? Should it take a DateInterval instead of two Date parameters?


(Pull request swiftlang/swift#81300 hasn't been merged yet.)

Overall, yes, but mostly I'm surprised to see this as a Foundation proposal rather than as a stdlib proposal. Of course, doing it in Foundation doesn't necessarily preclude later adding it to the stdlib, but it might make it harder to do so.

Every other language I can think of includes this functionality in the stdlib:
C gmtime, C++ utc_clock, Rust SystemTime, Ruby Time::utc, Python gmtime, Haskell getCurrentTime, etc.

Is the argument that the stdlib doesn't want to include more large chunks of ICU, sufficient justification to put this in Foundation? Or should we take a more pragmatic approach like Rust, and reduce the functionality to the point that we only need to call clock_gettime?

The topic of whether Date should move to the stdlib, and whether Date should change its representation from Double to something accurate, comes up pretty regularly on these forums. It seems like this proposal is an opportunity to reconsider both things, or to consider a third option such as adding an accurate UTCTime to the stdlib, and conversion methods to & from Date in Foundation.

I agree with the other reviewers that the name of the epoch property shouldn't be misleading. Notably in prior art, Rust provides UNIX_EPOCH on all platforms, but doesn't offer the system epoch. I think the argument on that page about needing this constant for a lot of internet protocols is good justification to provide the unix epoch unconditionally (whether or not you also want to provide the less-standard Foundation or Windows epochs).

8 Likes

AFAICS this is being proposed in Foundation because the deadline being a Date which is a Foundation type. We should thoroughly examine this though, and it should not just automatically result in this clock landing in Foundation where Date conveniently exists today.

I worry a bit that when we get to implemented deadlines in the stdlib/concurrency we may find ourselfes wanting to use UTCClock in almost any real application dealing with deadlines cross process, as we'd be relying on the fact that both systems have a notion of UTC I think.

Specifically, an interesting example here is Go's Deadline and time.Time. That time is a bit "special" because:

In addition to the required “wall clock” reading, a Time may contain an optional reading of the current process's monotonic clock, to provide additional precision for comparison or subtraction. See the “Monotonic Clocks” section in the package documentation for details.

The reason I mention this isn't to do the weird two-clocks trick as Go does, but to highlight the necessity of the UTC clock. The UTC clock can be conveniently used when crossing machine boundaries and is especially useful for setting deadlines which will cross machines/devices, which is something we're interested in doing. Otherwise we'd have to do conversions to an UTC clock and again, be forced into pulling in Foundation for this one single type whenever we deal with this situation.

Have we thought about conversions from monotonic clocks to an UTCTime by the way?

Let's entertain this idea:

What if the concurrency library gained a UTCTime type that makes it clear it is not calendrical date, but is good enough for our UTC deadline needs? Along with the UTCClock which would use the UTCTime for its Instant.

~~

The original proposal says:

SE-0329: Clock, Instant, and Duration / Lowering of Date/UTCClock

Originally the proposal included a concept of lowering Date to the standard library in addition to altering its storage from Double to a Duration. There were strong objections on a few fronts with this move which ultimately had convincing merit. The primary objection was to the name Date; given that there was no additional contextual API within the standard library or concurrency library this meant that Date could easily get confused with the concept of a calendrical date (which that type definitively is not). Additionally it was rightfully brought up that Date is missing concepts of leap seconds (which has since been accepted and proposed as an alteration to Foundation) because we see the utility of that as an additional functionality to Date.

Also in the original revisions of the proposal we had a concept of WallClock. After much discussion we feel that the name wall clock is misleading since the type really represents a clock based on UTC (once Date has a historical accounting of leap seconds). But furthermore, we feel that the general utility of scheduling via a UTC clock is not a common task and that a vast majority of clocks for scheduling are really things that transact either via a clock that time passes while the machine is asleep or a clock that time does not pass while the machine is asleep. That accounting means that we feel that the right home for UTCClock is in a higher level framework for that specialized task along side the calendrical calculation APIs; which is Foundation.

The first part are valid improvements that still have merit -- we could have a new integer based type that is the time, accounts for leap seconds, and lives in the concurrency library.

The second part, about it not being "common" I'm not so convienced about, I do feel it definitely can be pretty common to set deadlines based on some UTC time, especially when crossing devices. And I've had specific requests from network teams to provide deadline APIs in Concurrency that will scale nicely beyond a single device. Others have shown already that other languages readily offer such clocks in the core of a language, and I do agree with this sentiment as well.

So I'd still like to consider having this be a standard library type, and if not, we have to be really clear about why not and what the impact will be on practical use of deadline APIs in Swift concurrency.

15 Likes

If the primary concern there is simply the name Date, then the stdlib type could be named WallClockInstantValue (or… not that) and a typealias Date = ... could be added in Foundation along with calendar/timezone/etc. extensions.

I think that's beyond the scope of this proposal though.

If we're offering UTCClock.sleep(), or equivalently allowing Task.sleep() on a UTCClock, how do we cope with time adjustments?

Using UTC does free us from the troubling issue of daylight saving time, but what if we do a sleep() and either NTP or the user adjusts the system clock? Equally, what if leap seconds are added or removed?

Is there a guarantee that we will never wake early? i.e. if we ask to wait until 5am UTC, there is no circumstance under which we will find ourselves woken up at 4am UTC? (Obviously there's a race condition here — if we wake at what is currently 5am UTC and the user immediately moves time back an hour, that's another thing — but do we guarantee that, for instance, if the user moves time back an hour, we won't inadvertently then wake at the wrong time)?

I also share the concern about calling Date(timeIntervalSinceReferenceDate: 0) systemEpoch. That is true on Darwin. It isn't the system epoch on other platforms, and I would, like @compnerd, expect January 1st 1601 on Windows and January 1st 1970 on UNIX more generally.

We could maybe have foundationEpoch or even just epoch and set that to Date(timeIntervalSinceReferenceDate: 0), then systemEpoch would be the native epoch of the system on which we are running? That would actually be quite useful as it would provide the relevant constant for converting timestamps from the system native representation.

2 Likes

This can be used not only for alarms, but also scheduled maintenance routines or other such chronological tasks.

For alarms and other items scheduled for human beings, the interaction between UTC and local time can be tricky. It isn't necessarily the right thing to do to use UTC — that's almost always what you want for computers, but not for people.

For instance, if I have a wake-up alarm set for 7am, I want it to fire at 7am local time wherever I am, not at whatever UTC time was equivalent to 7am at the point at which some thing decided to tell the runtime or OS to sleep. Equally, a meeting might be set for 2pm Cupertino time or 11am London time, and I wouldn't necessarily expect that to change when DST kicked in.

I wonder if it's a good idea to suggest this is useful for alarms, therefore. It's fine for maintenance routines or timers run by a computer for a computer.

2 Likes