Time has rubbed me the wrong way ever since I discovered that Time.now was removed.
I do follow the argument that when creating a Time, which has a timezone, you should consider which timezone. And now doesn’t make you do that.
But there are still a lot of use cases where you don’t want to consider the timezone, as it’s irrelevant and confusing.
Even to experienced Crystalists. For instance, there’s a comment in kemal about file mtime always being stored in UTC. Except it’s not, most filesystems store it as a time interval since an epoch (which differs a bit between platforms, but the principle is the same), but Crystal only have Time so it has to pick a timezone.
And one might consider the choice of UTC a bug, it feels a bit much having to convert timezones when printing out the time of a local file, but I digress.
So I was thinking: why not split Time into Timestamp (or if someone can come up with a better name) and Time, the latter simply inheriting from the former? Timestamp just stores “time since 0001-01-01 00:00:00.0 UTC” as Time does now, and have all the functionality that doesn’t need a timezone (like #-), while Time brings the timezone to the party and have all the handy at_end_of_* and formatting functions.
Of course, you’d still have to add a .to_local to File::Info.modification_time, but now you’re doing it because a point in time doesn’t have a timezone, not because you got the wrong one.
I do remember having a use case where I wanted to represent a point in time without the timezone, which this would work well for. However, I don’t really remember what it was or what I ended up doing. Probably just defaulted to UTC? . Either way, I don’t think I’m sold if it would be worth it just yet without some further discussion.
I’d assume an epoch here would be a Unix epoch in which is that really any different than a Time.utc instance given Unix epoch is always UTC, just represented/stored differently? IMO it makes sense given you’d want the mtime to return the same value on your computer no matter what timezone you’re currently in, which having it be UTC (or unix timestamp) provides.
The most pressing problem is Time is a struct, and you cannot inherit from a non-abstract struct. So the implementation is not as straightforward as simply inheriting from Time.
I never really considered this before since I usually work within the user’s timezone, or I’m working with a monotonic time. For all other cases, I go with a UNIX epoch or a Universal Time when I didn’t want to consider timezones, and would just make a Time as-needed. Like with the Matrix bots I’ve written, times are always handled as Universal Times until the very last moment, at which they become a Time that’s displayed un UTC.
And yes, the epochs are defined based on UTC, but Win32 could just as well have used 28 October 1955 at 22:00 PST as epoch, as the epoch date and timezone only serves as an anchor so we can convert it.
But if I’m presenting that time to the user, they’d prefer that it was local time.
Ah shoot. Guess that kills the idea. While composition (Time having a Timestamp and a timezone) makes sense, it means that you can’t pass a Time to a method accepting a Timestamp which ruins the idea.
In which case File::Info.modification_time must be a bit of a nuisance, almost always requiring translation.
Interesting. As Blacksmoke points out, a Time in UTC is practically the same thing, so why bother? I’d rather like to avoid timezones when not needed, too, but it’s clearly more of a mental thing than a technical reason.
I don’t think there’s much point in talking about concrete implementations before understanding the problem well enough.
What I understand from your description is that you would like a type to represent a local time without any associated time zone information. That would actually be different from a time that’s represented in UTC because UTC is a time zone.
Of course, such a type can make a lot of sense when you need more complex interaction with time systems.
So maybe a shard with enhanced time features could be a good idea?
I’m not sure I agree that people not responding to it implies people aren’t interested or that it’s not relevant. This is the first I’ve heard about that issue in the repo. I don’t watch the issue tracker closely and there are a lot of things there that I don’t see and I don’t post an issue for everything I’d like to see in the language, but it is something I have to work around regularly. Any time I’m dealing with scheduling, I want local times. Work hours are always local time, for example — a lot of folks don’t want to update their work hours just because they’re traveling in a different time zone.
The fact that there is a representation of a timezoneless timestamp in so many major database engines indicates that there is a great reason to have a good representation of them in languages that interact with these databases:
The Crystal drivers for all of these databases all work around this limitation in the Crystal stdlib by just using UTC for them. But that’s a workaround, not a solution.
In fact, I’d argue that additional timeless dates and dateless times would also be a good thing to have, since those database drivers also have to shoehorn these types into Times. It’s not a great workflow and people end up doing silly things when there’s no first-class support for types like this when that’s what’s needed.
It may be a workaround, but it’s a very approachable one. And this kind of workaround is actually quite common.
For example, Int32 is often used to represent completely different units of measurement. Contextual information defines the meaning. You need to take care not mash up different units because the type system can’t differentiate.
But as long as that can be accomplished reasonably well, there’s no need to have more specific value types such as LinearMeasure, NumberOfItems, or Age (or even more specific ones such as Width, Length etc.).
In complex systems it can be very helpful to have fine grained typing help ensure correct behaviour. But in many simple applications there’s a lot of overhead complexity without much practical benefit.
So I think it’s fine to live with some of these workarounds.
Well, the “problem” is a bit meta. It’s not that you can do anything with a timestamp you can’t do with Time, I just see people getting confused about timezones.
Well, no, more the reverse, a global time that disregards timezones. Like unix timestamps.
“1725296525” is the unix time for a particular instant, just as “Sep 02 2024 19:02:05 GMT+0200” denotes the same instant. The thing is that people generally think in “Sep 02 2024 19:02:05”, which is a lot of instances. But unix time “1725296525” is “1725296525” and nothing else. Unix time has no timezones.
Even Time itself uses such a timezone-less scale internally, it just keeps a timezone around for when it needs to be formatted for the user (and the date modifying functions).
The thing is that Time always having a timezone means that people end up thinking about them, even if they’re irrelevant. Can I do Time.local - File::Info.modification_time to get age, when one is local and the other is UTC? The answer is obvious when you know, but this is the stuff that trips up beginners.
Timezones are so annoying that people will go out of the way to avoid them (ref MistressRemilias comment), and pretty much everything decent we work with stores times without timezones (unix time and all its cusins, etc.). Hell, even enlightened non-programmers would love to get rid of timezones, who remember Swatch Internet Time?
It seems inconsistent that we’re fine by DB drivers returning UTC times regardless of what you need, but we can’t have a Time.now because you have to think about timezones.
Okay, thanks for clarifying. So you’re looking for something like Instant in Java’s Date-Time API.
This could indeed be an option.
However, I’m not sure whether that is really an improvement. Then developers would have to think about whether they want a timestamp or a zoned timestamp. The point of conflict shifts from Time.local vs. Time.utc to Time vs Instant. But the former still continues on. So now you have even more concepts to wrap your head around and choices to consider. The intention to simplify things leads to an increase in complexity. That may be entirely justifyable if we see enough benefit from it. But I’m doubtful.
I think for most practical purposes, it’s entirely sufficient to treat a UTC timestamp as an opaque timestamp that refers to an instant on the time line. It just uses Time with UTC as a representation for that instant.
A dedicated type for that is not strictly necessary.
Well, that’s why I thought my original idea was pretty neat. A Time would also be an Instant, and File.utime could signal that it doesn’t care about timezone, and it would be obvious from the return type that DBs doesn’t store the timezone.
It seems like an obvious rookie mistake to think that if you store a Time with a local timezone in a DB, that’s what you’ll get back, but right now that’s not the case.
That’s my issue with Time, it’s a datatype for time and timezone, but the timezone data is often thrown away or pulled out of thin air.
But if Instant is a completely separate type, it gets impractical, and we might as well just use ints.
Couldn’t overloads still allow a similar behaviour?
Or maybe a module could also be a good idea. The benefit would be that it’s quite extensible and could be used by 3rd party shards with more fine-grained time types as well.
Here’s what wikipedia has to say about the matter:
Unix time is currently defined as the number of non-leap seconds which have passed since 00:00:00 UTC on Thursday, 1 January 1970, which is referred to as the Unix epoch. Unix time - Wikipedia
UNIX timestamps aren’t timezone-less, they’re implicitly associated to the UTC timezone! They may feel like they don’t have a timezone, because they’re mere integers, and they can’t have another timezone but UTC.
A file modification time being recorded and interpreted as UTC is by design. The time a file was created or updated musn’t change when the computer changes timezone. Only the translation to local time will change. The absolute time the file was saved is always the same.
I’m not sure about that conclusion. Unix time is defined as the number of elapsed seconds since a specific point in time.
The definition uses UTC as a means to identify that specific point in time. But it could easily do so with a different time zone offset. It doesn’t matter because the elapsed time on the instant time line is independent of the zone offset.
Unix time is currently defined as the number of non-leap seconds which have passed since 00:00:00 UTC01:00:00 +01:00 on Thursday, 1 January 1970*, which is referred to as the Unix epoch.
So I don’t think it’s correct to assume Unix time is UTC.
Crystal’s Time type chooses UTC to represent Unix timestamps. And that’s a reasonable approximation.
But it does invent an association to a specific time zone which is not representative of the source value.
Hmm, wouldn’t that require the cooperation of any Time/Instant using class? If Instant lives in a third party shard, how much support is it going to get?
Besides, I think there’s an inherent beauty in Instant being just that and Time extending the concept. It’s the OO modelling the textbooks promised us.
They are timezone-less, there’s no such thing as Central European Unixtime and Eastern Standard Unixtime (thank god for that). That the anchor point happens to coincide with a nicely formed and rememberable date in the UTC timezone is handy, but it’s only needed so we have a reference point for our (timezone based) time measurement.
Indeed, and I know it’s a very minor nitpick, but it annoys me. I can deal with the concept of “a point in time, without timezone” and realize that if I want to know if it was a Monday, I have to answer the timezone question.
Then again, I wonder if I might be a minority when I run into an API that decides to represent times in local time. Without timezone information. From a company whose business spans at least two timezones.