diff --git a/examples/datetime_human_tests.nbt b/examples/datetime_human_tests.nbt index 9857f400..92cefc8b 100644 --- a/examples/datetime_human_tests.nbt +++ b/examples/datetime_human_tests.nbt @@ -1,7 +1,13 @@ assert((0 second -> human) == "0 seconds") +assert((0.1 second -> human) == "0.100 seconds") +assert((0.01 second -> human) == "0.010 seconds") +assert((0.001 second -> human) == "0.001 seconds") + assert((1 second -> human) == "1 second") assert((5 second -> human) == "5 seconds") assert((1.5 second -> human) == "1.500 seconds") +assert((2.55 second -> human) == "2.550 seconds") +assert((5.99999 second -> human) == "5.999990 seconds") assert((60 seconds -> human) == "1 minute") assert((73 seconds -> human) == "1 minute + 13 seconds") @@ -22,7 +28,20 @@ assert((1.37 day -> human) == "1 day + 8 hours + 52 minutes + 48 seconds assert((1 week -> human) == "7 days") assert((1.5 weeks -> human) == "10 days + 12 hours") assert((2 weeks -> human) == "14 days") +assert((4.566 weeks -> human) == "31 days + 23 hours + 5 minutes + 16.800 seconds") + +assert((1 year -> human) == "365 days + 5 hours + 48 minutes + 45.051840004 seconds") +assert((3.45 years -> human) == "1260 days") +assert((1 month -> human) == "30 days + 10 hours + 29 minutes + 3.754320 seconds") +assert((3.78 months -> human) == "115 days + 1 hour + 13 minutes + 51.391329601 seconds") +assert((1 gregorian_year -> human) == "365 days + 5 hours + 49 minutes + 12 seconds") +assert((2.34 gregorian_year -> human) == "854 days + 16 hours + 1 minute + 7.679999992 seconds") +assert((1 julian_year -> human) == "365 days + 6 hours") +assert((1.06 julian_year -> human) == "387 days + 3 hours + 57 minutes + 36 seconds") +assert((1 decade -> human) == "3652 days") +assert((1 century -> human) == "36524 days") +assert((1 millennium -> human) == "365_242 days") assert((1 sidereal_day -> human) == "23 hours + 56 minutes + 4.090500 seconds") assert((10000 days -> human) == "10000 days") @@ -32,7 +51,16 @@ assert((1e12 days -> human) == "1_000_000_000_000 days") assert((1e15 days -> human) == "1.0e+15 days") assert((1 ms -> human) == "0.001 seconds") +assert((12 ms -> human) == "0.012 seconds") +assert((342 ms -> human) == "0.342 seconds") + assert((1 µs -> human) == "0.000001 seconds") +assert((1245 µs -> human) == "0.001245 seconds") +assert((1236876 µs -> human) == "1.236876 seconds") + assert((1 ns -> human) == "0.000000001 seconds") assert((1234 ns -> human) == "0.000001234 seconds") assert((1s + 1234 ns -> human) == "1.000001234 seconds") + +assert((-1 second -> human) == "1 second ago") +assert((-7.89 hour -> human) == "7 hours + 53 minutes + 24 seconds ago") \ No newline at end of file diff --git a/numbat/modules/datetime/human.nbt b/numbat/modules/datetime/human.nbt index 9a334b14..017e1977 100644 --- a/numbat/modules/datetime/human.nbt +++ b/numbat/modules/datetime/human.nbt @@ -3,7 +3,19 @@ use core::strings use units::si use datetime::functions -fn _human_num_days(time: Time) -> Scalar = floor(time / day) +fn _human_num_days(time: Time) -> Time = floor(time / day)*day + +fn _human_num_hours(time: Time) -> Time = floor(time / hours)*hour + +fn _human_num_minutes(time: Time) -> Time = floor(time / minutes)*minute + +fn _human_num_seconds(time: Time) -> Time = (time / seconds)second + +fn _seconds_on_stopwatch(time: Time) -> Time = _human_num_seconds(time)-_human_num_seconds(_human_num_minutes(time)) + +fn _minutes_on_clock(time: Time) -> Time = _human_num_minutes(time)-_human_num_minutes(_human_num_hours(time)) + +fn _hours_of_a_day(time: Time) -> Time = _human_num_hours(time)-_human_num_hours(_human_num_days(time)) fn _human_join(a: String, b: String) -> String = if str_slice(a, 0, 2) == "0 " then b else if str_slice(b, 0, 2) == "0 " then a else "{a} + {b}" @@ -11,26 +23,31 @@ fn _human_join(a: String, b: String) -> String = fn _remove_plural_suffix(str: String) -> String = if str_slice(str, 0, 2) == "1 " then str_slice(str, 0, str_length(str) - 1) else str -fn _human_seconds(dt: DateTime) -> String = - _remove_plural_suffix(format_datetime("%-S%.f seconds", dt)) +fn _human_formatted_seconds(time: Time) -> String = + _remove_plural_suffix(format_datetime("%-S%.f seconds", datetime("0001-01-01T00:00:00Z") + _seconds_on_stopwatch(time))) -fn _human_minutes(dt: DateTime) -> String = - _remove_plural_suffix(format_datetime("%-M minutes", dt)) +fn _human_formatted_minutes(time: Time) -> String = + _remove_plural_suffix("{_minutes_on_clock(time)/minute} minutes") -fn _human_hours(dt: DateTime) -> String = - _remove_plural_suffix(format_datetime("%-H hours", dt)) +fn _human_formatted_hours(time: Time) -> String = + _remove_plural_suffix("{_hours_of_a_day(time)/hour} hours") -fn _human_days(num_days: Scalar) -> String = - _remove_plural_suffix("{num_days} days") +fn _human_formatted_days(num_days: Time) -> String = + _remove_plural_suffix("{_human_num_days(num_days)/day} days") -fn _human_readable_duration(time: Time, dt: DateTime, num_days: Scalar) -> String = - _human_join(_human_join(_human_join(_human_days(_human_num_days(time)), _human_hours(dt)), _human_minutes(dt)), _human_seconds(dt)) +fn _human_readable_duration(time: Time) -> String = + _human_join(_human_join(_human_join(_human_formatted_days(time), _human_formatted_hours(time)), _human_formatted_minutes(time)), _human_formatted_seconds(time)) # Implementation details: # we skip hours/minutes/seconds for durations larger than 1000 days because: # (a) we run into floating point precision problems at the nanosecond level at this point # (b) for much larger numbers, we can't convert to DateTimes anymore +fn human_time_check_overflow(time: Time) = + if _human_num_days(time) > 1000 days + then _human_formatted_days(time) + else _human_readable_duration(time) + fn human(time: Time) = - if _human_num_days(time) > 1000 - then "{_human_num_days(time)} days" - else _human_readable_duration(time, datetime("0001-01-01T00:00:00Z") + time, _human_num_days(time)) + if _human_num_seconds(time) < 0 seconds + then "{human_time_check_overflow(time*-1)} ago" + else human_time_check_overflow(time)