Skip to content

Timezone.convert/2 gives :error on DST gap instead of AmbiguousDateTime #789

@tisdall

Description

@tisdall

Looking at the source code, it looks like Timezone.convert/2 is supposed to return AmbiguousDateTime on :ambiguous AND :gap. However, in some cases it instead returns a not very helpful :error tuple.

Example with gap datetime:

iex(1)> use Timex
Timex.Timezone
iex(2)> naive_datetime_on_gap = ~N[2025-03-09 02:30:00]
~N[2025-03-09 02:30:00]
iex(3)> Timex.to_datetime(naive_datetime_on_gap, "America/Toronto")
{:error, {:could_not_resolve_timezone, "America/Toronto", 63908706600, :wall}}
iex(4)> Timezone.convert(naive_datetime_on_gap, "America/Toronto")
{:error, {:could_not_resolve_timezone, "America/Toronto", 63908706600, :wall}}

The error seems to come from calling Timezone.get("America/Toronto", ~N[2025-03-09 02:30:00]) and ultimately Tzdata.periods_for_time("America/Toronto", 63908706600, :wall) which comes back with []. From the period_for_time docs, this seems to be the expected return for "non-existing wall time for the zone".

I seem to be able to get around this issue if I skip Timezone.get:

iex(6)> a = Timezone.convert(naive_datetime_on_gap, %TimezoneInfo{full_name: "America/Toronto"})
#<Gap(#DateTime<2025-03-09 01:59:59.999999-05:00 EST America/Toronto> ~ #DateTime<2025-03-09 03:00:00-04:00 EDT America/Toronto>)>
iex(7)> a.__struct__
Timex.AmbiguousDateTime
iex(8)> a.before
#DateTime<2025-03-09 01:59:59.999999-05:00 EST America/Toronto>
iex(9)> a.after
#DateTime<2025-03-09 03:00:00-04:00 EDT America/Toronto>

I'm wondering if the following should be changed from

def convert(%NaiveDateTime{} = date, tz) do
with %TimezoneInfo{} = tzinfo <- get(tz, date) do
convert(date, tzinfo)
end
end

to something like:

  def convert(%NaiveDateTime{} = date, tz) do
    convert(date, %TimezoneInfo{full_name: tz})
  end

That seems to give the expected results:

iex(22)> Timezone.convert(~N[2025-03-09 02:30:00], %TimezoneInfo{full_name: "America/Toronto"})
#<Gap(#DateTime<2025-03-09 01:59:59.999999-05:00 EST America/Toronto> ~ #DateTime<2025-03-09 03:00:00-04:00 EDT America/Toronto>)>
iex(23)> Timezone.convert(~N[2025-11-02 01:30:00], %TimezoneInfo{full_name: "America/Toronto"})
#<Ambiguous(#DateTime<2025-11-02 01:30:00-04:00 EDT America/Toronto> ~ #DateTime<2025-11-02 01:30:00-05:00 EST America/Toronto>)>
iex(24)> Timezone.convert(~N[2025-11-12 01:30:00], %TimezoneInfo{full_name: "America/Toronto"})
#DateTime<2025-11-12 01:30:00-05:00 EST America/Toronto>
iex(25)> Timezone.convert(~N[2025-11-12 01:30:00], %TimezoneInfo{full_name: "NOT FOUND"})
{:error, :time_zone_not_found}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions