Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 42 additions & 17 deletions lib/elixir/lib/calendar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -461,25 +461,30 @@ defmodule Calendar do
it can't contain the `%X` format and defaults to `"%H:%M:%S"`
if the option is not received

* `:am_pm_names` - a function that receives either `:am` or `:pm` and returns
* `:am_pm_names` - a function that receives either `:am` or `:pm`
(and also the datetime if the function is arity/2) and returns
the name of the period of the day, if the option is not received it defaults
to a function that returns `"am"` and `"pm"`, respectively

* `:month_names` - a function that receives a number and returns the name of
* `:month_names` - a function that receives a number (and also the
datetime if the function is arity/2) and returns the name of
the corresponding month, if the option is not received it defaults to a
function that returns the month names in English

* `:abbreviated_month_names` - a function that receives a number and returns the
* `:abbreviated_month_names` - a function that receives a number (and also
the datetime if the function is arity/2) and returns the
abbreviated name of the corresponding month, if the option is not received it
defaults to a function that returns the abbreviated month names in English

* `:day_of_week_names` - a function that receives a number and returns the name of
* `:day_of_week_names` - a function that receives a number and (and also the
datetime if the function is arity/2) returns the name of
the corresponding day of week, if the option is not received it defaults to a
function that returns the day of week names in English

* `:abbreviated_day_of_week_names` - a function that receives a number and returns
the abbreviated name of the corresponding day of week, if the option is not received
it defaults to a function that returns the abbreviated day of week names in English
* `:abbreviated_day_of_week_names` - a function that receives a number (and also
the datetime if the function is arity/2) and returns the abbreviated name of
the corresponding day of week, if the option is not received it defaults to a
function that returns the abbreviated day of week names in English

## Formatting syntax

Expand Down Expand Up @@ -650,12 +655,12 @@ defmodule Calendar do
format_modifiers(rest, width, pad, datetime, format_options, acc)
end

defp am_pm(hour, format_options) when hour > 11 do
format_options.am_pm_names.(:pm)
defp am_pm(hour, format_options, datetime) when hour > 11 do
apply_format(:pm, format_options.am_pm_names, datetime)
end

defp am_pm(hour, format_options) when hour <= 11 do
format_options.am_pm_names.(:am)
defp am_pm(hour, format_options, datetime) when hour <= 11 do
apply_format(:am, format_options.am_pm_names, datetime)
end

defp default_pad(format) when format in ~c"aAbBpPZ", do: ?\s
Expand All @@ -676,7 +681,7 @@ defmodule Calendar do
result =
datetime
|> Date.day_of_week()
|> format_options.abbreviated_day_of_week_names.()
|> apply_format(format_options.abbreviated_day_of_week_names, datetime)
|> pad_leading(width, pad)

parse(rest, datetime, format_options, [result | acc])
Expand All @@ -687,7 +692,7 @@ defmodule Calendar do
result =
datetime
|> Date.day_of_week()
|> format_options.day_of_week_names.()
|> apply_format(format_options.day_of_week_names, datetime)
|> pad_leading(width, pad)

parse(rest, datetime, format_options, [result | acc])
Expand All @@ -697,15 +702,18 @@ defmodule Calendar do
defp format_modifiers("b" <> rest, width, pad, datetime, format_options, acc) do
result =
datetime.month
|> format_options.abbreviated_month_names.()
|> apply_format(format_options.abbreviated_month_names, datetime)
|> pad_leading(width, pad)

parse(rest, datetime, format_options, [result | acc])
end

# Full month name
defp format_modifiers("B" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.month |> format_options.month_names.() |> pad_leading(width, pad)
result =
datetime.month
|> apply_format(format_options.month_names, datetime)
|> pad_leading(width, pad)

parse(rest, datetime, format_options, [result | acc])
end
Expand Down Expand Up @@ -783,7 +791,11 @@ defmodule Calendar do

# "AM" or "PM" (noon is "PM", midnight as "AM")
defp format_modifiers("p" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.hour |> am_pm(format_options) |> String.upcase() |> pad_leading(width, pad)
result =
datetime.hour
|> am_pm(format_options, datetime)
|> String.upcase()
|> pad_leading(width, pad)

parse(rest, datetime, format_options, [result | acc])
end
Expand All @@ -792,7 +804,7 @@ defmodule Calendar do
defp format_modifiers("P" <> rest, width, pad, datetime, format_options, acc) do
result =
datetime.hour
|> am_pm(format_options)
|> am_pm(format_options, datetime)
|> String.downcase()
|> pad_leading(width, pad)

Expand Down Expand Up @@ -958,6 +970,19 @@ defmodule Calendar do
defp do_pad_leading(count, padding, acc),
do: do_pad_leading(count - 1, padding, [padding | acc])

defp apply_format(term, formatter, datetime) do
case Function.info(formatter, :arity) do
{:arity, 1} ->
formatter.(term)

{:arity, 2} ->
formatter.(term, datetime)

{:arity, n} ->
raise ArgumentError, "Formatter funtion must be of arity 1 or 2. Found #{inspect(n)}"
end
end

defp options(user_options) do
default_options = %{
preferred_date: "%Y-%m-%d",
Expand Down
33 changes: 33 additions & 0 deletions lib/elixir/test/elixir/calendar_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,39 @@ defmodule CalendarTest do
) == "четверг ЧТВ P Agosto Ago % 02019-08-15 07: 1757"
end

test "formats according to custom configs with 2-arity functions" do
assert Calendar.strftime(
~U[2019-08-15 17:07:57.001Z],
"%A %a %p %B %b %c %x %X",
am_pm_names: fn
:am, ~U[2019-08-15 17:07:57.001Z] -> "a"
:pm, ~U[2019-08-15 17:07:57.001Z] -> "p"
end,
month_names: fn month, ~U[2019-08-15 17:07:57.001Z] ->
{"Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto",
"Setembro", "Outubro", "Novembro", "Dezembro"}
|> elem(month - 1)
end,
day_of_week_names: fn day_of_week, ~U[2019-08-15 17:07:57.001Z] ->
{"понедельник", "вторник", "среда", "четверг", "пятница", "суббота",
"воскресенье"}
|> elem(day_of_week - 1)
end,
abbreviated_month_names: fn month, ~U[2019-08-15 17:07:57.001Z] ->
{"Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov",
"Dez"}
|> elem(month - 1)
end,
abbreviated_day_of_week_names: fn day_of_week, ~U[2019-08-15 17:07:57.001Z] ->
{"ПНД", "ВТР", "СРД", "ЧТВ", "ПТН", "СБТ", "ВСК"}
|> elem(day_of_week - 1)
end,
preferred_date: "%05Y-%m-%d",
preferred_time: "%M:%_3H%S",
preferred_datetime: "%%"
) == "четверг ЧТВ P Agosto Ago % 02019-08-15 07: 1757"
end

test "raises on unknown option according to custom configs" do
assert_raise ArgumentError, "unknown option :unknown given to Calendar.strftime/3", fn ->
Calendar.strftime(~D[2019-08-15], "%D", unknown: "option")
Expand Down