diff --git a/crates/nu-std/std/dt.nu b/crates/nu-std/std/dt.nu index 4ee76c0cb..9a81f55d3 100644 --- a/crates/nu-std/std/dt.nu +++ b/crates/nu-std/std/dt.nu @@ -79,7 +79,7 @@ def borrow-minute [from: record, current: record] { def borrow-second [from: record, current: record] { mut current = $current - $current.millisecond = $current.millisecond + 1_000 + $current.nanosecond = $current.nanosecond + 1_000_000_000 $current.second = $current.second - 1 if $current.second < 0 { $current = (borrow-minute $from $current) @@ -88,37 +88,33 @@ def borrow-second [from: record, current: record] { $current } -def borrow-millisecond [from: record, current: record] { - mut current = $current - $current.microsecond = $current.microsecond + 1_000_000 - $current.millisecond = $current.millisecond - 1 - if $current.millisecond < 0 { - $current = (borrow-second $from $current) +# Subtract later from earlier datetime and return the unit differences as a record +# Example: +# > dt datetime-diff 2023-05-07T04:08:45+12:00 2019-05-10T09:59:12-07:00 +# ╭─────────────┬────╮ +# │ year │ 3 │ +# │ month │ 11 │ +# │ day │ 26 │ +# │ hour │ 23 │ +# │ minute │ 9 │ +# │ second │ 33 │ +# │ millisecond │ 0 │ +# │ microsecond │ 0 │ +# │ nanosecond │ 0 │ +# ╰─────────────┴────╯ +export def datetime-diff [ + later: datetime, # a later datetime + earlier: datetime # earlier (starting) datetime + ] { + if $earlier > $later { + let start = (metadata $later).span.start + let end = (metadata $earlier).span.end + error make {msg: "Incompatible arguments", label: {start:$start, end:$end, text:$"First datetime must be >= second, but was actually ($later - $earlier) less than it."}} } + let from_expanded = ($later | date to-timezone utc | date to-record) + let to_expanded = ($earlier | date to-timezone utc | date to-record) - $current -} - -def borrow-microsecond [from: record, current: record] { - mut current = $current - $current.nanosecond = $current.nanosecond + 1_000_000_000 - $current.microsecond = $current.microsecond - 1 - if $current.microsecond < 0 { - $current = (borrow-millisecond $from $current) - } - - $current -} - -# Subtract two datetimes and return a record with the difference -# Examples -# print (datetime-diff 2023-05-07T04:08:45+12:00 2019-05-10T09:59:12+12:00) -# print (datetime-diff (date now) 2019-05-10T09:59:12-07:00) -export def datetime-diff [from: datetime, to: datetime] { - let from_expanded = ($from | date to-timezone utc | date to-record | merge { millisecond: 0, microsecond: 0}) - let to_expanded = ($to | date to-timezone utc | date to-record | merge { millisecond: 0, microsecond: 0}) - - mut result = { year: ($from_expanded.year - $to_expanded.year), month: ($from_expanded.month - $to_expanded.month)} + mut result = { year: ($from_expanded.year - $to_expanded.year), month: ($from_expanded.month - $to_expanded.month), day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0, nanosecond:0} if $result.month < 0 { $result = (borrow-year $from_expanded $result) @@ -156,65 +152,69 @@ export def datetime-diff [from: datetime, to: datetime] { $result } -export def pretty-print-duration [dur: duration] { +# Convert record from datetime-diff into humanized string +# Example: +# > dt pretty-print-duration (dt datetime-diff 2023-05-07T04:08:45+12:00 2019-05-10T09:59:12+12:00) +# 3yrs 11months 27days 18hrs 9mins 33secs +export def pretty-print-duration [dur: record] { mut result = "" - if $dur.year > 0 { + if $dur.year != 0 { if $dur.year > 1 { $result = $"($dur.year)yrs " } else { $result = $"($dur.year)yr " } } - if $dur.month > 0 { + if $dur.month != 0 { if $dur.month > 1 { $result = $"($result)($dur.month)months " } else { $result = $"($result)($dur.month)month " } } - if $dur.day > 0 { + if $dur.day != 0 { if $dur.day > 1 { $result = $"($result)($dur.day)days " } else { $result = $"($result)($dur.day)day " } } - if $dur.hour > 0 { + if $dur.hour != 0 { if $dur.hour > 1 { $result = $"($result)($dur.hour)hrs " } else { $result = $"($result)($dur.hour)hr " } } - if $dur.minute > 0 { + if $dur.minute != 0 { if $dur.minute > 1 { $result = $"($result)($dur.minute)mins " } else { $result = $"($result)($dur.minute)min " } } - if $dur.second > 0 { + if $dur.second != 0 { if $dur.second > 1 { $result = $"($result)($dur.second)secs " } else { $result = $"($result)($dur.second)sec " } } - if $dur.millisecond > 0 { + if $dur.millisecond != 0 { if $dur.millisecond > 1 { $result = $"($result)($dur.millisecond)ms " } else { $result = $"($result)($dur.millisecond)ms " } } - if $dur.microsecond > 0 { + if $dur.microsecond != 0 { if $dur.microsecond > 1 { $result = $"($result)($dur.microsecond)µs " } else { $result = $"($result)($dur.microsecond)µs " } } - if $dur.nanosecond > 0 { + if $dur.nanosecond != 0 { if $dur.nanosecond > 1 { $result = $"($result)($dur.nanosecond)ns " } else { diff --git a/crates/nu-std/tests/test_dt.nu b/crates/nu-std/tests/test_dt.nu new file mode 100644 index 000000000..72f608248 --- /dev/null +++ b/crates/nu-std/tests/test_dt.nu @@ -0,0 +1,44 @@ +use std assert +use std dt * + +#[test] +def equal_times [] { + let t1 = (date now) + assert equal (datetime-diff $t1 $t1) ({year:0, month:0, day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0 nanosecond:0}) +} + +#[test] +def one_ns_later [] { + let t1 = (date now) + assert equal (datetime-diff ($t1 + 1ns) $t1) ({year:0, month:0, day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0 nanosecond:1}) +} + +#[test] +def one_yr_later [] { + let t1 = ('2022-10-1T0:1:2z' | into datetime) # a date for which one year later is 365 days, since duration doesn't support year or month + assert equal (datetime-diff ($t1 + 365day) $t1) ({year:1, month:0, day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0 nanosecond:0}) +} + +#[test] +def carry_ripples [] { + let t1 = ('2023-10-9T0:0:0z' | into datetime) + let t2 = ('2022-10-9T0:0:0.000000001z' | into datetime) + assert equal (datetime-diff $t1 $t2) ({year:0, month:11, day:30, hour:23, minute:59, second:59, millisecond:999, microsecond:999 nanosecond:999}) +} + +#[test] +def earlier_arg_must_be_less_or_equal_later [] { + let t1 = ('2022-10-9T0:0:0.000000001z' | into datetime) + let t2 = ('2023-10-9T0:0:0z' | into datetime) + assert error {|| (datetime-diff $t1 $t2)} +} + +#[test] +def pp_skips_zeros [] { + assert equal (pretty-print-duration {year:1, month:0, day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0 nanosecond:0}) "1yr " +} + +#[test] +def pp_doesnt_skip_neg [] { # datetime-diff can't return negative units, but prettyprint shouldn't skip them (if passed handcrafted record) + assert equal (pretty-print-duration {year:-1, month:0, day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0 nanosecond:0}) "-1yr " +} \ No newline at end of file