defmodule Cldr.Calendar.Sigils do @moduledoc """ Implements the `~d` sigils to produce dates, datetimes and naive datetimes. """ alias Cldr.Config @doc """ Implements a ~d sigil for expressing dates. Dates can be expressed in the following formats: * `~d[yyyy-mm-dd]` which produces a date in the `Cldr.Calendar.Gregorian` calendar * `~d[yyyy-Wmm-dd]` which produces a date in the `Cldr.Calendar.ISOWeek` calendar * `~d[yyyy-mm-dd calendar]` which produces a date in the given month-based calendar * `~d[yyyy-Wmm-dd calendar]` which produces a date in the given week-based calendar * `~d[yyyy-mm-dd C.E Julian]` which produces a date in the Cldr.Calendar.Julian calendar * `~d[yyyy-mm-dd B.C.E Julian]` which produces a date in the Cldr.Calendar.Julian calendar ## Examples iex> import Cldr.Calendar.Sigils iex> ~d[2019-01-01 Gregorian] ~d[2019-01-01 Gregorian] iex> ~d[2019-W01-01] ~d[2019-W01-1 ISOWeek] """ defmacro sigil_d({:<<>>, _, [string]}, modifiers) do do_sigil_d(string, modifiers) |> Macro.escape() end def do_sigil_d(<>, _) do to_date(year, month, day, Cldr.Calendar.Gregorian) end def do_sigil_d(<>, _) do to_date(year, month, day, Cldr.Calendar.Julian) end def do_sigil_d(<>, _) do to_date("-" <> year, month, day, Calendar.Julian) end def do_sigil_d( <>, _ ) do to_date(year, month, day, calendar) end def do_sigil_d( <<"-", year::bytes-4, "-", month::bytes-2, "-", day::bytes-2, " ", calendar::binary>>, _ ) do to_date("-" <> year, month, day, calendar) end def do_sigil_d(<>, _) do to_date(year, month, day, Cldr.Calendar.ISOWeek) end def do_sigil_d(<>, _) do to_date(year, month, day, Cldr.Calendar.ISOWeek) end def do_sigil_d( <>, _ ) do to_date(year, month, day, calendar) end def do_sigil_d( <>, _ ) do to_date(year, month, day, calendar) end defp to_date(year, month, day, calendar) do [year, month, day] = Enum.map([year, month, day], &String.to_integer/1) with {:ok, calendar} <- calendar_from_binary(calendar), {:ok, date} <- Date.new(year, month, day, calendar) do date end end defp calendar_from_binary(calendar) do inbuilt_calendar(calendar) || fiscal_calendar(calendar) || user_calendar(calendar) || calendar_error(calendar) end defp inbuilt_calendar(calendar) do calendar = Module.concat(Cldr.Calendar, calendar) get_calendar(calendar) end defp fiscal_calendar(calendar) do calendar = Module.concat(Cldr.Calendar.FiscalYear, calendar) get_calendar(calendar) end defp user_calendar(calendar) do Module.concat("Elixir", calendar) |> get_calendar end defp get_calendar(calendar) do if Config.ensure_compiled?(calendar) and function_exported?(calendar, :cldr_calendar_type, 0) do {:ok, calendar} else nil end end defp calendar_error(calendar) do {:error, {Cldr.UnknownCalendarError, calendar}} end end