[Pitch] Clock, Instant, Date, and Duration

Apple are also not completely consistent with terminology all the time - from NSTableView to the (kinda similar, but also very different) UITableView, to SwiftUI's List, or UIKit's UISwitch vs. SwiftUI's Toggle.

Swift developers also deal with multiple spellings for the same type - from sugar ([UInt8] is Array<UInt8>, Foo? is Optional<Foo>, etc) to type-aliases (Float64 is Double), to associated types (Array<UInt8>.Index is Int).

So it happens, and it's not a big deal. We can use a more appropriate name for Date.

And in terms of overall language complexity, this wouldn't even come close. I don't think it's credible to say, on the one hand, that people can understand result builders, but having Date be aliased to a more appropriate name has such a great cognitive burden that it isn't worth doing.

I agree that it would advance the status quo to make these relationships clearer, except that the name Date leaves us in a strange situation, where WallClock is a clock which tells you a "date". That muddies the advancement significantly - clocks tell time, not dates.

I think it's pretty clear that Date has the wrong name.

23 Likes

On reflection, I think that decision was the right one: CharacterSet was designed at a different time in the evolution of Unicode, and that pervades the API designā€”merely renaming doesnā€™t change that, and keeping the name actually advertises the discordance. The type wasnā€™t being given a different underlying storage, sunk into the standard library, and made the flagship ā€œcurrencyā€ type for a new Unicode scalar protocol in Swiftā€™s next generation of string processing APIs. This is what weā€™re talking about here.

2 Likes

After reading this entire pitch and thread I like where this is going, and look forward to improving these APIs.

However I'm still unclear about why the Clock protocol has a sleep method, especially given the initial definition of clock being:

My expectation of calling .sleep(...) on a clock would not be that the current thread/etc would sleep, I would expect an API that takes a Clock/Duration. My initial thought is that the clock would sleep similar to how some clocks "sleep" when the system does, which doesn't make sense for a wall clock but may for custom clocks (although there are probably better names).

Is there more context/reasoning for this that I missed and/or can someone clarify?

Also wanted to agree with several of the recent posts that we should take this opportunity to rename Date to something like Timestamp.

4 Likes

do we use Time name for anything yet and if not what it would be better useful for if not for this?

1 Like

The name Time has not been used as a typename in any Apple APIs, but I also don't think it would fit at this level. "Time" is a complicated name because it has varying meanings. It can refer to the general concept of experienced temporal flow, but it can also refer to something like "9:41 AM". The former might be OK for the standard library, but the latter definitely is not. So my personal vote would be to avoid the potential confusion and not use Time nor Date at this level.

4 Likes

I don't think Timestamp or Time is any more clear than Date. It has the exact same potential for confusion, because a time is something people generally associate with a point inside a given day. Who really thinks of the current "time" as something that has incremented from some value starting in 1970 instead of "9:42 AM"?

Even if we could all agree on a name for a new type, we will not be wholesale replacing every instance of Date in Apple's SDKs with this new type, because Swift values source and binary compatibility. In my opinion, requiring an explicit conversion between this new type and others is a non-starter. Every new API from this point on would have to decide if it takes one of the two or both (doubling the API surface).

Let's say we then made them something equivalent to a typealias. API authors from this point on would have to make a decision if they want to appear to fit in with the SDK API and use Date or use the new name. Having participated in quite a few discussions about these APIs, I believe the SDK authors will choose to stick with Date for consistency, therefore leaving a permanent bifurcation of names and a splintering of what feels "right for Swift" vs "right for the SDK." Even on platforms with less history, this will still happen. One of the noted benefits of using Swift on server is its compatibility (both conceptually and in source) with the Swift you would write for the client side.

Believe it or not, I too wish we could go back and time and pick new names for some of our API, including Date. However, I think it is best to use the opportunity of sinking date to build this better infrastructure around it and to support it in the most compatible way possible.

1 Like

I respectfully disagree with this. I've never heard of anyone seeing the name "timestamp" and wondering "does this mean 'October 12, 2021'?" But that happens all the time with Date.

20 Likes

For everyone interested; moving Date down and then renaming it and making it source compatible via a typealias would potentially be source compatible but it would not be ABI compatible. There are no facilities I am aware of that we have at our disposal to account for a rename like that.

I don't think that meets the bar for ABI breaking changes (even for Swift 6).

2 Likes

From wikipedia:

A timestamp is a sequence of characters or encoded information identifying when a certain event occurred

1 Like

It could be moved down as Date, and typealiased to the new name. That would be less than ideal, but should work today and be ABI stable.

Otherwise Iā€™m sure a type name field could be added to the @_originallyDefinedIn attribute.

3 Likes

Thereā€™s ample precedent from other languages to make this type Instant or Momentā€”indeed, this is the terminology used in the proposal text.

I am much more sanguine than you as to the consequences either of a typealias approach or of having a entirely separate new API co-exist with legacy APIs.

Additionally, if we need a CGFloat-like mechanism to smooth the way, it is clearly doable. Moreover, such a design has been envisioned in this very pitch for Duration and TimeInterval. And indeed, that conversion has been critiqued for being not nearly as common as CGFloat conversion is, a weakness that would not apply to any hypothetical Date to DistinctTypeConformingToInstantProtocol conversion.

16 Likes

The debate on name got a bit heated there so I missed this part. Basically any sort of clock mechanism needs a wakeup to make it usable other than measuring things. So in order to allow Task to sleep the clock provides a way to wake up after a given instant. This becomes the crux of the real functionality behind this proposal; in order to do things like throttling, or have a deadline in async/await code we need a way to wake up after a given instant which the only source of truth to how to do that is relative to the clock (or things that would use that clock). If you are interested, and I would guess others might be interested too at the progress, I have a start to the implementation here.

Obviously this isn't the whole story to get to those higher level parts but basically it would involve two tasks executing with one sleeping given a clock and another doing work. That can build up primitives to implement lots of really useful interfaces.

1 Like

Would you care quoting the analogous Wikipedia article for Date?

With all respect, that is not the best argument.

(The reason why many of us react so strongly is really that Date is truly misleading - if we could agree on that there are (much) better alternatives maybe we could discuss other possible ways forward)

5 Likes

With respect to the wikipedia source; my comment was to say that the common form of Timestamp is perhaps as ambiguous - Date has at least the refinement in the entries to have it listed as Calendar Date which there used to be a type NSCalendarDate that was deprecated which did represent the concept of a NSCalendar + a NSDate. The definitions with regards to Date as it has been used in swift for the past few years has been by the definition posed by Foundation which dates (pun definitely intended) back to early NeXTStep. I have tried to dig up the original impetus for the naming however I have yet to find anything worth sharing on that.

Is that true?

2 Likes

Additionally, if we need a CGFloat -like mechanism to smooth the way, it is clearly doable. Moreover, such a design has been envisioned in this very pitch for Duration and TimeInterval .

To me, this analogy hits the nail on the head:

  • The proposal recognizes that TimeInterval inadequately represents the semantics of a measurement in seconds.
  • Any APIs which today utilize TimeInterval would require duplication to support Duration, so the proposal reasonably suggests a bidirectional conversion to preserve ergonomics as TimeInterval phases out of usage.

Swapping the types in question:

  • Unlike TimeInterval with e.g. multiplication, the semantics of Dateā€”insofar as the concrete set of operations it providesā€”don't support intrinsically incorrect usage. But discussion here has demonstrated that the name itself suggests responsibilities outside of the type's designated purpose, and an incorrect understanding of the model can just as well result in invalid usage.
  • The second bullet above holds for a renaming of Date.

Finally, I'd argue the proposed story for changing the underlying representation of Date is made cleaner with the introduction of a new type: roundtrip serialization of both Date and a new type can remain lossless, with lossiness pushed into the implicit conversionā€”a pattern precedented by CGFloat-to-Double conversions on 32-bit platforms.

8 Likes

I wanna point out thatā€”unless the proposal seeks to change this, which is a unclear to meā€”this is not the case.

To verify, use calendar APIs to get the beginning of each year in utc since 1970, then print the dates timeIntervalSince1970. All cleanly divisible by 24x60x60.

Furthermore, the only affordance for measuring time between two Date instances, timeIntervalSince(_:), just subtracts the timestamps. No leap second handling to be found here.

No leap second handling to be found in Calendar APIs either. Creating a Date just before a past leap second, then using calendar API to step that date forward second by second will skip right over the leap second.

So in conclusion, the current model of Date is neither a true physical timestamp nor a human date, but a weird hybrid of the two that was maybe useful enough to justify when developers had to work with raw timestamps more routinely, but feels rather archaic today.

Now, if the proposal was to change this, I would welcome this very much. The Date types lying about the time interval between some dates that meet specific criteria (with no indication of this I could find in any documentation, I might add; indeed searching for "leap" in the Date docs yields no results at all) to me feels kind of akin to int overflow, and very non-Swifty.

9 Likes

Indeed. I think we should think of Date as "calendar seconds" since reference date, whereas "real seconds" since reference date should include leap seconds.

I think it really needs to be brought up that accounting for leap seconds would incur a file read from disk. On Darwin and Linux the source of truth for those adjustments to APIs like gettimeofday or clock_gettime are housed in /usr/share/zoneinfo/leapseconds. I would imagine that a random file read during time based calculations would be quite unexpected. Also that drift for leap seconds is not deterministic so any future date that would be scheduled would potentially (when serialized and deserialized) would be askew from any new updates to that database.

7 Likes

NSDate is very similar to unix time in this regards, see the table that shows how the leap second is crossed.

the more i think about it the more i realise that leap seconds concept was a mistake that should have never been done in the first place and that should be corrected asap (i hope as soon as 2023). that timeIntervalSince1970 on a whole day boundary is divisible by 24x60x60 is "a good thing" that i hope will carry over to the NewDate API. leap years, daylight saving time switching, and changes of those switching rules (that can happen due to countries boundaries changes and other politics) are resolved at levels higher than this proposal (Calendar / DateComponents / DateFormatter).

the new and old Date API will have to coexist for quite some time. we'll need ways to convert between new and old dates, ideally the new date round tripped through old date shall come unchanged and vice versa. old API of the form:

func foo(_ oldDate: Date) { ... }

could be called with:

foo(Date(newDate))

and face lifted to provide the version that support new dates (leaving the old date version as is for now):

func foo(_ newDate: NewDate) { ... } // same but for new dates

old API can be reimplemented on top of the new API:

func foo(_ oldDate: Date) {
    foo(NewDate(oldDate))
}

this is probably the least complex part of the proposal: providing a facelift version of a number of API's (like 200 or 300 hundred of those). initially we can facelift a small number of most frequently used APIs.

i believe that Time name coolness outweighs these concerns and if not for this purpose of being used as a "new Date currency" I see no better option how it can be used. Timestamp is not too bad, Date is quite bad (but reusing Date name per se is not be possible for a number of reasons starting with source and ABI compatibility, so that's not really an option unless we really want to name the type as, literally, NewDate which i hope we don't).

:rofl: