Notes to self

Summer and winter time changes with DateTime

Developers usually think of timezones, but European summertime changes can be easily overlooked. I have to admit I overlooked them when parsing dates with DateTime.from_naive!/2.

What’s the issue, you ask?

Let’s parse some time with DateTime.from_naive!/:

iex> datetime = DateTime.from_naive!(DateTime.utc_now(), "EET")
#DateTime<2021-11-26 10:36:32.810393+02:00 EET EET>

Most of the time, this works as you would expect – and looks innocent.

But it all breaks for summer and winter time changes which are known as Daylight Saving Time (DST). Consider getting a datetime string on the day and minute of the change:

iex(17)> datetime = DateTime.from_naive!(~N[2021-10-31 03:00:00], "EET")
** (ArgumentError) cannot convert ~N[2021-10-31 03:00:00] to datetime because such instant is ambiguous in time zone EET as there is an overlap between #DateTime<2021-10-31 03:00:00+03:00 EEST EET> and #DateTime<2021-10-31 03:00:00+02:00 EET EET>
    (elixir 1.12.3) lib/calendar/datetime.ex:559: DateTime.from_naive!/3

At 3 am, the daylight saving time changes in the EET timezone for winter…

Luckily Elixir provides a nice error message with all the details, and hopefully, it’s not too late to fix it.

In case of parsing naive date time, you’ll have to leave the bang method. DateTime.from_naive/2 supports handling ambiguous times:

datetime = case DateTime.from_naive(~N[2021-10-31 03:00:00], "EET") do
  {:ok, datetime} ->
    datetime

  {:ambiguous, before_dst_change, after_dst_change} ->
    after_dst_change

  {:gap, timezone_before, timezone_after} ->
    timezone_after

  {:error, error} ->
    # handle error, raise...
end

The before datetime would be #DateTime<2021-10-31 03:00:00+03:00 EEST EET> and the one after the change would be #DateTime<2021-10-31 03:00:00+02:00 EET EET>. Most will probably consider the edge case as a start of the new time, therefore I used the after_dst_change date.

I also had to pattern-match on :gap since that’s what would be used for a datetime that doesn’t actually exist, but falls inbetween two valid datetimes.

Although clarified and well documented (thank you Elixir!), you want to implement this beforehand so your processes don’t fail, and you don’t have to go back to fix some data.

Work with me

I have some availability for contract work. I can be your fractional CTO, a Ruby on Rails engineer, or consultant. Write me at strzibny@strzibny.name.

RSS