@@ -358,76 +358,109 @@ defmodule Duration do
358358 @ doc """
359359 Converts the given `duration` to a human readable representation.
360360
361+ ## Options
362+
363+ * `:units` - the units to be used alongside each duration component.
364+ The default units follow the ISO 80000-3 standard:
365+
366+ [
367+ year: "a",
368+ month: "mo",
369+ week: "wk",
370+ day: "d",
371+ hour: "h",
372+ minute: "min",
373+ second: "s"
374+ ]
375+
376+ * `:separator` - how to separate the distinct components
377+
361378 ## Examples
362379
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"
367380 iex> Duration.to_string(Duration.new!(second: 30))
368- "30 seconds"
381+ "30s"
382+ iex> Duration.to_string(Duration.new!(day: 40, hour: 12, minute: 42, second: 12))
383+ "40d 12h 42min 12s"
384+
385+ By default, this function uses ISO 80000-3 units, which uses "a" for years.
386+ But you can customize all units via the units option:
387+
388+ iex> Duration.to_string(Duration.new!(year: 3))
389+ "3a"
390+ iex> Duration.to_string(Duration.new!(year: 3), units: [year: "y"])
391+ "3y"
392+
393+ You may also choose the separator:
394+
395+ iex> Duration.to_string(Duration.new!(day: 40, hour: 12, minute: 42, second: 12), separator: ", ")
396+ "40d, 12h, 42min, 12s"
397+
398+ A duration without components is rendered as "0s":
369399
370400 iex> Duration.to_string(Duration.new!([]))
371- "0 seconds"
401+ "0s"
402+
403+ Microseconds are rendered as part of seconds with the appropriate precision:
372404
373405 iex> Duration.to_string(Duration.new!(second: 1, microsecond: {2_200, 3}))
374- "1.002 seconds "
406+ "1.002s "
375407 iex> Duration.to_string(Duration.new!(second: 1, microsecond: {-1_200_000, 4}))
376- "-0.2000 seconds "
408+ "-0.2000s "
377409
378410 """
379411 @ doc since: "1.18.0"
380- def to_string ( % Duration { } = duration ) do
381- case to_string_year ( duration , [ ] ) do
412+ def to_string ( % Duration { } = duration , opts \\ [ ] ) do
413+ units = Keyword . get ( opts , :units , [ ] )
414+ separator = Keyword . get ( opts , :separator , " " )
415+
416+ case to_string_year ( duration , [ ] , units ) do
382417 [ ] ->
383- "0 seconds"
418+ "0" <> Keyword . get ( units , :second , "s" )
384419
385420 [ part ] ->
386421 IO . iodata_to_binary ( part )
387422
388423 parts ->
389- parts |> Enum . reduce ( & [ & 1 , ", " | & 2 ] ) |> IO . iodata_to_binary ( )
424+ parts |> Enum . reduce ( & [ & 1 , separator | & 2 ] ) |> IO . iodata_to_binary ( )
390425 end
391426 end
392427
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 ]
428+ defp to_string_part ( 0 , _units , _key , _default , acc ) ,
429+ do: acc
396430
397- defp to_string_year ( % { year: year } = duration , acc ) do
398- to_string_month ( duration , to_string_part ( year , " year" , " years" , acc ) )
399- end
431+ defp to_string_part ( x , units , key , default , acc ) ,
432+ do: [ [ Integer . to_string ( x ) | Keyword . get ( units , key , default ) ] | acc ]
400433
401- defp to_string_month ( % { month: month } = duration , acc ) do
402- to_string_week ( duration , to_string_part ( month , " month" , " months " , acc ) )
434+ defp to_string_year ( % { year: year } = duration , acc , units ) do
435+ to_string_month ( duration , to_string_part ( year , units , :year , "a " , acc ) , units )
403436 end
404437
405- defp to_string_week ( % { week: week } = duration , acc ) do
406- to_string_day ( duration , to_string_part ( week , " week" , " weeks " , acc ) )
438+ defp to_string_month ( % { month: month } = duration , acc , units ) do
439+ to_string_week ( duration , to_string_part ( month , units , :month , "mo " , acc ) , units )
407440 end
408441
409- defp to_string_day ( % { day: day } = duration , acc ) do
410- to_string_hour ( duration , to_string_part ( day , " day" , " days " , acc ) )
442+ defp to_string_week ( % { week: week } = duration , acc , units ) do
443+ to_string_day ( duration , to_string_part ( week , units , :week , "wk " , acc ) , units )
411444 end
412445
413- defp to_string_hour ( % { hour: hour } = duration , acc ) do
414- to_string_minute ( duration , to_string_part ( hour , " hour" , " hours " , acc ) )
446+ defp to_string_day ( % { day: day } = duration , acc , units ) do
447+ to_string_hour ( duration , to_string_part ( day , units , :day , "d " , acc ) , units )
415448 end
416449
417- defp to_string_minute ( % { minute: minute } = duration , acc ) do
418- to_string_second ( duration , to_string_part ( minute , " minute" , " minutes " , acc ) )
450+ defp to_string_hour ( % { hour: hour } = duration , acc , units ) do
451+ to_string_minute ( duration , to_string_part ( hour , units , :hour , "h " , acc ) , units )
419452 end
420453
421- defp to_string_second ( % { second: 1 , microsecond: { 0 , _ } } , acc ) do
422- [ "1 second" | acc ]
454+ defp to_string_minute ( % { minute: minute } = duration , acc , units ) do
455+ to_string_second ( duration , to_string_part ( minute , units , :minute , "min" , acc ) , units )
423456 end
424457
425- defp to_string_second ( % { second: 0 , microsecond: { 0 , _ } } , acc ) do
458+ defp to_string_second ( % { second: 0 , microsecond: { 0 , _ } } , acc , _units ) do
426459 acc
427460 end
428461
429- defp to_string_second ( % { second: s , microsecond: { ms , p } } , acc ) do
430- [ [ second_component ( s , ms , p ) | " seconds" ] | acc ]
462+ defp to_string_second ( % { second: s , microsecond: { ms , p } } , acc , units ) do
463+ [ [ second_component ( s , ms , p ) | Keyword . get ( units , :second , "s" ) ] | acc ]
431464 end
432465
433466 @ doc """
0 commit comments