Packages
ex_cldr_dates_times
2.5.4
2.25.6
2.25.5
2.25.4
2.25.3
2.25.2
2.25.1
2.25.0
2.24.2
2.24.1
2.24.0
2.23.0
2.22.0
2.21.0
2.20.3
2.20.2
2.20.0
2.19.2
2.19.1
2.19.0
retired
2.18.1
2.18.0
2.17.1
2.17.0
2.16.0
2.15.0
2.14.3
2.14.2
2.14.1
2.14.0
2.13.3
2.13.2
2.13.1
2.13.0
2.12.0
2.11.0
2.10.2
2.10.1
2.10.0
2.10.0-rc.3
2.10.0-rc.2
2.10.0-rc.1
2.10.0-rc.0
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.0
2.7.2
2.7.1
retired
2.7.0
2.7.0-rc.0
2.6.4
2.6.3
2.6.2
2.6.1
retired
2.6.0
2.6.0-rc.0
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.4.0
2.4.0-rc.0
2.3.0
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.0.1
1.0.0
1.0.0-rc.1
1.0.0-rc.0
retired
0.3.3
0.3.2
retired
0.3.1
retired
0.3.0
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Date, Time and DateTime localization, internationalization and formatting functions using the Common Locale Data Repository (CLDR).
Current section
Files
Jump to
Current section
Files
lib/cldr/format/datetime_format.ex
defmodule Cldr.DateTime.Format do
@moduledoc """
Manages the Date, Time and DateTime formats
defined by CLDR.
The functions in `Cldr.DateTime.Format` are
primarily concerned with encapsulating the
data from CLDR in functions that are used
during the formatting process.
"""
alias Cldr.Locale
alias Cldr.LanguageTag
@type standard_formats :: %{
full: String.t(),
long: String.t(),
medium: String.t(),
short: String.t()
}
@type formats :: Cldr.Calendar.calendar()
@doc false
def format_list(config) do
locale_names = Cldr.Config.known_locale_names(config)
backend = config.backend
((known_formats(&all_date_formats(&1, backend), locale_names) ++
known_formats(&all_time_formats(&1, backend), locale_names) ++
known_formats(&all_date_time_formats(&1, backend), locale_names) ++
known_formats(&all_interval_formats(&1, backend), locale_names)) ++
config.precompile_date_time_formats ++ precompile_interval_formats(config))
|> Enum.uniq()
|> Enum.reject(&is_atom/1)
end
defp precompile_interval_formats(config) do
config.precompile_interval_formats
|> Enum.flat_map(&split_interval!/1)
end
@doc """
Returns a list of calendars defined for a given locale.
## Arguments
* `locale` is any valid locale name returned by `Cldr.known_locale_names/0`
or a `Cldr.LanguageTag` struct. The default is `Cldr.get_locale/0`
## Example
iex> Cldr.DateTime.Format.calendars_for "en", MyApp.Cldr
{:ok, [:buddhist, :chinese, :coptic, :dangi, :ethiopic, :ethiopic_amete_alem,
:generic, :gregorian, :hebrew, :indian, :islamic, :islamic_civil,
:islamic_rgsa, :islamic_tbla, :islamic_umalqura, :japanese, :persian, :roc]}
"""
@spec calendars_for(Locale.locale_name() | LanguageTag.t(), Cldr.backend()) ::
{:ok, [Cldr.Calendar.calendar(), ...]} | {:error, {atom, String.T}}
def calendars_for(locale, backend \\ Cldr.Date.default_backend()) do
backend = Module.concat(backend, DateTime.Format)
backend.calendars_for(locale)
end
@doc """
Returns the GMT offset format list for a
for a timezone offset for a given locale.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
## Example
iex> Cldr.DateTime.Format.gmt_format "en", MyApp.Cldr
{:ok, ["GMT", 0]}
"""
@spec(
gmt_format(Locale.locale_name() | LanguageTag.t(), Cldr.backend()) ::
{:ok, [non_neg_integer | String.t(), ...]},
{:error, {atom, String.t()}}
)
def gmt_format(locale, backend \\ Cldr.Date.default_backend()) do
backend = Module.concat(backend, DateTime.Format)
backend.gmt_format(locale)
end
@doc """
Returns the GMT format string for a
for a timezone with an offset of zero for
a given locale.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
## Example
iex> Cldr.DateTime.Format.gmt_zero_format "en", MyApp.Cldr
{:ok, "GMT"}
"""
@spec gmt_zero_format(Locale.locale_name() | LanguageTag.t(), Cldr.backend()) ::
{:ok, String.t()} | {:error, {atom, String.t()}}
def gmt_zero_format(locale, backend \\ Cldr.Date.default_backend()) do
backend = Module.concat(backend, DateTime.Format)
backend.gmt_zero_format(locale)
end
@doc """
Returns the postive and negative hour format
for a timezone offset for a given locale.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
## Example
iex> Cldr.DateTime.Format.hour_format "en", MyApp.Cldr
{:ok, {"+HH:mm", "-HH:mm"}}
"""
@spec hour_format(Locale.locale_name() | LanguageTag.t(), Cldr.backend()) ::
{:ok, {String.t(), String.t()}} | {:error, {atom, String.t()}}
def hour_format(locale \\ Cldr.get_locale(), backend \\ Cldr.Date.default_backend()) do
backend = Module.concat(backend, DateTime.Format)
backend.hour_format(locale)
end
@doc """
Returns a map of the standard date formats for a given locale and calendar.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
* `calendar` is any calendar returned by `Cldr.DateTime.Format.calendars_for/1`
The default is `:gregorian`
## Examples:
iex> Cldr.DateTime.Format.date_formats "en", :gregorian, MyApp.Cldr
{:ok, %Cldr.Date.Styles{
full: "EEEE, MMMM d, y",
long: "MMMM d, y",
medium: "MMM d, y",
short: "M/d/yy"
}}
iex> Cldr.DateTime.Format.date_formats "en", :buddhist, MyApp.Cldr
{:ok, %Cldr.Date.Styles{
full: "EEEE, MMMM d, y G",
long: "MMMM d, y G",
medium: "MMM d, y G",
short: "M/d/y GGGGG"
}}
"""
@spec date_formats(
Locale.locale_name() | LanguageTag.t(),
Cldr.Calendar.calendar(),
Cldr.backend()
) ::
{:ok, standard_formats} | {:error, {atom, String.t()}}
def date_formats(
locale \\ Cldr.get_locale(),
calendar \\ Cldr.Calendar.default_cldr_calendar(),
backend \\ Cldr.Date.default_backend()
) do
backend = Module.concat(backend, DateTime.Format)
backend.date_formats(locale, calendar)
end
@doc """
Returns a map of the standard time formats for a given locale and calendar.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
* `calendar` is any calendar returned by `Cldr.DateTime.Format.calendars_for/1`
The default is `:gregorian`
## Examples:
iex> Cldr.DateTime.Format.time_formats "en"
{:ok, %Cldr.Time.Styles{
full: "h:mm:ss a zzzz",
long: "h:mm:ss a z",
medium: "h:mm:ss a",
short: "h:mm a"
}}
iex> Cldr.DateTime.Format.time_formats "en", :buddhist
{:ok, %Cldr.Time.Styles{
full: "h:mm:ss a zzzz",
long: "h:mm:ss a z",
medium: "h:mm:ss a",
short: "h:mm a"
}}
"""
@spec time_formats(Locale.locale_name() | LanguageTag, Cldr.Calendar.calendar(), Cldr.backend()) ::
{:ok, standard_formats} | {:error, {atom, String.t()}}
def time_formats(
locale \\ Cldr.get_locale(),
calendar \\ Cldr.Calendar.default_cldr_calendar(),
backend \\ Cldr.Date.default_backend()
) do
backend = Module.concat(backend, DateTime.Format)
backend.time_formats(locale, calendar)
end
@doc """
Returns a map of the standard datetime formats for a given locale and calendar.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
* `calendar` is any calendar returned by `Cldr.DateTime.Format.calendars_for/1`
The default is `:gregorian`
## Examples:
iex> Cldr.DateTime.Format.date_time_formats "en"
{:ok, %Cldr.DateTime.Styles{
full: "{1} 'at' {0}",
long: "{1} 'at' {0}",
medium: "{1}, {0}",
short: "{1}, {0}"
}}
iex> Cldr.DateTime.Format.date_time_formats "en", :buddhist, MyApp.Cldr
{:ok, %Cldr.DateTime.Styles{
full: "{1} 'at' {0}",
long: "{1} 'at' {0}",
medium: "{1}, {0}",
short: "{1}, {0}"
}}
"""
@spec date_time_formats(
Locale.locale_name() | LanguageTag.t(),
Cldr.Calendar.calendar(),
Cldr.backend()
) ::
{:ok, map()} | {:error, {atom, String.t()}}
def date_time_formats(
locale \\ Cldr.get_locale(),
calendar \\ Cldr.Calendar.default_cldr_calendar(),
backend \\ Cldr.Date.default_backend()
) do
backend = Module.concat(backend, DateTime.Format)
backend.date_time_formats(locale, calendar)
end
@doc """
Returns a map of the available non-standard datetime formats for a
given locale and calendar.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
* `calendar` is any calendar returned by `Cldr.DateTime.Format.calendars_for/1`
The default is `:gregorian`
## Examples:
iex> Cldr.DateTime.Format.date_time_available_formats "en"
{:ok,
%{
yw_count_other: "'week' w 'of' Y",
mmm: "LLL",
d: "d",
ehm: "E h:mm a",
y_mmm: "MMM y",
mm_md: "MMM d",
gy_mm_md: "MMM d, y G",
e_bhm: "E h:mm B",
ed: "d E",
mmm_md: "MMMM d",
ehms: "E h:mm:ss a",
y_qqq: "QQQ y",
y_qqqq: "QQQQ y",
m_ed: "E, M/d",
md: "M/d",
bhm: "h:mm B",
hmv: "HH:mm v",
y_m: "M/y",
gy_mmm: "MMM y G",
mmm_ed: "E, MMM d",
y_m_ed: "E, M/d/y",
y_mm_md: "MMM d, y",
gy_mmm_ed: "E, MMM d, y G",
e_hms: "E HH:mm:ss",
e: "ccc",
e_hm: "E HH:mm",
yw_count_one: "'week' w 'of' Y",
mmmmw_count_one: "'week' W 'of' MMMM",
e_bhms: "E h:mm:ss B",
hms: "HH:mm:ss",
y_mmm_ed: "E, MMM d, y",
y_md: "M/d/y",
ms: "mm:ss",
hmsv: "HH:mm:ss v",
hm: "HH:mm",
h: "HH",
mmmmw_count_other: "'week' W 'of' MMMM",
bh: "h B",
m: "L",
bhms: "h:mm:ss B",
y_mmmm: "MMMM y",
y: "y",
gy: "y G"
}}
"""
@spec date_time_available_formats(
Locale.locale_name() | LanguageTag.t(),
Cldr.Calendar.calendar(),
Cldr.backend()
) :: {:ok, map()} | {:error, {atom, String.t()}}
def date_time_available_formats(
locale \\ Cldr.get_locale(),
calendar \\ Cldr.Calendar.default_cldr_calendar(),
backend \\ Cldr.Date.default_backend()
) do
backend = Module.concat(backend, DateTime.Format)
backend.date_time_available_formats(locale, calendar)
end
@doc """
Returns a map of the interval formats for a
given locale and calendar.
## Arguments
* `locale` is any locale returned by `Cldr.known_locale_names/0`
* `calendar` is any calendar returned by `Cldr.DateTime.Format.calendars_for/1`
The default is `:gregorian`
## Examples:
Cldr.DateTime.Format.interval_formats "en", :gregorian, MyApp.Cldr
=> {:ok,
%{
bh: %{b: ["h B", "h B"], h: ["h", "h B"]},
bhm: %{b: ["h:mm B", "h:mm B"], h: ["h:mm", "h:mm B"], m: ["h:mm", "h:mm B"]},
d: %{d: ["d", "d"]},
gy: %{g: ["y G", "y G"], y: ["y", "y G"]},
...
"""
@spec interval_formats(
Locale.locale_name() | LanguageTag.t(),
Cldr.Calendar.calendar(),
Cldr.backend()
) :: {:ok, map()} | {:error, {atom, String.t()}}
def interval_formats(
locale \\ Cldr.get_locale(),
calendar \\ Cldr.Calendar.default_cldr_calendar(),
backend \\ Cldr.Date.default_backend()
) do
backend = Module.concat(backend, DateTime.Format)
backend.date_time_interval_formats(locale, calendar)
end
@doc """
Returns a list of the date_time format types that are
available in all locales.
The format types returned by `common_date_time_format_names`
are guaranteed to be available in all known locales,
## Example:
iex> Cldr.DateTime.Format.common_date_time_format_names
[:bh, :bhm, :bhms, :d, :e, :e_bhm, :e_bhms, :e_hm, :e_hms, :ed, :ehm,
:ehms, :gy, :gy_mm_md, :gy_mmm, :gy_mmm_ed, :h, :hm, :hms, :hmsv,
:hmv, :m, :m_ed, :md, :mm_md, :mmm, :mmm_ed, :mmm_md,
:mmmmw_count_other, :ms, :y, :y_m, :y_m_ed, :y_md, :y_mm_md, :y_mmm,
:y_mmm_ed, :y_mmmm, :y_qqq, :y_qqqq, :yw_count_other]
"""
def common_date_time_format_names(backend \\ Cldr.Date.default_backend()) do
datetime_module = Module.concat(backend, DateTime.Format)
Cldr.known_locale_names(backend)
|> Enum.map(&datetime_module.date_time_available_formats/1)
|> Enum.map(&elem(&1, 1))
|> Enum.map(&Map.keys/1)
|> Enum.map(&MapSet.new/1)
|> intersect_mapsets
|> MapSet.to_list()
|> Enum.sort()
end
defp known_formats(list, locale_names) do
locale_names
|> Enum.map(&list.(&1))
|> List.flatten()
|> Enum.uniq()
end
defp all_date_formats(locale, backend) do
datetime_backend = Module.concat(backend, DateTime.Format)
all_formats_for(locale, backend, &datetime_backend.date_formats/2)
end
defp all_time_formats(locale, backend) do
datetime_backend = Module.concat(backend, DateTime.Format)
all_formats_for(locale, backend, &datetime_backend.time_formats/2)
end
defp all_date_time_formats(locale, backend) do
datetime_backend = Module.concat(backend, DateTime.Format)
all_formats_for(locale, backend, &datetime_backend.date_time_formats/2) ++
all_formats_for(locale, backend, &datetime_backend.date_time_available_formats/2)
end
defp all_interval_formats(locale, backend) do
datetime_backend = Module.concat(backend, DateTime.Format)
all_interval_formats_for(locale, backend, &datetime_backend.date_time_interval_formats/2)
end
defp all_formats_for(locale, backend, type_function) do
with {:ok, calendars} <- calendars_for(locale, backend) do
Enum.map(calendars, fn calendar ->
{:ok, calendar_formats} = type_function.(locale, calendar)
Map.values(calendar_formats)
end)
|> List.flatten()
|> Enum.uniq()
end
end
defp all_interval_formats_for(locale, backend, type_function) do
with {:ok, calendars} <- calendars_for(locale, backend) do
Enum.map(calendars, fn calendar ->
{:ok, calendar_formats} = type_function.(locale, calendar)
calendar_formats
|> Map.values()
|> Enum.filter(&is_map/1)
|> Enum.flat_map(&Map.values/1)
end)
|> List.flatten()
|> Enum.uniq()
end
end
defp intersect_mapsets([a, b | []]) do
MapSet.intersection(a, b)
end
defp intersect_mapsets([a, b | tail]) do
intersect_mapsets([MapSet.intersection(a, b) | tail])
end
# All locales define an hour_format that have the following characteristics:
# > :hour and :minute only (and always both)
# > :minute is always 2 digits: "mm"
# > always have a sign + or -
# > have either a separator of ":", "." or no separator
# Therefore the format is always either 4 parts (with separator) or 3 parts (without separator)
# Short format with zero minutes
def gmt_format_type([sign, hour, _sep, "00"], :short) do
[sign, String.replace_leading(hour, "0", "")]
end
# Short format with minutes > 0
def gmt_format_type([sign, hour, sep, minute], :short) do
[sign, String.replace_leading(hour, "0", ""), sep, minute]
end
# Long format
def gmt_format_type([sign, hour, sep, minute], :long) do
[sign, hour, sep, minute]
end
# The case when there is no separator
def gmt_format_type([sign, hour, minute], format_type) do
gmt_format_type([sign, hour, "", minute], format_type)
end
### Helpers
@doc false
# Used during compilation to split an interval into
# the from and to parts
def split_interval(interval) do
case do_split_interval(interval, [], "") do
{:error, reason} -> {:error, reason}
success -> {:ok, success}
end
end
@doc false
def split_interval!(interval) do
case do_split_interval(interval, [], "") do
{:error, {exception, reason}} -> raise exception, reason
success -> success
end
end
defp do_split_interval("", _acc, left) do
{:error,
{Cldr.DateTime.IntervalFormatError, "Invalid datetime interval format #{inspect(left)}"}}
end
# Quoted strings pass through. This assumes the quotes
# are correctly closed.
@literal "'"
defp do_split_interval(<<@literal, rest::binary>>, acc, left) do
[literal, rest] = String.split(rest, @literal, parts: 2)
do_split_interval(rest, acc, left <> @literal <> literal <> @literal)
end
# characters that are not format characters
# pass through
defp do_split_interval(<<c::utf8, rest::binary>>, acc, left)
when c not in ?a..?z and c not in ?A..?Z do
do_split_interval(rest, acc, left <> List.to_string([c]))
end
# Handle format characters that repeat up to a maximum of
# 5 times
defp do_split_interval(
<<c::binary-1, c::binary-1, c::binary-1, c::binary-1, c::binary-1, rest::binary>>,
acc,
left
) do
if c in acc do
[left, String.duplicate(c, 5) <> rest]
else
do_split_interval(rest, [c | acc], left <> String.duplicate(c, 5))
end
end
defp do_split_interval(
<<c::binary-1, c::binary-1, c::binary-1, c::binary-1, rest::binary>>,
acc,
left
) do
if c in acc do
[left, String.duplicate(c, 4) <> rest]
else
do_split_interval(rest, [c | acc], left <> String.duplicate(c, 4))
end
end
defp do_split_interval(<<c::binary-1, c::binary-1, c::binary-1, rest::binary>>, acc, left) do
if c in acc do
[left, String.duplicate(c, 3) <> rest]
else
do_split_interval(rest, [c | acc], left <> String.duplicate(c, 3))
end
end
defp do_split_interval(<<c::binary-1, c::binary-1, rest::binary>>, acc, left) do
if c in acc do
[left, String.duplicate(c, 2) <> rest]
else
do_split_interval(rest, [c | acc], left <> String.duplicate(c, 2))
end
end
defp do_split_interval(<<c::binary-1, rest::binary>>, acc, left) do
if c in acc do
[left, c <> rest]
else
do_split_interval(rest, [c | acc], left <> c)
end
end
end