@@ -355,6 +355,114 @@ defmodule Duration do
355
355
end
356
356
end
357
357
358
+ @ doc """
359
+ Converts the given `duration` to a human readable representation.
360
+
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` - a string used to separate the distinct components. Defaults to `" "`.
377
+
378
+ ## Examples
379
+
380
+ iex> Duration.to_string(Duration.new!(second: 30))
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":
399
+
400
+ iex> Duration.to_string(Duration.new!([]))
401
+ "0s"
402
+
403
+ Microseconds are rendered as part of seconds with the appropriate precision:
404
+
405
+ iex> Duration.to_string(Duration.new!(second: 1, microsecond: {2_200, 3}))
406
+ "1.002s"
407
+ iex> Duration.to_string(Duration.new!(second: 1, microsecond: {-1_200_000, 4}))
408
+ "-0.2000s"
409
+
410
+ """
411
+ @ doc since: "1.18.0"
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
417
+ [ ] ->
418
+ "0" <> Keyword . get ( units , :second , "s" )
419
+
420
+ [ part ] ->
421
+ IO . iodata_to_binary ( part )
422
+
423
+ parts ->
424
+ parts |> Enum . reduce ( & [ & 1 , separator | & 2 ] ) |> IO . iodata_to_binary ( )
425
+ end
426
+ end
427
+
428
+ defp to_string_part ( 0 , _units , _key , _default , acc ) ,
429
+ do: acc
430
+
431
+ defp to_string_part ( x , units , key , default , acc ) ,
432
+ do: [ [ Integer . to_string ( x ) | Keyword . get ( units , key , default ) ] | acc ]
433
+
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 )
436
+ end
437
+
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 )
440
+ end
441
+
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 )
444
+ end
445
+
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 )
448
+ end
449
+
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 )
452
+ end
453
+
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 )
456
+ end
457
+
458
+ defp to_string_second ( % { second: 0 , microsecond: { 0 , _ } } , acc , _units ) do
459
+ acc
460
+ end
461
+
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 ]
464
+ end
465
+
358
466
@ doc """
359
467
Converts the given `duration` to an [ISO 8601-2:2019](https://en.wikipedia.org/wiki/ISO_8601) formatted string.
360
468
@@ -406,15 +514,15 @@ defmodule Duration do
406
514
[ ]
407
515
end
408
516
409
- defp second_component ( % { second: 0 , microsecond: { _ , 0 } } ) do
410
- ~c " 0S "
517
+ defp second_component ( % { second: second , microsecond: { ms , p } } ) do
518
+ [ second_component ( second , ms , p ) , ?S ]
411
519
end
412
520
413
- defp second_component ( % { second: second , microsecond: { _ , 0 } } ) do
414
- [ Integer . to_string ( second ) , ?S ]
521
+ defp second_component ( second , _ms , 0 ) do
522
+ Integer . to_string ( second )
415
523
end
416
524
417
- defp second_component ( % { second: second , microsecond: { ms , p } } ) do
525
+ defp second_component ( second , ms , p ) do
418
526
total_ms = second * @ microseconds_per_second + ms
419
527
second = total_ms |> div ( @ microseconds_per_second ) |> abs ( )
420
528
ms = total_ms |> rem ( @ microseconds_per_second ) |> abs ( )
@@ -424,8 +532,7 @@ defmodule Duration do
424
532
sign ,
425
533
Integer . to_string ( second ) ,
426
534
?. ,
427
- ms |> Integer . to_string ( ) |> String . pad_leading ( 6 , "0" ) |> binary_part ( 0 , p ) ,
428
- ?S
535
+ ms |> Integer . to_string ( ) |> String . pad_leading ( 6 , "0" ) |> binary_part ( 0 , p )
429
536
]
430
537
end
431
538
0 commit comments