Working with datetimes from Vue Client to Absinthe and Ecto

Hello everyone, I am learning Elixir, Absinthe and Ecto and I am making an auctioning site to try and learn multiple aspects of these technologies. But I am super stuck on working with DateTimes. I let my users set the ending time on the auction via HTML

<input type="datetime-local"  value="2017-06-01T08:30">

Now I receive this Data object from the input. And can get ISO or UTC string. The issue I have is how to work with this? I have the following schemas

My ecto schema

  schema "items" do
    field :title, :string
    field :description, :string
    field :ends_at, :utc_datetime
    timestamps()
  end

And my Absinthe schema

  object :item do
    field :id, :id
    field :title, :string
    field :description, :string
    field :ends_at, :date_time
  end

  scalar :date_time do
    parse(fn input ->
      case DateTime.from_iso8601(input.value) do
        {:ok, datetime, _} -> {:ok, datetime}
        _ -> :error
      end
    end)

    serialize(fn date ->
      DateTime.to_iso8601(date)
    end)
  end

Now I need to show every user the correct time that the auction is ending based on their timezone, but I have no idea how to go about this. There are so many different terms being thrown around, timestampz timestamps, date time, naive date time.

I could just use Unix time but I feel like that be giving up on actually learning dates and how to use them, I’d also lose the querying capabilities in Postgres probably.

So I just want to send a date when the auction ends from my client and be able to store it properly in my db so that I can correctly show the date to my users when it’s actually ending. Any explanations would be greatly appreciated, I tried playing around with ISO, UTC and other formats from the client but I don’t fully understand how to go about it as there are some kind of timezone databases and what not

The DateTime struct stores the date, time and time zone information so you should be using that. Ecto’s utc_datetime type will use it and set the time zone to UTC.

You then need to use a time zone database to be able to convert the UTC DateTime to other time zones. Take a look at Tzdata — tzdata v1.1.1. And look at the conversion functions in the DateTime module for how you can use it.

ends_at is a date in the future, so I would store the naive datetime and the time zone as two separate fields in the db (that’s because you can never really know the correct UTC datetime for future dates as time zone information might change, especially true for dates far in the future; you might still want to add the UTC datetime additionally if you need sorting for example).

For your time zone conversion needs, Temporal is coming to JavaScript, a polyfill is available.

For Elixir we have for now 3 librairies that allow DateTime to handle time zones:

Not sure I follow, I understand that local date-time can be different in the future but can UTC change? Like if I save a date, for example 10 am 10 days from now, can that UTC date change so that 10am UTC isn’t 10 am anymore? I don’t understand how’s that possible.

Isn’t UTC correct and then local timezones can change? Why would I need to store TimeZone alongside naive datetime, can’t I just operate on UTC as that won’t change?

If an auction ends tomorrow at 10am UTC, I’ll just display that as 12am for people who are +2?

Read a lot of articles but still can’t properly wrap my head around this.

It depends on what you need to store. Do you need to store 48h from now (absolute time), or do you need to store 2pm in two days from now for the timezone of the user (wall time)? For the latter you can‘t just store a datetime in UTC and have it stay correct when subject to changing timezone definitions.

Storing future times as UTC and converting them back to the local time zone for displaying them to the user will work fine in most situation. However, there are certain edge cases where the rules for a certain time zone might change after the time entry was created (for example, DST might be abolished/introduced or start/end on a different date). To handle these cases, you need to record the time zone and the wall time. This post illustrates the problem very well and also points to a library that provides a solution: How to save datetimes for future events - (when UTC is not the right answer)

I’d also recommend the other post linked in the article: Falsehoods programmers believe about time and time zones

1 Like

I assume that a user will set the auction’s end date to a datetime in his time zone, not in UTC (I think it would be bad UX if the users need to be aware of UTC time at all).

Say Belgian user sets auction’s end date in 2023 to 5pm during winter time, so UTC+1. You store the UTC datetime 4pm for that date. Say EU decides to keep the summer time UTC+2 and abolish DST changes from 2023 (I think they’re currently still debating whether to keep winter or summer time). The UTC datetime that you stored now corresponds to 6pm Belgian time for that date, so it’s wrong as the user intended to stop the auction at 5pm for his local time.

But if ends_at is always just a few days away from current date then it shouldn’t matter as the timespan should be too short for such changes to occur.

That library is no longer relevant as it was introduced before elixir supported the Calendar types we know today.

The way I understand the README, this is still relevant if you want to store future times that are not UTC and must be robust to TZ changes. Quoting the README:

If Ecto 2.1 and newer is used, Calecto should only be used for the Calecto.DateTime type, which is meant for DateTimes that are not UTC only.

And:

If you are using Postgres as a database you can also use the Calecto.DateTime type. This allows you to save any Calendar.DateTime struct. This is useful for saving for instance future times for meetings in a certain timezone. Even if timezone rules change, the “wall time” will stay the same. See the “DateTime with Postgres” heading below.

Exactly the problem we are trying to solve here :slight_smile:

1 Like

I don’t really see the added value to have an Ecto type for this rather than storing the naive datetime and time zone as two separate fields in DB :thinking:

I never used it myself, but I’m thinking that using Calecto.DateTime would reduce the amount of boilerplate and handle a couple of things for you? Transparently storing the time zone with the datetime for example, and reading them back.

It’s possible that there’s not much added value compared to doing it by yourself.

I created TzDatetime — tz_datetime v0.1.3 for the “don’t try to be too smart” way, which also additionally tries to be optimistic and stores a UTC datetime, which you can use for sorting until you actually run into the case of a changed offset.

2 Likes