defmodule Timex.Timezone.Database do @behaviour Calendar.TimeZoneDatabase alias Timex.Timezone alias Timex.TimezoneInfo @impl true @doc false def time_zone_period_from_utc_iso_days(iso_days, time_zone) do db = Tzdata.TimeZoneDatabase case db.time_zone_period_from_utc_iso_days(iso_days, time_zone) do {:error, :time_zone_not_found} -> # Get a NaiveDateTime for time_zone_periods_from_wall_datetime {year, month, day, hour, minute, second, microsecond} = Calendar.ISO.naive_datetime_from_iso_days(iso_days) with {:ok, naive} <- NaiveDateTime.new(year, month, day, hour, minute, second, microsecond) do time_zone_periods_from_wall_datetime(naive, time_zone) else {:error, _} -> {:error, :time_zone_not_found} end result -> result end end @impl true @doc false def time_zone_periods_from_wall_datetime(naive, time_zone) do db = Tzdata.TimeZoneDatabase if Tzdata.zone_exists?(time_zone) do case db.time_zone_periods_from_wall_datetime(naive, time_zone) do {:error, :time_zone_not_found} -> time_zone_periods_from_wall_datetime_fallback(naive, time_zone) result -> result end else time_zone_periods_from_wall_datetime_fallback(naive, time_zone) end end # Fallback method which looks for a desired timezone in the process state defp time_zone_periods_from_wall_datetime_fallback(naive, time_zone) do # Try to pop the time zone from process state, validate the desired datetime falls # within the bounds of the time zone, and return its period description if so case Process.put(__MODULE__, nil) do %TimezoneInfo{from: from, until: until} = tz -> with {:ok, range_start} <- period_boundary_to_naive(from), {:ok, range_end} <- period_boundary_to_naive(until) do cond do range_start == :min and range_end == :max -> {:ok, TimezoneInfo.to_period(tz)} range_start == :min and NaiveDateTime.compare(naive, range_end) in [:lt, :eq] -> {:ok, TimezoneInfo.to_period(tz)} range_end == :max and NaiveDateTime.compare(naive, range_start) in [:gt, :eq] -> {:ok, TimezoneInfo.to_period(tz)} range_start != :min and range_end != :max and NaiveDateTime.compare(naive, range_start) in [:gt, :eq] and NaiveDateTime.compare(naive, range_end) in [:lt, :eq] -> {:ok, TimezoneInfo.to_period(tz)} :else -> {:error, :time_zone_not_found} end else {:error, _} -> {:error, :time_zone_not_found} end nil -> time_zone_periods_from_wall_datetime_by_name(naive, time_zone) end end # Fallback method which attempts to lookup the timezone by name defp time_zone_periods_from_wall_datetime_by_name(naive, time_zone) do with %TimezoneInfo{} = tz <- Timezone.get(time_zone, naive) do {:ok, TimezoneInfo.to_period(tz)} end end defp period_boundary_to_naive(:min), do: {:ok, :min} defp period_boundary_to_naive(:max), do: {:ok, :max} defp period_boundary_to_naive({_, {{y, m, d}, {hh, mm, ss}}}) do NaiveDateTime.new(y, m, d, hh, mm, ss) end defp period_boundary_to_naive(_), do: {:error, :invalid_period} end