Skip to content

Commit 511415c

Browse files
committed
Add a human readable representation of duration
1 parent 44107f1 commit 511415c

File tree

2 files changed

+146
-7
lines changed

2 files changed

+146
-7
lines changed

lib/elixir/lib/calendar/duration.ex

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,81 @@ defmodule Duration do
355355
end
356356
end
357357

358+
@doc """
359+
Converts the given `duration` to a human readable representation.
360+
361+
## Examples
362+
363+
iex> Duration.to_string(Duration.new!(year: 3))
364+
"3 years"
365+
iex> Duration.to_string(Duration.new!(day: 40, hour: 12, minute: 42, second: 12))
366+
"40 days, 12 hours, 42 minutes, 12 seconds"
367+
iex> Duration.to_string(Duration.new!(second: 30))
368+
"30 seconds"
369+
370+
iex> Duration.to_string(Duration.new!([]))
371+
"0 seconds"
372+
373+
iex> Duration.to_string(Duration.new!(second: 1, microsecond: {2_200, 3}))
374+
"1.002 seconds"
375+
iex> Duration.to_string(Duration.new!(second: 1, microsecond: {-1_200_000, 4}))
376+
"-0.2000 seconds"
377+
378+
"""
379+
@doc since: "1.18.0"
380+
def to_string(%Duration{} = duration) do
381+
case to_string_year(duration, []) do
382+
[] ->
383+
"0 seconds"
384+
385+
[part] ->
386+
IO.iodata_to_binary(part)
387+
388+
parts ->
389+
parts |> Enum.reduce(&[&1, ", " | &2]) |> IO.iodata_to_binary()
390+
end
391+
end
392+
393+
defp to_string_part(0, _singular, _plural, acc), do: acc
394+
defp to_string_part(1, singular, _plural, acc), do: [["1" | singular] | acc]
395+
defp to_string_part(x, _singular, plural, acc), do: [[Integer.to_string(x) | plural] | acc]
396+
397+
defp to_string_year(%{year: year} = duration, acc) do
398+
to_string_month(duration, to_string_part(year, " year", " years", acc))
399+
end
400+
401+
defp to_string_month(%{month: month} = duration, acc) do
402+
to_string_week(duration, to_string_part(month, " month", " months", acc))
403+
end
404+
405+
defp to_string_week(%{week: week} = duration, acc) do
406+
to_string_day(duration, to_string_part(week, " week", " weeks", acc))
407+
end
408+
409+
defp to_string_day(%{day: day} = duration, acc) do
410+
to_string_hour(duration, to_string_part(day, " day", " days", acc))
411+
end
412+
413+
defp to_string_hour(%{hour: hour} = duration, acc) do
414+
to_string_minute(duration, to_string_part(hour, " hour", " hours", acc))
415+
end
416+
417+
defp to_string_minute(%{minute: minute} = duration, acc) do
418+
to_string_second(duration, to_string_part(minute, " minute", " minutes", acc))
419+
end
420+
421+
defp to_string_second(%{second: 1, microsecond: {0, _}}, acc) do
422+
["1 second" | acc]
423+
end
424+
425+
defp to_string_second(%{second: 0, microsecond: {0, _}}, acc) do
426+
acc
427+
end
428+
429+
defp to_string_second(%{second: s, microsecond: {ms, p}}, acc) do
430+
[[second_component(s, ms, p) | " seconds"] | acc]
431+
end
432+
358433
@doc """
359434
Converts the given `duration` to an [ISO 8601-2:2019](https://en.wikipedia.org/wiki/ISO_8601) formatted string.
360435
@@ -407,15 +482,15 @@ defmodule Duration do
407482
[]
408483
end
409484

410-
defp second_component(%{second: 0, microsecond: {_, 0}}) do
411-
~c"0S"
485+
defp second_component(%{second: second, microsecond: {ms, p}}) do
486+
[second_component(second, ms, p), ?S]
412487
end
413488

414-
defp second_component(%{second: second, microsecond: {_, 0}}) do
415-
[Integer.to_string(second), ?S]
489+
defp second_component(second, _ms, 0) do
490+
Integer.to_string(second)
416491
end
417492

418-
defp second_component(%{second: second, microsecond: {ms, p}}) do
493+
defp second_component(second, ms, p) do
419494
total_ms = second * @microseconds_per_second + ms
420495
second = total_ms |> div(@microseconds_per_second) |> abs()
421496
ms = total_ms |> rem(@microseconds_per_second) |> abs()
@@ -425,8 +500,7 @@ defmodule Duration do
425500
sign,
426501
Integer.to_string(second),
427502
?.,
428-
ms |> Integer.to_string() |> String.pad_leading(6, "0") |> binary_part(0, p),
429-
?S
503+
ms |> Integer.to_string() |> String.pad_leading(6, "0") |> binary_part(0, p)
430504
]
431505
end
432506

lib/elixir/test/elixir/calendar/duration_test.exs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,4 +358,69 @@ defmodule DurationTest do
358358
assert %Duration{microsecond: {-800_000, 0}} |> Duration.to_iso8601() == "PT0S"
359359
assert %Duration{microsecond: {-1_200_000, 2}} |> Duration.to_iso8601() == "PT-1.20S"
360360
end
361+
362+
test "to_string/1" do
363+
assert Duration.to_string(%Duration{year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6}) ==
364+
"1 year, 2 months, 3 days, 4 hours, 5 minutes, 6 seconds"
365+
366+
assert Duration.to_string(%Duration{week: 3, hour: 5, minute: 3}) ==
367+
"3 weeks, 5 hours, 3 minutes"
368+
369+
assert Duration.to_string(%Duration{hour: 5, minute: 3}) ==
370+
"5 hours, 3 minutes"
371+
372+
assert Duration.to_string(%Duration{year: 1, month: 2, day: 3}) ==
373+
"1 year, 2 months, 3 days"
374+
375+
assert Duration.to_string(%Duration{hour: 4, minute: 5, second: 6}) ==
376+
"4 hours, 5 minutes, 6 seconds"
377+
378+
assert Duration.to_string(%Duration{year: 1, month: 2}) ==
379+
"1 year, 2 months"
380+
381+
assert Duration.to_string(%Duration{day: 3}) ==
382+
"3 days"
383+
384+
assert Duration.to_string(%Duration{hour: 4, minute: 5}) ==
385+
"4 hours, 5 minutes"
386+
387+
assert Duration.to_string(%Duration{second: 6}) ==
388+
"6 seconds"
389+
390+
assert Duration.to_string(%Duration{second: 1, microsecond: {600_000, 1}}) ==
391+
"1.6 seconds"
392+
393+
assert Duration.to_string(%Duration{second: -1, microsecond: {-600_000, 1}}) ==
394+
"-1.6 seconds"
395+
396+
assert Duration.to_string(%Duration{second: -1, microsecond: {-234_567, 6}}) ==
397+
"-1.234567 seconds"
398+
399+
assert Duration.to_string(%Duration{second: 1, microsecond: {123_456, 6}}) ==
400+
"1.123456 seconds"
401+
402+
assert Duration.to_string(%Duration{year: 3, week: 4, day: -3, second: -6}) ==
403+
"3 years, 4 weeks, -3 days, -6 seconds"
404+
405+
assert Duration.to_string(%Duration{second: -4, microsecond: {-230_000, 2}}) ==
406+
"-4.23 seconds"
407+
408+
assert Duration.to_string(%Duration{second: -4, microsecond: {230_000, 2}}) ==
409+
"-3.77 seconds"
410+
411+
assert Duration.to_string(%Duration{second: 2, microsecond: {-1_200_000, 4}}) ==
412+
"0.8000 seconds"
413+
414+
assert Duration.to_string(%Duration{second: 1, microsecond: {-1_200_000, 3}}) ==
415+
"-0.200 seconds"
416+
417+
assert Duration.to_string(%Duration{microsecond: {-800_000, 2}}) ==
418+
"-0.80 seconds"
419+
420+
assert Duration.to_string(%Duration{microsecond: {-800_000, 0}}) ==
421+
"0 seconds"
422+
423+
assert Duration.to_string(%Duration{microsecond: {-1_200_000, 2}}) ==
424+
"-1.20 seconds"
425+
end
361426
end

0 commit comments

Comments
 (0)