diff --git a/lib/elixir/lib/calendar.ex b/lib/elixir/lib/calendar.ex index fe630a07af8..6e79e3c18dd 100644 --- a/lib/elixir/lib/calendar.ex +++ b/lib/elixir/lib/calendar.ex @@ -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 @@ -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 @@ -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]) @@ -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]) @@ -697,7 +702,7 @@ 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]) @@ -705,7 +710,10 @@ defmodule Calendar do # 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 @@ -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 @@ -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) @@ -958,6 +970,18 @@ 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) when is_function(formatter, 1) do + formatter.(term) + end + + defp apply_format(term, formatter, datetime) when is_function(formatter, 2) do + formatter.(term, datetime) + end + + defp apply_format(_term, formatter, _datetime) do + raise ArgumentError, "formatter functions must be of arity 1 or 2, got: #{inspect(formatter)}" + end + defp options(user_options) do default_options = %{ preferred_date: "%Y-%m-%d", diff --git a/lib/elixir/test/elixir/calendar_test.exs b/lib/elixir/test/elixir/calendar_test.exs index 2d427214f0f..183d74dcdc5 100644 --- a/lib/elixir/test/elixir/calendar_test.exs +++ b/lib/elixir/test/elixir/calendar_test.exs @@ -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")