Skip to content

Commit

Permalink
Add style: :at to Cldr.DateTime.to_string/2
Browse files Browse the repository at this point in the history
  • Loading branch information
kipcole9 committed Oct 13, 2023
1 parent 529f3fa commit ce18e4c
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 104 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

This is the changelog for Cldr_Dates_Times v2.15.0 released on _____, 2023. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_cldr_dates_times/tags)

### Deprecations

* The support of `:style` as a synonym for `:format` with the functions `Cldr.Time.to_string/2`, `Cldr.Date.to_string/2` and `Cldr.DateTime.to_string/2` is now removed. The `:style` option is now used influence the use of "at" formats in `Cldr.DateTime.to_string/2`. `:style` also remains a valid option for interval formatting.

### Enhancements

* Add support for "at" style formatting for `DateTime` structs. This style is documented in [TR35](https://unicode.org/reports/tr35/tr35-dates.html#Date_Time_Combination_Examples) and was introduced in [CLDR 43](https://cldr.unicode.org/index/downloads/cldr-43). Thanks to @jueberschlag for the report and motivation to get this done.
Expand Down
38 changes: 19 additions & 19 deletions lib/cldr/backend/format.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defmodule Cldr.DateTime.Format.Backend do
## Example
iex> #{inspect(__MODULE__)}.calendars_for :en
iex> #{inspect(__MODULE__)}.calendars_for(:en)
{: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]}
Expand Down Expand Up @@ -77,16 +77,16 @@ defmodule Cldr.DateTime.Format.Backend do
## Examples:
iex> #{inspect(__MODULE__)}.date_formats :en
{:ok, %Cldr.Date.Styles{
iex> #{inspect(__MODULE__)}.date_formats(:en)
{:ok, %Cldr.Date.Formats{
full: "EEEE, MMMM d, y",
long: "MMMM d, y",
medium: "MMM d, y",
short: "M/d/yy"
}}
iex> #{inspect(__MODULE__)}.date_formats :en, :buddhist
{:ok, %Cldr.Date.Styles{
iex> #{inspect(__MODULE__)}.date_formats(:en, :buddhist)
{:ok, %Cldr.Date.Formats{
full: "EEEE, MMMM d, y G",
long: "MMMM d, y G",
medium: "MMM d, y G",
Expand Down Expand Up @@ -124,16 +124,16 @@ defmodule Cldr.DateTime.Format.Backend do
## Examples:
iex> #{inspect(__MODULE__)}.time_formats :en
{:ok, %Cldr.Time.Styles{
iex> #{inspect(__MODULE__)}.time_formats(:en)
{:ok, %Cldr.Time.Formats{
full: "h:mm:ss a zzzz",
long: "h:mm:ss a z",
medium: "h:mm:ss a",
short: "h:mm a"
}}
iex> #{inspect(__MODULE__)}.time_formats :en, :buddhist
{:ok, %Cldr.Time.Styles{
iex> #{inspect(__MODULE__)}.time_formats(:en, :buddhist)
{:ok, %Cldr.Time.Formats{
full: "h:mm:ss a zzzz",
long: "h:mm:ss a z",
medium: "h:mm:ss a",
Expand Down Expand Up @@ -171,16 +171,16 @@ defmodule Cldr.DateTime.Format.Backend do
## Examples:
iex> #{inspect(__MODULE__)}.date_time_formats :en
{:ok, %Cldr.DateTime.Styles{
iex> #{inspect(__MODULE__)}.date_time_formats(:en)
{:ok, %Cldr.DateTime.Formats{
full: "{1}, {0}",
long: "{1}, {0}",
medium: "{1}, {0}",
short: "{1}, {0}"
}}
iex> #{inspect(__MODULE__)}.date_time_formats :en, :buddhist
{:ok, %Cldr.DateTime.Styles{
iex> #{inspect(__MODULE__)}.date_time_formats(:en, :buddhist)
{:ok, %Cldr.DateTime.Formats{
full: "{1}, {0}",
long: "{1}, {0}",
medium: "{1}, {0}",
Expand Down Expand Up @@ -224,15 +224,15 @@ defmodule Cldr.DateTime.Format.Backend do
## Examples:
iex> #{inspect(__MODULE__)}.date_time_at_formats(:en)
{:ok, %Cldr.DateTime.Styles{
{:ok, %Cldr.DateTime.Formats{
full: "{1} 'at' {0}",
long: "{1} 'at' {0}",
medium: "{1}, {0}",
short: "{1}, {0}"}
}
iex> #{inspect(__MODULE__)}.date_time_at_formats(:en, :buddhist)
{:ok, %Cldr.DateTime.Styles{
{:ok, %Cldr.DateTime.Formats{
full: "{1} 'at' {0}",
long: "{1} 'at' {0}",
medium: "{1}, {0}",
Expand Down Expand Up @@ -534,21 +534,21 @@ defmodule Cldr.DateTime.Format.Backend do
|> Map.get(:dates)
|> get_in([:calendars, calendar])

date_formats = struct(Cldr.Date.Styles, Map.get(calendar_data, :date_formats))
date_formats = struct(Cldr.Date.Formats, Map.get(calendar_data, :date_formats))

def date_formats(unquote(locale), unquote(calendar)) do
{:ok, unquote(Macro.escape(date_formats))}
end

time_formats = struct(Cldr.Time.Styles, Map.get(calendar_data, :time_formats))
time_formats = struct(Cldr.Time.Formats, Map.get(calendar_data, :time_formats))

def time_formats(unquote(locale), unquote(calendar)) do
{:ok, unquote(Macro.escape(time_formats))}
end

date_time_formats =
struct(
Cldr.DateTime.Styles,
Cldr.DateTime.Formats,
Map.get(calendar_data, :date_time_formats)
|> Map.take(@standard_formats)
)
Expand All @@ -559,7 +559,7 @@ defmodule Cldr.DateTime.Format.Backend do

date_time_at_formats =
struct(
Cldr.DateTime.Styles,
Cldr.DateTime.Formats,
Map.get(calendar_data, :date_time_formats_at_time)
|> Map.get(:standard)
|> Map.take(@standard_formats)
Expand Down
2 changes: 1 addition & 1 deletion lib/cldr/backend/formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ defmodule Cldr.DateTime.Formatter.Backend do
| Elixir.Calendar.time()
| Elixir.Calendar.datetime(),
String.t(),
Cldr.LanguageTag.t() | Cldr.Locale.locale_name(),
Cldr.Locale.locale_reference(),
Keyword.t()
) :: {:ok, String.t()} | {:error, {module(), String.t()}}

Expand Down
20 changes: 10 additions & 10 deletions lib/cldr/date.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ defmodule Cldr.Date do
alias Cldr.DateTime.Format
alias Cldr.LanguageTag

@style_types [:short, :medium, :long, :full]
@format_types [:short, :medium, :long, :full]
@default_type :medium

defmodule Styles do
defmodule Formats do
@moduledoc false
defstruct Module.get_attribute(Cldr.Date, :style_types)
defstruct Module.get_attribute(Cldr.Date, :format_types)
end

@doc """
Expand All @@ -43,14 +43,14 @@ defmodule Cldr.Date do
## Options
* `format:` `:short` | `:medium` | `:long` | `:full` or a format string.
The default is `:medium`
* `:format` is on of `:short`, `:medium`, `:long`, `:full` or a format string.
The default is `:medium`.
* `locale:` any locale returned by `Cldr.known_locale_names/1`.
The default is `Cldr.get_locale()`.
The default is `Cldr.get_locale/0`.
* `number_system:` a number system into which the formatted date digits
should be transliterated
* `:number_system` a number system into which the formatted date digits
should be transliterated.
## Returns
Expand Down Expand Up @@ -208,7 +208,7 @@ defmodule Cldr.Date do
end

defp format_string(format, %LanguageTag{cldr_locale_name: locale_name}, calendar, backend)
when format in @style_types do
when format in @format_types do
with {:ok, date_formats} <- Format.date_formats(locale_name, calendar, backend) do
{:ok, Map.get(date_formats, format)}
end
Expand All @@ -222,7 +222,7 @@ defmodule Cldr.Date do
defp format_string(style, _locale, _calendar, _backend) when is_atom(style) do
{:error,
{Cldr.DateTime.InvalidStyle,
"Invalid date style. " <> "The valid styles are #{inspect(@style_types)}."}}
"Invalid date style. " <> "The valid styles are #{inspect(@format_types)}."}}
end

defp format_string(format_string, _locale, _calendar, _backend)
Expand Down
90 changes: 54 additions & 36 deletions lib/cldr/datetime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ defmodule Cldr.DateTime do
alias Cldr.DateTime.Format
alias Cldr.LanguageTag

@style_types [:short, :medium, :long, :full]
@format_types [:short, :medium, :long, :full]
@default_type :medium
@default_style :default

defmodule Styles do
defmodule Formats do
@moduledoc false
defstruct Module.get_attribute(Cldr.DateTime, :style_types)
defstruct Module.get_attribute(Cldr.DateTime, :format_types)
end

@doc """
Expand All @@ -44,22 +45,26 @@ defmodule Cldr.DateTime do
## Options
* `format:` `:short` | `:medium` | `:long` | `:full` or a format string or
* `:format` is one of `:short`, `:medium`, `:long`, `:full` or a format string or
any of the keys returned by `Cldr.DateTime.Format.date_time_available_formats/0`.
The default is `:medium`
The default is `:medium`.
* `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`
* `:style` is either `:at` or `:default`. When set to `:at` the datetime is
formatted with a localised string representing `<date> at `<time>`. See
`Cldr.DateTime.Format.date_time_at_formats/2`.
* `number_system:` a number system into which the formatted date digits should
be transliterated
* `: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`.
* `:number_system` a number system into which the formatted date digits should
be transliterated.
* `era: :variant` will use a variant for the era is one is available in the locale.
In the "en" for example, the locale `era: :variant` will return "BCE" instead of "BC".
* `period: :variant` will use a variant for the time period and flexible time period if
one is available in the locale. For example, in the "en" locale `period: :variant` will
return "pm" instead of "PM"
return "pm" instead of "PM".
## Returns
Expand Down Expand Up @@ -109,7 +114,7 @@ defmodule Cldr.DateTime do
with {:ok, locale} <- Cldr.validate_locale(options[:locale], backend),
{:ok, cldr_calendar} <- type_from_calendar(calendar),
{:ok, _} <- Cldr.Number.validate_number_system(locale, number_system, backend),
{:ok, format_string} <- format_string(options[:format], locale, cldr_calendar, backend),
{:ok, format_string} <- format_string(options[:format], options[:style], locale, cldr_calendar, backend),
{:ok, formatted} <- format_backend.format(datetime, format_string, locale, options) do
{:ok, formatted}
end
Expand All @@ -126,19 +131,20 @@ defmodule Cldr.DateTime do
{locale, _backend} = Cldr.locale_and_backend_from(nil, backend)
number_system = Cldr.Number.System.number_system_from_locale(locale, backend)

[locale: locale, number_system: number_system, format: @default_type]
[locale: locale, number_system: number_system, format: @default_type, style: @default_style]
end

defp normalize_options(backend, options) do
{locale, _backend} = Cldr.locale_and_backend_from(options[:locale], backend)
format = options[:format] || options[:style] || @default_type
format = options[:format] || @default_type
style = options[:style] || @default_style
locale_number_system = Cldr.Number.System.number_system_from_locale(locale, backend)
number_system = Keyword.get(options, :number_system, locale_number_system)

options
|> Keyword.put(:locale, locale)
|> Keyword.put(:format, format)
|> Keyword.delete(:style)
|> Keyword.put(:style, style)
|> Keyword.put_new(:number_system, number_system)
end

Expand Down Expand Up @@ -174,22 +180,26 @@ defmodule Cldr.DateTime do
## Options
* `format:` `:short` | `:medium` | `:long` | `:full` or a format string or
any of the keys returned by `Cldr.DateTime.available_format_names` or a format string.
The default is `:medium`
* `:format` is one of `:short`, `:medium`, `:long`, `:full` or a format string or
any of the keys returned by `Cldr.DateTime.Format.date_time_available_formats/0`.
The default is `:medium`.
* `:style` is either `:at` or `:default`. When set to `:at` the datetime is
formatted with a localised string representing `<date> at `<time>`. See
`Cldr.DateTime.Format.date_time_at_formats/2`.
* `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`
* `: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`.
* `number_system:` a number system into which the formatted date digits should
be transliterated
* `:number_system` a number system into which the formatted date digits should
be transliterated.
* `era: :variant` will use a variant for the era is one is available in the locale.
In the "en" for example, the locale `era: :variant` will return "BCE" instead of "BC".
* `period: :variant` will use a variant for the time period and flexible time period if
one is available in the locale. For example, in the "en" locale `period: :variant` will
return "pm" instead of "PM"
return "pm" instead of "PM".
## Returns
Expand Down Expand Up @@ -221,39 +231,47 @@ defmodule Cldr.DateTime do
end
end

# Standard format
defp format_string(style, %LanguageTag{cldr_locale_name: locale_name}, cldr_calendar, backend)
when style in @style_types do
with {:ok, styles} <- Format.date_time_formats(locale_name, cldr_calendar, backend) do
{:ok, Map.get(styles, style)}
# Standard format, at style
defp format_string(format, :at, %LanguageTag{cldr_locale_name: locale_name}, cldr_calendar, backend)
when format in @format_types do
with {:ok, formats} <- Format.date_time_at_formats(locale_name, cldr_calendar, backend) do
{:ok, Map.get(formats, format)}
end
end

# Standard format, standard style
defp format_string(format, _style, %LanguageTag{cldr_locale_name: locale_name}, cldr_calendar, backend)
when format in @format_types do
with {:ok, formats} <- Format.date_time_formats(locale_name, cldr_calendar, backend) do
{:ok, Map.get(formats, format)}
end
end

# Look up for the format in :available_formats
defp format_string(style, %LanguageTag{cldr_locale_name: locale_name}, cldr_calendar, backend)
when is_atom(style) do
with {:ok, styles} <-
defp format_string(format, _style, %LanguageTag{cldr_locale_name: locale_name}, cldr_calendar, backend)
when is_atom(format) do
with {:ok, formats} <-
Format.date_time_available_formats(locale_name, cldr_calendar, backend),
format_string <- Map.get(styles, style) do
format_string <- Map.get(formats, format) do
if format_string do
{:ok, format_string}
else
{:error,
{Cldr.DateTime.InvalidStyle,
"Invalid datetime style #{inspect(style)}. " <>
"The valid styles are #{inspect(styles)}."}}
"Invalid datetime style #{inspect(format)}. " <>
"The valid formaats are #{inspect(formats)}."}}
end
end
end

# Format with a number system
defp format_string(%{number_system: number_system, format: style}, locale, calendar, backend) do
{:ok, format_string} = format_string(style, locale, calendar, backend)
defp format_string(%{number_system: number_system, format: format}, style, locale, calendar, backend) do
{:ok, format_string} = format_string(format, style, locale, calendar, backend)
{:ok, %{number_system: number_system, format: format_string}}
end

# Straight up format string
defp format_string(format_string, _locale, _calendar, _backend)
defp format_string(format_string, _style, _locale, _calendar, _backend)
when is_binary(format_string) do
{:ok, format_string}
end
Expand Down
Loading

0 comments on commit ce18e4c

Please sign in to comment.