Packages
tz
0.9.0
0.28.2
0.28.1
0.28.0
0.27.3
0.27.2
0.27.1
0.27.0
0.26.6
0.26.5
0.26.4
0.26.3
0.26.2
0.26.1
0.26.0
0.25.1
0.25.0
0.24.0
0.23.0
0.22.0
0.21.1
0.21.0
0.20.1
0.20.0
0.19.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Time zone support for Elixir
Current section
Files
Jump to
Current section
Files
lib/time_zone_database.ex
defmodule Tz.TimeZoneDatabase do
@moduledoc false
@behaviour Calendar.TimeZoneDatabase
alias Tz.IanaFileParser
alias Tz.PeriodsBuilder
alias Tz.PeriodsProvider
@impl true
def time_zone_period_from_utc_iso_days(iso_days, time_zone) do
with {:ok, periods_by_year} <- PeriodsProvider.periods_by_year(time_zone) do
naive_datetime = naive_datetime_from_iso_days(iso_days)
utc_gregorian_seconds = NaiveDateTime.diff(naive_datetime, ~N[0000-01-01 00:00:00])
found_periods =
Map.get(periods_by_year, naive_datetime.year, periods_by_year.minmax)
|> find_periods_for_timestamp(utc_gregorian_seconds, :utc_gregorian_seconds)
case found_periods do
[period] ->
{:ok, period}
_ ->
raise "#{Enum.count(found_periods)} periods found"
end
end
end
@impl true
def time_zone_periods_from_wall_datetime(naive_datetime, time_zone) do
with {:ok, periods_by_year} <- PeriodsProvider.periods_by_year(time_zone) do
wall_gregorian_seconds = NaiveDateTime.diff(naive_datetime, ~N[0000-01-01 00:00:00])
found_periods =
Map.get(periods_by_year, naive_datetime.year, periods_by_year.minmax)
|> find_periods_for_timestamp(wall_gregorian_seconds, :wall_gregorian_seconds)
case found_periods do
[%{zone_abbr: _} = period] ->
{:ok, period}
[%{period_before_gap: _} = period] ->
{:gap, {period.period_before_gap, period.from.wall}, {period.period_after_gap, period.to.wall}}
[second_period, first_period] ->
{:ambiguous, first_period, second_period}
_ ->
raise "#{Enum.count(found_periods)} periods found"
end
end
end
defp generate_dynamic_periods([period1, period2], year) do
rule_records = IanaFileParser.denormalized_rule_data([
IanaFileParser.change_rule_year(period1.rule, year - 1),
IanaFileParser.change_rule_year(period1.rule, year),
IanaFileParser.change_rule_year(period2.rule, year)
])
PeriodsBuilder.build_periods([period1.zone_line], rule_records)
|> PeriodsBuilder.shrink_and_reverse_periods()
end
defp find_periods_for_timestamp(periods, timestamp, time_modifier) do
do_find_periods_for_timestamp(periods, timestamp, time_modifier)
|> maybe_generate_dynamic_periods(periods, timestamp, time_modifier)
end
defp do_find_periods_for_timestamp(periods, timestamp, time_modifier) do
Enum.filter(periods, fn period ->
period_from = if(period.from == :min, do: :min, else: period.from[time_modifier])
period_to = if(period.to == :max, do: :max, else: period.to[time_modifier])
is_timestamp_in_range?(timestamp, period_from, period_to)
end)
end
defp is_timestamp_in_range?(_, :min, :max), do: true
defp is_timestamp_in_range?(timestamp, :min, date_to), do: timestamp < date_to
defp is_timestamp_in_range?(timestamp, date_from, :max), do: timestamp >= date_from
defp is_timestamp_in_range?(timestamp, date_from, date_to), do: timestamp >= date_from && timestamp < date_to
defp maybe_generate_dynamic_periods([%{to: :max} | _], [%{to: :max} = p1, %{to: :max} = p2 | _], timestamp, time_modifier) do
year = NaiveDateTime.add(~N[0000-01-01 00:00:00], timestamp).year
[p1, p2]
|> generate_dynamic_periods(year)
|> do_find_periods_for_timestamp(timestamp, time_modifier)
end
defp maybe_generate_dynamic_periods(found_periods, _periods, _timestamp, _time_modifier) do
found_periods
end
defp naive_datetime_from_iso_days(iso_days) do
Calendar.ISO.naive_datetime_from_iso_days(iso_days)
|> (&apply(NaiveDateTime, :new, Tuple.to_list(&1))).()
|> elem(1)
end
end