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}
Looking at the source code, it looks like
Timezone.convert/2is supposed to returnAmbiguousDateTimeon:ambiguousAND:gap. However, in some cases it instead returns a not very helpful:errortuple.Example with gap datetime:
The error seems to come from calling
Timezone.get("America/Toronto", ~N[2025-03-09 02:30:00])and ultimatelyTzdata.periods_for_time("America/Toronto", 63908706600, :wall)which comes back with[]. From theperiod_for_timedocs, 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:I'm wondering if the following should be changed from
timex/lib/timezone/timezone.ex
Lines 584 to 588 in 5ad1b82
to something like:
That seems to give the expected results: