Packages
tz_extra
0.20.1
0.45.0
0.44.0
0.43.0
0.42.0
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.1
0.22.0
0.21.1
0.21.0
0.20.1
0.20.0
0.17.0
0.16.7
0.16.6
0.16.5
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Time zone-related utilities
Current section
Files
Jump to
Current section
Files
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