diff --git a/src/pkg.generated.mbti b/src/pkg.generated.mbti index 530726e..dc82a71 100644 --- a/src/pkg.generated.mbti +++ b/src/pkg.generated.mbti @@ -154,6 +154,7 @@ pub fn Duration::days(Int64) -> Self pub fn Duration::divide(Self, Int64) -> Self raise TempoError pub fn Duration::format_iso(Self) -> String pub fn Duration::hours(Int64) -> Self +pub fn Duration::humanize(Self) -> String pub fn Duration::is_negative(Self) -> Bool pub fn Duration::is_positive(Self) -> Bool pub fn Duration::is_zero(Self) -> Bool diff --git a/src/tempo.mbt b/src/tempo.mbt index 602a130..6e84cda 100644 --- a/src/tempo.mbt +++ b/src/tempo.mbt @@ -2492,6 +2492,56 @@ pub fn Duration::format_iso(self : Duration) -> String { } } +///| +/// Format this duration as English elapsed time using day/hour/minute/second +/// units. Sub-second remainder is dropped. +pub fn Duration::humanize(self : Duration) -> String { + let total_seconds = self.abs().as_seconds() + if total_seconds == 0L { + "0 seconds" + } else { + let days = total_seconds / 86_400L + let rem = total_seconds - days * 86_400L + let hours = rem / 3_600L + let rem = rem - hours * 3_600L + let minutes = rem / 60L + let seconds = rem - minutes * 60L + let buf = StringBuilder() + if self.is_negative() { + buf.write_string("-") + } + let wrote = write_humanized_duration_component(buf, false, days, "day") + let wrote = write_humanized_duration_component(buf, wrote, hours, "hour") + let wrote = write_humanized_duration_component( + buf, wrote, minutes, "minute", + ) + let _ = write_humanized_duration_component(buf, wrote, seconds, "second") + buf.to_string() + } +} + +///| +fn write_humanized_duration_component( + buf : StringBuilder, + wrote : Bool, + value : Int64, + unit : String, +) -> Bool { + if value == 0L { + wrote + } else { + if wrote { + buf.write_string(" ") + } + buf.write_string("\{value} ") + buf.write_string(unit) + if value != 1L { + buf.write_string("s") + } + true + } +} + ///| fn[T] json_decode_error( path : @json.JsonPath, diff --git a/src/tempo_test.mbt b/src/tempo_test.mbt index c7bf5bb..b409d10 100644 --- a/src/tempo_test.mbt +++ b/src/tempo_test.mbt @@ -1895,6 +1895,30 @@ test "Duration::format_iso canonical components" { assert_eq(@tempo.Duration::hours(-1L).format_iso(), "-PT1H") } +///| +test "Duration::humanize formats elapsed components" { + assert_eq( + (@tempo.Duration::hours(2L) + @tempo.Duration::minutes(30L)).humanize(), + "2 hours 30 minutes", + ) + assert_eq(@tempo.Duration::seconds(1L).humanize(), "1 second") + assert_eq( + (@tempo.Duration::days(1L) + + @tempo.Duration::hours(1L) + + @tempo.Duration::minutes(1L) + + @tempo.Duration::seconds(1L)).humanize(), + "1 day 1 hour 1 minute 1 second", + ) + assert_eq(@tempo.Duration::seconds(0L).humanize(), "0 seconds") + assert_eq(@tempo.Duration::milliseconds(500L).humanize(), "0 seconds") + assert_eq(@tempo.Duration::seconds(-5L).humanize(), "-5 seconds") + assert_eq(@tempo.Duration::minutes(-90L).humanize(), "-1 hour 30 minutes") + assert_eq( + @tempo.Duration::nanoseconds(-9_223_372_036_854_775_808L).humanize(), + "-106751 days 23 hours 47 minutes 16 seconds", + ) +} + ///| test "Duration::parse_iso fixed-unit components" { assert_eq(