Skip to content

Commit b118698

Browse files
authored
Support 2-arity options for Calendar.strftime/3 (#14247)
1 parent 80aad35 commit b118698

File tree

2 files changed

+74
-17
lines changed

2 files changed

+74
-17
lines changed

lib/elixir/lib/calendar.ex

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -461,25 +461,30 @@ defmodule Calendar do
461461
it can't contain the `%X` format and defaults to `"%H:%M:%S"`
462462
if the option is not received
463463
464-
* `:am_pm_names` - a function that receives either `:am` or `:pm` and returns
464+
* `:am_pm_names` - a function that receives either `:am` or `:pm`
465+
(and also the datetime if the function is arity/2) and returns
465466
the name of the period of the day, if the option is not received it defaults
466467
to a function that returns `"am"` and `"pm"`, respectively
467468
468-
* `:month_names` - a function that receives a number and returns the name of
469+
* `:month_names` - a function that receives a number (and also the
470+
datetime if the function is arity/2) and returns the name of
469471
the corresponding month, if the option is not received it defaults to a
470472
function that returns the month names in English
471473
472-
* `:abbreviated_month_names` - a function that receives a number and returns the
474+
* `:abbreviated_month_names` - a function that receives a number (and also
475+
the datetime if the function is arity/2) and returns the
473476
abbreviated name of the corresponding month, if the option is not received it
474477
defaults to a function that returns the abbreviated month names in English
475478
476-
* `:day_of_week_names` - a function that receives a number and returns the name of
479+
* `:day_of_week_names` - a function that receives a number and (and also the
480+
datetime if the function is arity/2) returns the name of
477481
the corresponding day of week, if the option is not received it defaults to a
478482
function that returns the day of week names in English
479483
480-
* `:abbreviated_day_of_week_names` - a function that receives a number and returns
481-
the abbreviated name of the corresponding day of week, if the option is not received
482-
it defaults to a function that returns the abbreviated day of week names in English
484+
* `:abbreviated_day_of_week_names` - a function that receives a number (and also
485+
the datetime if the function is arity/2) and returns the abbreviated name of
486+
the corresponding day of week, if the option is not received it defaults to a
487+
function that returns the abbreviated day of week names in English
483488
484489
## Formatting syntax
485490
@@ -650,12 +655,12 @@ defmodule Calendar do
650655
format_modifiers(rest, width, pad, datetime, format_options, acc)
651656
end
652657

653-
defp am_pm(hour, format_options) when hour > 11 do
654-
format_options.am_pm_names.(:pm)
658+
defp am_pm(hour, format_options, datetime) when hour > 11 do
659+
apply_format(:pm, format_options.am_pm_names, datetime)
655660
end
656661

657-
defp am_pm(hour, format_options) when hour <= 11 do
658-
format_options.am_pm_names.(:am)
662+
defp am_pm(hour, format_options, datetime) when hour <= 11 do
663+
apply_format(:am, format_options.am_pm_names, datetime)
659664
end
660665

661666
defp default_pad(format) when format in ~c"aAbBpPZ", do: ?\s
@@ -676,7 +681,7 @@ defmodule Calendar do
676681
result =
677682
datetime
678683
|> Date.day_of_week()
679-
|> format_options.abbreviated_day_of_week_names.()
684+
|> apply_format(format_options.abbreviated_day_of_week_names, datetime)
680685
|> pad_leading(width, pad)
681686

682687
parse(rest, datetime, format_options, [result | acc])
@@ -687,7 +692,7 @@ defmodule Calendar do
687692
result =
688693
datetime
689694
|> Date.day_of_week()
690-
|> format_options.day_of_week_names.()
695+
|> apply_format(format_options.day_of_week_names, datetime)
691696
|> pad_leading(width, pad)
692697

693698
parse(rest, datetime, format_options, [result | acc])
@@ -697,15 +702,18 @@ defmodule Calendar do
697702
defp format_modifiers("b" <> rest, width, pad, datetime, format_options, acc) do
698703
result =
699704
datetime.month
700-
|> format_options.abbreviated_month_names.()
705+
|> apply_format(format_options.abbreviated_month_names, datetime)
701706
|> pad_leading(width, pad)
702707

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

706711
# Full month name
707712
defp format_modifiers("B" <> rest, width, pad, datetime, format_options, acc) do
708-
result = datetime.month |> format_options.month_names.() |> pad_leading(width, pad)
713+
result =
714+
datetime.month
715+
|> apply_format(format_options.month_names, datetime)
716+
|> pad_leading(width, pad)
709717

710718
parse(rest, datetime, format_options, [result | acc])
711719
end
@@ -783,7 +791,11 @@ defmodule Calendar do
783791

784792
# "AM" or "PM" (noon is "PM", midnight as "AM")
785793
defp format_modifiers("p" <> rest, width, pad, datetime, format_options, acc) do
786-
result = datetime.hour |> am_pm(format_options) |> String.upcase() |> pad_leading(width, pad)
794+
result =
795+
datetime.hour
796+
|> am_pm(format_options, datetime)
797+
|> String.upcase()
798+
|> pad_leading(width, pad)
787799

788800
parse(rest, datetime, format_options, [result | acc])
789801
end
@@ -792,7 +804,7 @@ defmodule Calendar do
792804
defp format_modifiers("P" <> rest, width, pad, datetime, format_options, acc) do
793805
result =
794806
datetime.hour
795-
|> am_pm(format_options)
807+
|> am_pm(format_options, datetime)
796808
|> String.downcase()
797809
|> pad_leading(width, pad)
798810

@@ -958,6 +970,18 @@ defmodule Calendar do
958970
defp do_pad_leading(count, padding, acc),
959971
do: do_pad_leading(count - 1, padding, [padding | acc])
960972

973+
defp apply_format(term, formatter, _datetime) when is_function(formatter, 1) do
974+
formatter.(term)
975+
end
976+
977+
defp apply_format(term, formatter, datetime) when is_function(formatter, 2) do
978+
formatter.(term, datetime)
979+
end
980+
981+
defp apply_format(_term, formatter, _datetime) do
982+
raise ArgumentError, "formatter functions must be of arity 1 or 2, got: #{inspect(formatter)}"
983+
end
984+
961985
defp options(user_options) do
962986
default_options = %{
963987
preferred_date: "%Y-%m-%d",

lib/elixir/test/elixir/calendar_test.exs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,39 @@ defmodule CalendarTest do
397397
) == "четверг ЧТВ P Agosto Ago % 02019-08-15 07: 1757"
398398
end
399399

400+
test "formats according to custom configs with 2-arity functions" do
401+
assert Calendar.strftime(
402+
~U[2019-08-15 17:07:57.001Z],
403+
"%A %a %p %B %b %c %x %X",
404+
am_pm_names: fn
405+
:am, ~U[2019-08-15 17:07:57.001Z] -> "a"
406+
:pm, ~U[2019-08-15 17:07:57.001Z] -> "p"
407+
end,
408+
month_names: fn month, ~U[2019-08-15 17:07:57.001Z] ->
409+
{"Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto",
410+
"Setembro", "Outubro", "Novembro", "Dezembro"}
411+
|> elem(month - 1)
412+
end,
413+
day_of_week_names: fn day_of_week, ~U[2019-08-15 17:07:57.001Z] ->
414+
{"понедельник", "вторник", "среда", "четверг", "пятница", "суббота",
415+
"воскресенье"}
416+
|> elem(day_of_week - 1)
417+
end,
418+
abbreviated_month_names: fn month, ~U[2019-08-15 17:07:57.001Z] ->
419+
{"Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov",
420+
"Dez"}
421+
|> elem(month - 1)
422+
end,
423+
abbreviated_day_of_week_names: fn day_of_week, ~U[2019-08-15 17:07:57.001Z] ->
424+
{"ПНД", "ВТР", "СРД", "ЧТВ", "ПТН", "СБТ", "ВСК"}
425+
|> elem(day_of_week - 1)
426+
end,
427+
preferred_date: "%05Y-%m-%d",
428+
preferred_time: "%M:%_3H%S",
429+
preferred_datetime: "%%"
430+
) == "четверг ЧТВ P Agosto Ago % 02019-08-15 07: 1757"
431+
end
432+
400433
test "raises on unknown option according to custom configs" do
401434
assert_raise ArgumentError, "unknown option :unknown given to Calendar.strftime/3", fn ->
402435
Calendar.strftime(~D[2019-08-15], "%D", unknown: "option")

0 commit comments

Comments
 (0)