Current section

Files

Jump to
tz_extra lib iana_file_parser.ex
Raw

lib/iana_file_parser.ex

defmodule TzExtra.IanaFileParser do
@moduledoc false
import TzExtra.Helper
def countries() do
Path.join([:code.priv_dir(:tz), "tzdata#{Tz.iana_version()}", "iso3166.tab"])
|> file_to_list()
|> parse_countries()
|> Enum.sort_by(&normalize_string(&1.name))
end
def time_zones_with_country(countries) do
Path.join([:code.priv_dir(:tz), "tzdata#{Tz.iana_version()}", "zone1970.tab"])
|> file_to_list()
|> parse_time_zones_with_country()
|> Enum.map(fn map ->
{country_code, map} = pop_in(map, [:country_code])
country = Enum.find(countries, & &1.code == country_code)
Map.put(map, :country, country)
end)
end
def time_zones() do
for filename <- ~w(africa antarctica asia australasia etcetera europe northamerica southamerica)s do
Path.join([:code.priv_dir(:tz), "tzdata#{Tz.iana_version()}", filename])
|> file_to_list()
|> parse_time_zones()
end
|> List.flatten()
|> merge_canonical_link_time_zones()
end
defp file_to_list(file_path) do
File.stream!(file_path)
|> strip_comments()
|> strip_empty()
|> trim()
|> Enum.to_list()
end
defp strip_comments(stream) do
stream
|> Stream.filter(&(!Regex.match?(~r/^[\s]*#/, &1)))
|> Stream.map(&Regex.replace(~r/[\s]*#.+/, &1, ""))
end
defp strip_empty(stream) do
Stream.filter(stream, &(!Regex.match?(~r/^[\s]*\n$/, &1)))
end
defp trim(stream) do
Stream.map(stream, &String.trim(&1))
end
defp parse_countries([]), do: []
defp parse_countries([string | tail]) do
map =
Enum.zip([
[:code, :name],
String.split(string, ~r{\s}, trim: true, parts: 2)
|> Enum.map(& String.trim(&1))
])
|> Enum.into(%{})
[map | parse_countries(tail)]
end
defp merge_canonical_link_time_zones(time_zones) do
Enum.reduce(time_zones, %{}, fn
%{canonical_zone_name: canonical_zone_name, link_zone_name: link_zone_name}, map ->
link_zone_names = Map.get(map, canonical_zone_name, [])
Map.put(map, canonical_zone_name, [link_zone_name | link_zone_names])
%{canonical_zone_name: canonical_zone_name}, map ->
Map.put_new(map, canonical_zone_name, [])
end)
end
defp parse_time_zones([]), do: []
defp parse_time_zones([string | tail]) do
cond do
String.starts_with?(string, "Link") ->
[parse_link_string_into_map(string) | parse_time_zones(tail)]
String.starts_with?(string, "Zone") ->
[parse_zone_string_into_map(string) | parse_time_zones(tail)]
true ->
parse_time_zones(tail)
end
end
defp parse_link_string_into_map(link_string) do
Enum.zip([
[:canonical_zone_name, :link_zone_name],
tl(String.split(link_string, ~r{\s}, trim: true, parts: 3))
|> Enum.map(& String.trim(&1))
])
|> Enum.into(%{})
end
defp parse_zone_string_into_map(zone_string) do
Enum.zip([
[:canonical_zone_name],
tl(String.split(zone_string, ~r{\s}, trim: true, parts: 6))
|> Enum.map(& String.trim(&1))
])
|> Enum.into(%{})
end
defp parse_time_zones_with_country([]), do: []
defp parse_time_zones_with_country([string | tail]) do
map =
Enum.zip([
[:country_codes, :coordinates, :time_zone, :_],
String.split(string, ~r{\s}, trim: true, parts: 4)
|> Enum.map(& String.trim(&1))
])
|> Enum.into(%{})
|> Map.delete(:_)
country_codes = String.split(map.country_codes, ",", trim: true)
for country_code <- country_codes do
%{
country_code: country_code,
coordinates: map.coordinates,
time_zone: map.time_zone
}
end
++ parse_time_zones_with_country(tail)
end
end