What is the semantic of DateTime?

What is a DateTime struct supposed to mean?

The documentation indicates:

This datetime can be seen as an ephemeral snapshot of a datetime at a given time zone.

I am not sure to understand precisely the intended use case for DateTime. Does it mean:

  1. A wall time in a given time zone: an unambiguous indication of the time as decided by the politic decision makers of the corresponding time zone. In that case, because politicians can change rules, we cannot predict with an absolute guarantee how long such a future DateTime will occur from now, for example.

  2. A point in time: a reference of time in an unambiguous and explicit referential (such as UTC). It allows for unambiguous time difference calculations with another such point in time. We can also get a wall time in a given time zone from such a point in time.

Looking at the code of DateTime in more details, I believe that its meaning is the use case number 1: a wall time in a given time zone.

I want to build an application with the use case number 2: a point in time. I do not care about time zone. What data structure allow me to unambiguously and explicitly do that in Elixir?

  • DateTime is subject to politician decisions which are reflected in the tzdata. So I cannot rely on DateTime to unambiguously represent a “point in time”.

  • NaiveDateTime allows me to represent an unambiguous time but in an undefined referential.

I could decide to use NaiveDateTime and interpret the data in the UTC referential, which would solve my problem but not in an explicit way.
Alternatively, I could develop a Elixir struct (which could be called UtcDateTime) that explicitly encode that “interpret the data in the UTC referential”.

I believe that use case 2 in pretty common so I am wondering if I am missing something here. Isn’t there already a Elixir struct for use case 2?

How would your imaginary UtcDateTime be different from %DateTime{time_zone: "Etc/UTC"}?

Edit:

This seems not correct. DateTime in elixir stores not only the timezone and datetime, but it also stores offsets it used for it’s calculation. So you should be able to detect those “politically affected” changes when reading a datetime and adjust accordingly.

1 Like

Perhaps what you need is actually a Timestamp:

Timestamps don’t care about political rulers nor timezones :smiley:

I would imagine UtcDateTime to have the same internal struct as NaiveDateTime. UtcDateTime indicates that it is in UTC so there is no need to store time_zone, zone_abbr, utc_offset or std_offset. Knowing that it is in UTC, UtcDateTime could provide a focused API different than NaiveDateTime or DateTime.

I should indeed be able to detect a “politically affected” changes. But what I do with it depends on what the semantic of the DateTime was.

Let’s take an example.

Today, suppose we are “2019-02-04 10:00:00+01:00 CET Europe/Paris”, your application build a #DateTime<2020-02-01 10:00:00+01:00 CET Europe/Paris> and keeps it in memory. Its equivalent in UTC is #DateTime<2020-02-01 09:00:00Z>. On “2019-06-01 10:00:00+01:00 CET Europe/Paris”, France officially announces that they will be using the same time as London starting from “2020-01-01 00:00:00 UTC”. The change is reported in tzdata and Elixir 2 months later. Somewhere in the middle of 2020, you retrieve your original #DateTime<2020-02-01 10:00:00+01:00 CET Europe/Paris> and detect that there is an inconsistency.

  • If the DateTime was intended as a wall time then what you want after processing the inconsistency is #DateTime<2020-02-01 10:00:00+00:00 GMT Europe/Paris> which is equivalent to #DateTime<2020-02-01 10:00:00Z>.

  • If the DateTime was intended as a point in time then what you want after processing the inconsistency is #DateTime<2020-02-01 09:00:00Z> which is equivalent to #DateTime<2020-02-01 09:00:00+00:00 GMT Europe/Paris>.

So yes, I can technically use DateTime to store a point in time but because DateTime could be interpreted both as “a wall time” or a “point in time”, I would need to make all code aware of what I intended by the DateTime. If the way to interpret the data is explicitly tied to the struct used to store the data, then everything is clear and unambiguous. It could be DateTime for “wall times” and UtcDateTime for “points in time”.

1 Like

How you interpret your data is up to yourself. DateTime makes no attempt to be either wall time or a point in time. It’s just data. What that data represents must be defined by your application.

1 Like

Can the maintainers (@josevalim, @Lau, @michalmuskala, @wojtekmach, …) of DateTime confirm that the semantic of DateTime is up to the user? My apologies in advance if you are one of those maintainers LostKobrakai. I would like to be sure before going further in the discussion.

1 Like

I’m not a maintainer, but the docs are saying:

A datetime implementation with a time zone.

This datetime can be seen as an ephemeral snapshot of a datetime at a given time zone. For such purposes, it also includes both UTC and Standard offsets, as well as the zone abbreviation field used exclusively for formatting purposes.

There’s no word on any purpose you should be using the mentioned “ephemeral snapshot” for. It’s up to the user to give the data a purpose.
Also notice the “ephemeral”, which shows it’s not really meant to be used for longtime storage purposes. Most people would be using a database for that anyways, but if you are looking into storing the actual elixir DateTime you might want to keep that in mind. The pitfalls are the same for both: things might change without the data getting updated.

1 Like

Good question.

The short answer is to use DateTime with the “Etc/UTC” time zone.

UTC won’t change its UTC offset. That would make no sense if you think about it.

It is ephemeral for non-UTC timezones because time zone rules change. So if you get a DateTime today for a datetime in the future - let us say midnight 2030-11-01 in New York - and then the time zone rules change. Then if some time before the DateTime happens the time zone rules change making the offset now out of date with those new rules.

NaiveDateTime is inferior in this case because you know that it is UTC and want to specify that it is UTC. NaiveDateTime tells the world that you do not know exactly what time zone it is in. DateTime on the other hand tells both future programmers and code knows that it is UTC in this case. This enables functions to do certain operations that requires the knowledge of the UTC offset. For instance a function cannot convert a NaiveDateTime to unix time without making a very risky non-data-based assumption about the UTC offset of that NaiveDateTime. This is why DateTime.to_unix/1 does not accept a NaiveDateTime struct.

A good thing about using one kind of struct (DateTime) for both UTC and different time zones is that you can have code that can handle both UTC and non-UTC. And this way you can use the appropriate data without having to pretend it is something else just because you need some functionality only available for a different kind of struct. If for some reason you want some code to only accept UTC you could pattern match on the time zone.

I would not create a struct such as UtcDateTime because %DateTime{time_zone: “Etc/UTC”} is more standardized and versatile and compatible with all kinds of libraries.

2 Likes

Ok, it is now cleared for me how I can use DateTime.

Thank you and everyone else involved directly or indirectly in this discussion.

2 Likes