defmodule TzExtra.Compiler do
@moduledoc false
require TzExtra.IanaFileParser
import TzExtra.Helper
alias TzExtra.IanaFileParser
def compile() do
time_zones = IanaFileParser.time_zones()
canonical_time_zones =
time_zones
|> Map.keys()
|> Enum.uniq()
|> Enum.sort()
countries =
IanaFileParser.countries()
|> localize_country_name()
all_time_zones =
time_zones
|> Enum.map(fn {canonical, links} -> [canonical | links] end)
|> List.flatten()
|> Enum.uniq()
|> Enum.sort()
link_canonical_map =
time_zones
|> Enum.reduce(%{}, fn {canonical, links}, map ->
Enum.reduce(links, map, fn link, map ->
Map.put(map, link, canonical)
end)
|> Map.put(canonical, canonical)
end)
get_time_zone_links_for_canonical_fun =
fn canonical ->
time_zones[canonical] |> Enum.sort()
end
countries_time_zones =
IanaFileParser.time_zones_with_country(countries)
|> add_time_zone_links(get_time_zone_links_for_canonical_fun)
|> add_offset_data()
|> add_location_name()
|> add_id()
|> Enum.sort_by(
&{&1.country && normalize_string(&1.country.name), &1.utc_to_std_offset, &1.time_zone_id}
)
{countries, countries_time_zones} =
add_canonical_time_zone_ids(countries, countries_time_zones)
civil_time_zones =
countries_time_zones
|> Enum.map(& &1.time_zone_id)
|> Enum.uniq()
|> Enum.sort()
civil_time_zones_with_links =
countries_time_zones
|> Enum.map(&[&1.time_zone_id | &1.time_zone_alias_ids])
|> List.flatten()
|> Enum.uniq()
|> Enum.sort()
quoted = [
for canonical_time_zone <- canonical_time_zones do
filtered_countries_time_zones =
Enum.filter(countries_time_zones, &(&1.time_zone_id == canonical_time_zone))
if length(filtered_countries_time_zones) > 0 do
quote do
def by_time_zone(unquote(canonical_time_zone)) do
{:ok, unquote(Macro.escape(filtered_countries_time_zones))}
end
end
else
quote do
def by_time_zone(unquote(canonical_time_zone)) do
{:error, :time_zone_not_linked_to_country}
end
end
end
end,
quote do
def by_time_zone(link_time_zone) do
if canonical_time_zone = unquote(Macro.escape(link_canonical_map))[link_time_zone] do
countries_time_zones_for_link =
Enum.filter(
unquote(Macro.escape(countries_time_zones)),
&(&1.time_zone_id == canonical_time_zone)
)
if countries_time_zones_for_link do
{:ok, countries_time_zones_for_link}
else
{:error, :time_zone_not_linked_to_country}
end
else
{:error, :time_zone_not_found}
end
end
end,
for %{code: country_code} <- countries do
filtered_countries_time_zones =
Enum.filter(countries_time_zones, &(&1.country.code == country_code))
country_code_atom = String.to_atom(country_code)
quote do
def by_country_code(unquote(country_code)) do
{:ok, unquote(Macro.escape(filtered_countries_time_zones))}
end
def by_country_code(unquote(country_code_atom)) do
by_country_code(unquote(country_code))
end
end
end,
quote do
def by_country_code(_) do
{:error, :country_not_found}
end
end,
for %{id: id} = country_time_zone <- countries_time_zones do
quote do
def by_id(unquote(id)) do
{:ok, unquote(Macro.escape(country_time_zone))}
end
end
end,
quote do
def by_id(_) do
{:error, :country_time_zone_id_not_found}
end
end
]
module = :"Elixir.TzExtra.CountryTimeZone"
Module.create(module, quoted, Macro.Env.location(__ENV__))
:code.purge(module)
contents = [
quote do
def iana_version() do
unquote(Tz.iana_version())
end
def utc_time_zone_id(), do: "Etc/UTC"
def canonical_time_zone_id(time_zone_id) do
if time_zone_id = unquote(Macro.escape(link_canonical_map))[time_zone_id] do
{:ok, time_zone_id}
else
{:error, :time_zone_not_found}
end
end
def canonical_time_zone_id!(time_zone_id) do
case canonical_time_zone_id(time_zone_id) do
{:ok, time_zone_id} ->
time_zone_id
{:error, :time_zone_not_found} ->
raise "time zone identifier \"#{time_zone_id}\" not found"
end
end
def civil_time_zone_ids(opts \\ []) do
include_aliases = Keyword.get(opts, :include_aliases, false)
if include_aliases do
unquote(Macro.escape(civil_time_zones_with_links))
else
unquote(Macro.escape(civil_time_zones))
end
end
def time_zone_ids(opts \\ []) do
include_aliases = Keyword.get(opts, :include_aliases, false)
if include_aliases do
unquote(Macro.escape(all_time_zones))
else
unquote(Macro.escape(canonical_time_zones))
end
end
def time_zone_id_exists?(time_zone_id) do
time_zone_ids(include_aliases: true)
|> Enum.any?(&(&1 == time_zone_id))
end
def country_code_exists?(country_code) do
countries()
|> Enum.any?(&(&1.code == country_code))
end
def round_datetime(%DateTime{} = datetime, step_in_seconds, mode)
when mode in [:floor, :ceil] do
utc_datetime = DateTime.to_unix(datetime, :second)
rounded_unix_time =
case mode do
:floor ->
div(utc_datetime, step_in_seconds) * step_in_seconds
:ceil ->
div(utc_datetime + step_in_seconds - 1, step_in_seconds) * step_in_seconds
end
rounded_datetime = DateTime.from_unix!(rounded_unix_time)
DateTime.shift_zone!(rounded_datetime, datetime.time_zone)
end
def new_resolved_datetime!(%Date{} = date, %Time{} = time, time_zone, opts) do
ambiguous = Keyword.fetch!(opts, :ambiguous)
gap = Keyword.fetch!(opts, :gap)
case DateTime.new(date, time, time_zone, Tz.TimeZoneDatabase) do
{:ok, dt} ->
dt
{:error, :time_zone_not_found} ->
raise "time zone not found"
{:ambiguous, first, second} ->
case ambiguous do
:first -> first
:second -> second
end
{:gap, just_before, just_after} ->
case gap do
:just_before -> just_before
:just_after -> just_after
end
end
end
def countries_time_zones(country_or_time_zone) do
country_or_time_zone = to_string(country_or_time_zone)
if String.length(country_or_time_zone) == 2 do
:"Elixir.TzExtra.CountryTimeZone".by_country_code(country_or_time_zone)
else
:"Elixir.TzExtra.CountryTimeZone".by_time_zone(country_or_time_zone)
end
end
def countries_time_zones!(country_or_time_zone) do
case countries_time_zones(country_or_time_zone) do
{:ok, countries_time_zones} ->
countries_time_zones
{:error, _} ->
raise "country time zone not found"
end
end
def country_time_zone(id) do
:"Elixir.TzExtra.CountryTimeZone".by_id(id)
end
def country_time_zone!(id) do
case country_time_zone(id) do
{:ok, country_time_zone} ->
country_time_zone
{:error, _} ->
raise "country time zone not found"
end
end
def countries_time_zones() do
unquote(Macro.escape(countries_time_zones))
end
def countries() do
unquote(Macro.escape(countries))
end
def utc_datetime_range(%DateTime{} = start_dt, %DateTime{} = end_dt, step_in_seconds) do
start_unix_time = DateTime.to_unix(start_dt, :second)
end_unix_time = DateTime.to_unix(end_dt, :second)
Enum.map(start_unix_time..end_unix_time//step_in_seconds, &DateTime.from_unix!(&1))
end
def shifts_clock?(time_zone_id) when time_zone_id != nil do
case Tz.PeriodsProvider.periods(time_zone_id) do
{:error, :time_zone_not_found} ->
raise "invalid time zone #{time_zone_id}"
{:ok, [{utc_secs, _, _, nil} | _]} ->
hardcoded_dst_future_periods? =
DateTime.from_gregorian_seconds(utc_secs).year >
Tz.PeriodsProvider.compiled_at().year + 20
hardcoded_dst_future_periods?
{:ok, [{_, _, _, _} | _]} ->
true
end
end
def utc_offset_id(%DateTime{} = datetime, mode \\ :standard)
when mode in [:standard, :pretty] do
"UTC" <> offset_to_string(datetime.utc_offset + datetime.std_offset, mode)
end
def next_clock_shift_in_year_span(%DateTime{} = datetime) do
case Tz.PeriodsProvider.next_period(datetime) do
{from, _, _, _} ->
first_datetime_in_next_period =
DateTime.from_gregorian_seconds(from)
|> DateTime.shift_zone!(datetime.time_zone, Tz.TimeZoneDatabase)
year_difference = DateTime.diff(first_datetime_in_next_period, datetime, :day) / 365
if year_difference > 1 do
:no_shift
else
clock_shift = clock_shift(datetime, first_datetime_in_next_period)
offset = datetime.utc_offset + datetime.std_offset
next_offset =
first_datetime_in_next_period.utc_offset +
first_datetime_in_next_period.std_offset
{
clock_shift,
first_datetime_in_next_period,
div(next_offset - offset, 60)
}
end
nil ->
:no_shift
end
end
def clock_shift(datetime1, datetime2) do
if DateTime.compare(datetime1, datetime2) == :gt do
raise "first datetime must be earlier than or equal to second datetime"
end
offset1 = datetime1.utc_offset + datetime1.std_offset
offset2 = datetime2.utc_offset + datetime2.std_offset
cond do
offset1 < offset2 -> :forward
offset1 > offset2 -> :backward
offset1 == offset2 -> :no_shift
end
end
end,
for %{code: country_code} <- countries do
{:ok, time_zones_for_country} =
:"Elixir.TzExtra.CountryTimeZone".by_country_code(country_code)
country_code_atom = String.to_atom(country_code)
quote do
def country_time_zone(unquote(country_code), time_zone_id) do
case canonical_time_zone_id(time_zone_id) do
{:error, _} = error ->
error
{:ok, canonical_time_zone_id} ->
country_time_zone =
Enum.find(
unquote(Macro.escape(time_zones_for_country)),
&(&1.time_zone_id == canonical_time_zone_id)
)
if country_time_zone do
{:ok, country_time_zone}
else
{:error, :time_zone_not_found_for_country}
end
end
end
def country_time_zone(unquote(country_code_atom), time_zone_id) do
country_time_zone(unquote(country_code), time_zone_id)
end
end
end,
quote do
def country_time_zone(_, _) do
{:error, :country_not_found}
end
def country_time_zone!(country_code, time_zone_id) do
case country_time_zone(country_code, time_zone_id) do
{:ok, country_time_zone} ->
country_time_zone
{:error, _} ->
raise "no time zone data found for country #{country_code} and time zone ID #{time_zone_id}"
end
end
end
]
module = :"Elixir.TzExtra"
Module.create(module, contents, Macro.Env.location(__ENV__))
:code.purge(module)
end
defp add_offset_data(time_zones) do
Enum.map(time_zones, fn %{time_zone_id: time_zone_id} = time_zone ->
{:ok, periods} = Tz.PeriodsProvider.periods(time_zone_id)
{utc_to_std_offset, utc_to_dst_offset, time_zone_abbr, dst_time_zone_abbr} =
case hd(periods) do
{utc_secs, {utc_to_std_offset, std_offset, time_zone_abbr},
{_, prev_std_offset, prev_zone_abbr}, nil} ->
hardcoded_dst_future_periods? =
DateTime.from_gregorian_seconds(utc_secs).year > Date.utc_today().year + 20
if hardcoded_dst_future_periods? do
utc_to_dst_offset = utc_to_std_offset + max(std_offset, prev_std_offset)
utc_to_std_offset = utc_to_std_offset + min(std_offset, prev_std_offset)
{time_zone_abbr, dst_time_zone_abbr} =
cond do
std_offset < prev_std_offset ->
{time_zone_abbr, prev_zone_abbr}
std_offset > prev_std_offset ->
{prev_zone_abbr, time_zone_abbr}
end
{utc_to_std_offset, utc_to_dst_offset, time_zone_abbr, dst_time_zone_abbr}
else
{utc_to_std_offset, utc_to_std_offset + std_offset, time_zone_abbr, time_zone_abbr}
end
{_, {utc_to_std_offset, std_offset, time_zone_abbr},
{_, prev_std_offset, prev_zone_abbr}, _} ->
utc_to_dst_offset = utc_to_std_offset + max(std_offset, prev_std_offset)
utc_to_std_offset = utc_to_std_offset + min(std_offset, prev_std_offset)
{time_zone_abbr, dst_time_zone_abbr} =
cond do
std_offset < prev_std_offset ->
{time_zone_abbr, prev_zone_abbr}
std_offset > prev_std_offset ->
{prev_zone_abbr, time_zone_abbr}
end
{utc_to_std_offset, utc_to_dst_offset, time_zone_abbr, dst_time_zone_abbr}
end
time_zone
|> Map.put(:utc_to_std_offset, utc_to_std_offset)
|> Map.put(:utc_to_dst_offset, utc_to_dst_offset)
|> Map.put(:utc_to_std_offset_id, "UTC" <> offset_to_string(utc_to_std_offset, :standard))
|> Map.put(:utc_to_dst_offset_id, "UTC" <> offset_to_string(utc_to_dst_offset, :standard))
|> Map.put(
:pretty_utc_to_std_offset_id,
"UTC" <> offset_to_string(utc_to_std_offset, :pretty)
)
|> Map.put(
:pretty_utc_to_dst_offset_id,
"UTC" <> offset_to_string(utc_to_dst_offset, :pretty)
)
|> Map.put(:time_zone_abbr, time_zone_abbr)
|> Map.put(:dst_time_zone_abbr, dst_time_zone_abbr)
end)
end
defp add_id(countries_time_zones) do
countries_time_zones
|> Enum.map(fn country_time_zone ->
id = country_time_zone.time_zone_id <> "_" <> country_time_zone.country.code
slugified_country_name = slugify(country_time_zone.country.name)
slugify_city_or_region = fn time_zone_id ->
time_zone_id
|> String.split("/")
|> List.last()
|> slugify()
end
slugified_time_zone_id = slugify_city_or_region.(country_time_zone.time_zone_id)
country_has_multiple_canonical_time_zones? =
Enum.count(countries_time_zones, &(&1.country.code == country_time_zone.country.code)) > 1
slug = slugified_country_name <> "-" <> slugified_time_zone_id
short_slug =
if country_has_multiple_canonical_time_zones? do
slug
else
slugified_country_name
end
title =
country_time_zone.country.name <> ", " <> country_time_zone.pretty_time_zone_location
short_title =
if country_has_multiple_canonical_time_zones? do
title
else
country_time_zone.country.name
end
country_time_zone
|> Map.put(:id, id)
|> Map.put(:short_slug, short_slug)
|> Map.put(:slug, slug)
|> Map.put(:short_title, short_title)
|> Map.put(:title, title)
end)
end
defp add_time_zone_links(countries_time_zones, get_time_zone_links_for_canonical_fun) do
Enum.map(countries_time_zones, fn %{time_zone_id: time_zone_id} = time_zone ->
Map.put(
time_zone,
:time_zone_alias_ids,
get_time_zone_links_for_canonical_fun.(time_zone_id)
)
end)
end
defp add_location_name(countries_time_zones) do
Enum.map(countries_time_zones, fn %{time_zone_id: time_zone_id} = time_zone ->
pretty_time_zone_location =
time_zone_id
|> String.split("/")
|> List.last()
|> String.split("_")
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
Map.put(time_zone, :pretty_time_zone_location, pretty_time_zone_location)
end)
end
defp localize_country_name(countries) do
Enum.map(countries, fn country ->
local_names = local_country_names(country.code)
search_text = country.code <> country.name <> Enum.join(local_names)
search_text = String.downcase(search_text)
country
|> Map.put(:local_names, local_names)
|> Map.put(:search_text, search_text)
|> Map.put(:slug, slugify(country.name))
end)
end
defp add_canonical_time_zone_ids(countries, countries_time_zones) do
countries =
Enum.map(countries, fn country ->
time_zone_ids =
countries_time_zones
|> Enum.filter(&(&1.country.code == country.code))
|> Enum.map(& &1.time_zone_id)
Map.put(country, :canonical_time_zone_ids, time_zone_ids)
end)
countries_time_zones =
Enum.map(countries_time_zones, fn country_time_zone ->
country = Enum.find(countries, &(&1.code == country_time_zone.country.code))
Map.put(country_time_zone, :country, country)
end)
{countries, countries_time_zones}
end
defp slugify(string) do
string
|> String.normalize(:nfd)
|> String.downcase()
|> String.replace("_", "-")
|> String.replace("'", "-")
|> String.replace("&", "-and-")
|> String.replace(~r/[^a-z\s-]/u, "")
|> String.replace(~r/\s+/, "-")
|> String.replace(~r/-+/, "-")
|> String.trim("-")
end
defp local_country_names(country_code) do
local_countries_names =
%{
"AF" => ["افغانستان"],
"AX" => ["Åland"],
"AL" => ["Shqipëri"],
"DZ" => ["الجزائر"],
"AD" => ["Andorra"],
"AO" => ["Angola"],
"AI" => ["Anguilla"],
"AQ" => ["Antarctica"],
"AG" => ["Antigua & Barbuda"],
"AR" => ["Argentina"],
"AM" => ["Հայաստան"],
"AW" => ["Aruba"],
"AU" => ["Australia"],
"AT" => ["Österreich"],
"AZ" => ["Azərbaycan"],
"BS" => ["Bahamas"],
"BH" => ["البحرين"],
"BD" => ["বাংলাদেশ"],
"BB" => ["Barbados"],
"BY" => ["Беларусь"],
"BE" => ["België", "Belgique"],
"BZ" => ["Belize"],
"BJ" => ["Bénin"],
"BM" => ["Bermuda"],
"BT" => ["འབྲུག"],
"BO" => ["Bolivia", "Buliwya", "Wuliwya"],
"BA" => ["Bosna i Hercegovina"],
"BW" => ["Botswana"],
"BV" => ["Bouvetøya"],
"BR" => ["Brasil"],
"GB" => ["United Kingdom"],
"IO" => ["British Indian Ocean Territory"],
"BN" => ["Brunei"],
"BG" => ["България"],
"BF" => ["Burkina Faso"],
"BI" => ["Uburundi"],
"KH" => ["កម្ពុជា"],
"CM" => ["Cameroun", "Cameroon"],
"CA" => ["Canada"],
"CV" => ["Cabo Verde"],
"BQ" => ["Caribisch Nederland"],
"KY" => ["Cayman Islands"],
"CF" => ["Ködörösêse tî Bêafrîka"],
"TD" => ["Tchad", "تشاد"],
"CL" => ["Chile"],
"CN" => ["中国"],
"CX" => ["Christmas Island"],
"CC" => ["Cocos (Keeling) Islands"],
"CO" => ["Colombia"],
"KM" => ["Komori", "جزر القمر", "Comores"],
"CD" => ["République démocratique du Congo"],
"CG" => ["République du Congo"],
"CK" => ["Cook Islands"],
"CR" => ["Costa Rica"],
"CI" => ["Côte d'Ivoire"],
"HR" => ["Hrvatska"],
"CU" => ["Cuba"],
"CW" => ["Curaçao"],
"CY" => ["Κύπρος", "Kıbrıs"],
"CZ" => ["Česká republika"],
"DK" => ["Danmark"],
"DJ" => ["جيبوتي", "Djibouti"],
"DM" => ["Dominica"],
"DO" => ["República Dominicana"],
"TL" => ["Timor Lorosa'e"],
"EC" => ["Ecuador"],
"EG" => ["مصر"],
"SV" => ["El Salvador"],
"GQ" => ["Guinea Ecuatorial"],
"ER" => ["ኤርትራ", "إرتريا"],
"EE" => ["Eesti"],
"SZ" => ["Eswatini"],
"ET" => ["ኢትዮጵያ"],
"FK" => ["Falkland Islands"],
"FO" => ["Føroyar"],
"FJ" => ["Fiji"],
"FI" => ["Suomi"],
"FR" => ["France"],
"GF" => ["Guyane"],
"PF" => ["Polynésie française"],
"TF" => ["Terres australes et antarctiques françaises"],
"GA" => ["Gabon"],
"GM" => ["Gambia"],
"GE" => ["საქართველო"],
"DE" => ["Deutschland"],
"GH" => ["Ghana"],
"GI" => ["Gibraltar"],
"GR" => ["Ελλάδα"],
"GL" => ["Kalaallit Nunaat"],
"GD" => ["Grenada"],
"GP" => ["Guadeloupe"],
"GU" => ["Guåhån"],
"GT" => ["Guatemala"],
"GG" => ["Guernsey"],
"GN" => ["Guinée"],
"GW" => ["Guiné-Bissau"],
"GY" => ["Guyana"],
"HT" => ["Haïti", "Ayiti"],
"HM" => ["Heard Island & McDonald Islands"],
"HN" => ["Honduras"],
"HK" => ["香港"],
"HU" => ["Magyarország"],
"IS" => ["Ísland"],
"IN" => ["भारत", "Bharat", "இந்தியா"],
"ID" => ["Indonesia"],
"IR" => ["ایران"],
"IQ" => ["العراق"],
"IE" => ["Éire", "Ireland"],
"IM" => ["Ellan Vannin", "Isle of Man"],
"IL" => ["ישראל"],
"IT" => ["Italia"],
"JM" => ["Jamaica"],
"JP" => ["日本"],
"JE" => ["Jersey"],
"JO" => ["الأردن"],
"KZ" => ["Қазақстан", "Казахстан"],
"KE" => ["Kenya"],
"KI" => ["Kiribati"],
"KP" => ["조선"],
"KR" => ["한국"],
"KW" => ["الكويت"],
"KG" => ["Кыргызстан"],
"LA" => ["ລາວ"],
"LV" => ["Latvija"],
"LB" => ["لبنان"],
"LS" => ["Lesotho"],
"LR" => ["Liberia"],
"LY" => ["ليبيا"],
"LI" => ["Liechtenstein"],
"LT" => ["Lietuva"],
"LU" => ["Lëtzebuerg", "Luxembourg", "Luxemburg"],
"MO" => ["澳門", "Macau"],
"MG" => ["Madagasikara", "Madagascar"],
"MW" => ["Malawi"],
"MY" => ["Malaysia"],
"MV" => ["ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ"],
"ML" => ["Mali"],
"MT" => ["Malta"],
"MH" => ["M̧ajeļ", "Marshall Islands"],
"MQ" => ["Martinique"],
"MR" => ["موريتانيا"],
"MU" => ["Maurice", "Moris"],
"YT" => ["Mayotte"],
"MX" => ["México"],
"FM" => ["Micronesia"],
"MD" => ["Moldova"],
"MC" => ["Monaco"],
"MN" => ["Монгол улс"],
"ME" => ["Crna Gora", "Црна Гора"],
"MS" => ["Montserrat"],
"MA" => ["المغرب"],
"MZ" => ["Moçambique"],
"MM" => ["မြန်မာ"],
"NA" => ["Namibia"],
"NR" => ["Nauru"],
"NP" => ["नेपाल"],
"NL" => ["Nederland"],
"NC" => ["Nouvelle-Calédonie"],
"NZ" => ["New Zealand", "Aotearoa"],
"NI" => ["Nicaragua"],
"NE" => ["Niger"],
"NG" => ["Nigeria"],
"NU" => ["Niue"],
"NF" => ["Norfolk Island"],
"MK" => ["Северна Македонија"],
"MP" => ["Northern Mariana Islands"],
"NO" => ["Norge", "Noreg"],
"OM" => ["عمان"],
"PK" => ["پاکستان"],
"PW" => ["Palau"],
"PS" => ["فلسطين"],
"PA" => ["Panamá"],
"PG" => ["Papua Niugini", "Papua New Guinea"],
"PY" => ["Paraguay"],
"PE" => ["Perú"],
"PH" => ["Pilipinas"],
"PN" => ["Pitcairn"],
"PL" => ["Polska"],
"PT" => ["Portugal"],
"PR" => ["Puerto Rico"],
"QA" => ["قطر"],
"RE" => ["La Réunion"],
"RO" => ["România"],
"RU" => ["Россия"],
"RW" => ["Rwanda"],
"AS" => ["Amerika Samoa"],
"WS" => ["Samoa"],
"SM" => ["San Marino"],
"ST" => ["São Tomé e Príncipe"],
"SA" => ["السعودية"],
"SN" => ["Sénégal"],
"RS" => ["Србија"],
"SC" => ["Sesel", "Seychelles"],
"SL" => ["Sierra Leone"],
"SG" => ["新加坡", "சிங்கப்பூர்", "Singapore"],
"SK" => ["Slovensko"],
"SI" => ["Slovenija"],
"SB" => ["Solomon Islands"],
"SO" => ["Soomaaliya", "الصومال"],
"ZA" => ["South Africa", "Suid-Afrika", "Afrika Borwa"],
"GS" => ["South Georgia & the South Sandwich Islands"],
"SS" => ["جنوب السودان"],
"ES" => ["España"],
"LK" => ["ශ්රී ලංකා", "இலங்கை"],
"BL" => ["Saint-Barthélemy"],
"SH" => ["Saint Helena"],
"KN" => ["Saint Kitts & Nevis"],
"LC" => ["Saint Lucia"],
"SX" => ["Sint Maarten"],
"MF" => ["Saint-Martin"],
"PM" => ["Saint-Pierre et Miquelon"],
"VC" => ["Saint Vincent"],
"SD" => ["السودان"],
"SR" => ["Suriname"],
"SJ" => ["Svalbard og Jan Mayen"],
"SE" => ["Sverige"],
"CH" => ["Schweiz", "Suisse", "Svizzera"],
"SY" => ["سوريا"],
"TW" => ["臺灣"],
"TJ" => ["Тоҷикистон"],
"TZ" => ["Tanzania"],
"TH" => ["ไทย"],
"TG" => ["Togo"],
"TK" => ["Tokelau"],
"TO" => ["Tonga"],
"TT" => ["Trinidad & Tobago"],
"TN" => ["تونس"],
"TR" => ["Türkiye"],
"TM" => ["Türkmenistan"],
"TC" => ["Turks & Caicos Is"],
"TV" => ["Tuvalu"],
"UM" => ["US minor outlying islands"],
"UG" => ["Uganda"],
"UA" => ["Україна"],
"AE" => ["الإمارات"],
"US" => ["United States"],
"UY" => ["Uruguay"],
"UZ" => ["O'zbekiston"],
"VU" => ["Vanuatu"],
"VA" => ["Città del Vaticano", "Status Civitatis Vaticanæ"],
"VE" => ["Venezuela"],
"VN" => ["Việt Nam"],
"VG" => ["Virgin Islands"],
"VI" => ["Virgin Islands"],
"WF" => ["Wallis-et-Futuna"],
"EH" => ["الصحراء الغربية"],
"YE" => ["اليمن"],
"ZM" => ["Zambia"],
"ZW" => ["Zimbabwe"]
}
local_names = local_countries_names[country_code]
unless local_names do
raise "local country names not found for country #{country_code}"
end
local_names
end
end