lib/cldr/time_zone_name.ex

defmodule Cldr.TimeZoneName do
  @moduledoc """
  Support for fetching time zone info by zone name and meta zone.
  """

  alias Cldr.TimeZoneName.Info

  @doc """
  Fetches information for a given time zone and meta zone.

  ## Arguments

  * `zone_name` is an Olson time zone name (e.g. "America/Chicago").
  * `meta_zone` ts the meta zone type for the time zone (e.g. "America_Central").
  * `opts` is a keyword list of options.

  ## Options

  * `:locale` is any locale or locale name validated
    by `Cldr.validate_locale/2`. The default is
    `Cldr.get_locale()` which returns the locale
    set for the current process

  """
  @callback resolve(
              zone_name :: Elixir.Calendar.time_zone(),
              meta_zone :: String.t(),
              opts :: Keyword.t()
            ) :: {:ok, Info.t()} | {:error, term()}

  @doc false
  def cldr_backend_provider(config) do
    module = inspect(__MODULE__)
    backend = config.backend
    config = Macro.escape(config)

    quote location: :keep,
          bind_quoted: [
            module: module,
            backend: backend,
            config: config
          ] do
      defmodule TimeZoneName do
        @moduledoc false
        alias Cldr.LanguageTag
        alias Cldr.Locale

        alias Cldr.TimeZoneName.Info

        @behaviour Cldr.TimeZoneName

        # Simpler than unquoting the backend everywhere
        defp backend, do: unquote(backend)
        defp get_locale, do: backend().get_locale()
        defp default_locale, do: backend().default_locale()

        @impl Cldr.TimeZoneName
        def resolve(zone_name, meta_zone, opts \\ []) do
          resolve_by_locale(zone_name, meta_zone, opts[:locale] || get_locale())
        end

        defp resolve_by_locale(zone_name, meta_zone, %LanguageTag{
               cldr_locale_name: cldr_locale_name
             }) do
          resolve_by_locale(zone_name, meta_zone, cldr_locale_name)
        end

        for locale_name <- Locale.Loader.known_locale_names(config) do
          locale_data = Locale.Loader.get_locale(locale_name, config)

          zones =
            locale_data
            |> Map.get(:dates, %{})
            |> Map.get(:time_zone_names, %{})
            |> Map.get(:zone, %{})

          meta_zones =
            locale_data
            |> Map.get(:dates, %{})
            |> Map.get(:time_zone_names, %{})
            |> Map.get(:metazone, %{})

          defp resolve_by_locale(zone_name, meta_zone, unquote(locale_name)) do
            zones = unquote(Macro.escape(zones))
            meta_zones = unquote(Macro.escape(meta_zones))

            zone_name_parts =
              zone_name
              |> String.split("/")
              |> Enum.map(&String.downcase/1)

            zone_data =
              Enum.reduce(zone_name_parts, zones, fn part, acc ->
                Map.get(acc, part, %{})
              end)

            meta_zone_data = meta_zones[String.downcase(meta_zone)]

            if meta_zone_data do
              {:ok, Info.new(zone_data, meta_zone_data)}
            else
              {:error, "Meta zone type \"#{meta_zone}\" not found"}
            end
          end
        end

        defp resolve_by_locale(_zone_name, _meta_zone, locale),
          do: {:error, Locale.locale_error(locale)}
      end
    end
  end
end