Packages
tz
0.21.1
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/compiler.ex
defmodule Tz.Compiler do
@moduledoc false
require Tz.IanaFileParser
require Tz.PeriodsBuilder
alias Tz.IanaDataDir
alias Tz.IanaFileParser
alias Tz.PeriodsBuilder
def compile() do
IanaDataDir.maybe_copy_iana_files_to_custom_dir()
tzdata_dir_path = IanaDataDir.tzdata_dir_path()
"tzdata" <> tzdata_version = Path.basename(tzdata_dir_path)
{periods_and_links, ongoing_rules} =
for filename <- ~w(africa antarctica asia australasia backward etcetera europe northamerica southamerica)s do
{zone_records, rule_records, link_records, ongoing_rules} =
IanaFileParser.parse(Path.join(tzdata_dir_path, filename))
Enum.each(rule_records, fn {rule_name, rules} ->
ongoing_rules_count = Enum.count(rules, & &1.to == :max)
if ongoing_rules_count > 2 do
raise "unexpected case: #{ongoing_rules_count} ongoing rules for rule \"#{rule_name}\""
end
end)
periods =
for {zone_name, zone_lines} <- zone_records do
periods =
PeriodsBuilder.build_periods(zone_lines, rule_records)
|> PeriodsBuilder.periods_to_tuples_and_reverse()
|> reject_periods_before_year()
if length(periods) == 0 do
raise "no periods for time zone #{zone_name}"
end
{:periods, zone_name, periods}
end
links =
for link <- link_records do
{:link, link.link_zone_name, link.canonical_zone_name}
end
{periods ++ links, ongoing_rules}
end
|> Enum.reduce(
{[], %{}},
fn {periods_and_links, ongoing_rules}, {all_periods_and_links, all_ongoing_rules} ->
{periods_and_links ++ all_periods_and_links, Map.merge(ongoing_rules, all_ongoing_rules)}
end)
compile_periods(periods_and_links, tzdata_version)
compile_map_ongoing_changing_rules(ongoing_rules)
end
defp reject_periods_before_year(periods) do
case Application.get_env(:tz, :reject_time_zone_periods_before_year) do
nil ->
periods
reject_before_year ->
filtered_periods =
Enum.reject(periods, fn {secs, _, _, _} ->
%{year: year} = gregorian_seconds_to_naive_datetime(secs)
year < reject_before_year
end)
if length(filtered_periods) > 0 do
filtered_periods
else
periods
end
end
end
defp gregorian_seconds_to_naive_datetime(seconds) do
:calendar.gregorian_seconds_to_datetime(seconds)
|> NaiveDateTime.from_erl!()
end
def compile_periods(periods_and_links, tzdata_version) do
quoted = [
quote do
@moduledoc(false)
def iana_version() do
unquote(tzdata_version)
end
end,
for period_or_link <- periods_and_links do
case period_or_link do
{:link, link_zone_name, canonical_zone_name} ->
quote do
def periods(unquote(link_zone_name)) do
periods(unquote(Macro.escape(canonical_zone_name)))
end
end
{:periods, zone_name, periods} ->
quote do
def periods(unquote(zone_name)) do
{:ok, unquote(Macro.escape(periods))}
end
end
end
end,
quote do
def periods(_) do
{:error, :time_zone_not_found}
end
end
]
module = :"Elixir.Tz.PeriodsProvider"
Module.create(module, quoted, Macro.Env.location(__ENV__))
:code.purge(module)
end
defp compile_map_ongoing_changing_rules(ongoing_rules) do
quoted = [
quote do
@moduledoc(false)
end,
for {rule_name, rules} <- ongoing_rules do
quote do
def rules(unquote(rule_name)) do
unquote(Macro.escape(rules))
end
end
end
]
module = :"Elixir.Tz.OngoingChangingRulesProvider"
Module.create(module, quoted, Macro.Env.location(__ENV__))
:code.purge(module)
end
end