diff --git a/NEWS.md b/NEWS.md index d70465ef1..c30508a04 100644 --- a/NEWS.md +++ b/NEWS.md @@ -246,6 +246,9 @@ Changes to the Mercury standard library - func `det_date_from_string/1` (replacement: `det_date_time_from_string/1`) - func `date_to_string/1` (replacement: `date_time_to_string/1`) +* `date_time/0` and `duration/0` values now only allow for a single positive + leap second for any given minute. + * The following functions and predicates have been added: - pred `init_date_time/8` @@ -1424,6 +1427,11 @@ Changes to the Mercury standard library - pred `spawn_native_joinable/5` - pred `join_thread/4` +### Changes to the `time` module + +* The `tm_sec` field of the `tm/0` type now only allows for a single positive + leap second for any given minute. + ### Changes to the `tree_bitset` module * The following predicates and functions have been added: diff --git a/library/calendar.m b/library/calendar.m index 496f8677a..3b6a5d240 100644 --- a/library/calendar.m +++ b/library/calendar.m @@ -55,7 +55,7 @@ :- type day_of_month == int. % 1 .. 31 depending on the month and year :- type hour == int. % 0 .. 23 :- type minute == int. % 0 .. 59 -:- type second == int. % 0 .. 61 (60 and 61 are for leap seconds) +:- type second == int. % 0 .. 60 (60 is for a positive leap second) :- type microsecond == int. % 0 .. 999,999 :- type month @@ -165,8 +165,8 @@ % % - Minute is in the range 0 .. 59 % - % - Second is in the range 0 .. 61 - % (to account for up to two leap seconds being added in a year) + % - Second is in the range 0 .. 60 + % (to account for one positive leap second being added to a day) % % - MicroSecond is in the range 0 .. 999,999 % @@ -313,9 +313,9 @@ % - Adding -1 year to February 29, 2020 gives February 28, 2019 % % Note on leap seconds: although individual dates can represent times - % with leap seconds (seconds 60-61), durations ignore them. A day is + % with leap seconds (second 60), durations ignore them. A day is % always treated as exactly 86,400 seconds, even though UTC days - % containing leap seconds are 86,401 or 86,402 seconds long. + % containing leap seconds are 86,399 or 86,401 seconds long. % % Durations are stored internally using four components only: months, days, % seconds and microseconds. When a duration is constructed by @@ -774,7 +774,7 @@ init_date_time(Year, Month, Day, Hour, Minute, Second, MicroSecond, Minute >= 0, Minute < 60, Second >= 0, - Second < 62, + Second < 61, MicroSecond >= 0, MicroSecond < 1000000, DateTime = date_time(Year, month_to_int(Month), Day, Hour, Minute, Second, @@ -843,7 +843,7 @@ date_time_from_string(Str, Date) :- Minute =< 59, read_char((:), !Chars), read_int_and_num_chars(Second, 2, !Chars), - Second < 62, + Second < 61, read_microseconds(MicroSecond, !Chars), !.Chars = [], Date = date_time(Year, Month, Day, Hour, Minute, Second, MicroSecond) diff --git a/library/time.m b/library/time.m index 58dc7605f..1962762fe 100644 --- a/library/time.m +++ b/library/time.m @@ -71,8 +71,8 @@ tm_mday :: int, % MonthDay (1-31) tm_hour :: int, % Hours (after midnight, 0-23) tm_min :: int, % Minutes (0-59) - tm_sec :: int, % Seconds (0-61) - % (60 and 61 are for leap seconds) + tm_sec :: int, % Seconds (0-60) + % (60 allows for a positive leap second) tm_yday :: int, % YearDay (number since Jan 1st, 0-365) tm_wday :: int, % WeekDay (number since Sunday, 0-6) tm_dst :: maybe(dst) % IsDST (is DST applicable, and if so, diff --git a/tests/hard_coded/calendar_date_time_conv.exp b/tests/hard_coded/calendar_date_time_conv.exp index 3757903ba..3b8e3edb1 100644 --- a/tests/hard_coded/calendar_date_time_conv.exp +++ b/tests/hard_coded/calendar_date_time_conv.exp @@ -31,7 +31,6 @@ date_time_from_string("2024-12-31 00:00:00") ===> TEST PASSED (accepted: date_ti date_time_from_string("2024-01-01 00:00:00") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 0)) date_time_from_string("2024-01-01 23:59:59") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 23, 59, 59, 0)) date_time_from_string("2024-01-01 00:00:60") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 60, 0)) -date_time_from_string("2024-01-01 00:00:61") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 61, 0)) date_time_from_string("2024-01-01 00:00:00.1") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 100000)) date_time_from_string("2024-01-01 00:00:00.12") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 120000)) date_time_from_string("2024-01-01 00:00:00.123") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 123000)) @@ -40,7 +39,7 @@ date_time_from_string("2024-01-01 00:00:00.12345") ===> TEST PASSED (accepted: d date_time_from_string("2024-01-01 00:00:00.123456") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 123456)) date_time_from_string("2024-01-01 00:00:00.000001") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 1)) date_time_from_string("2024-01-01 00:00:00.999999") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 999999)) -date_time_from_string("2024-12-31 23:59:61.999999") ===> TEST PASSED (accepted: date_time(2024, 12, 31, 23, 59, 61, 999999)) +date_time_from_string("2024-12-31 23:59:60.999999") ===> TEST PASSED (accepted: date_time(2024, 12, 31, 23, 59, 60, 999999)) date_time_from_string("1970-01-01 00:00:00") ===> TEST PASSED (accepted: date_time(1970, 1, 1, 0, 0, 0, 0)) date_time_from_string("1582-10-15 00:00:00") ===> TEST PASSED (accepted: date_time(1582, 10, 15, 0, 0, 0, 0)) date_time_from_string("2024-01-01 00:00:00.10") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 100000): to-string: "2024-01-01 00:00:00.1")) @@ -86,7 +85,7 @@ date_time_from_string("1900-02-29 00:00:00") ===> TEST PASSED (rejected: Feb 29 date_time_from_string("2024-04-31 00:00:00") ===> TEST PASSED (rejected: day 31 in a 30-day month) date_time_from_string("2024-01-01 24:00:00") ===> TEST PASSED (rejected: hour 24) date_time_from_string("2024-01-01 00:60:00") ===> TEST PASSED (rejected: minute 60) -date_time_from_string("2024-01-01 00:00:62") ===> TEST PASSED (rejected: second 62) +date_time_from_string("2024-01-01 00:00:61") ===> TEST PASSED (rejected: second 61) date_time_from_string("2024-01-01 00:00:00.") ===> TEST PASSED (rejected: trailing dot with no digits) date_time_from_string("2024-01-01 00:00:00.1234567") ===> TEST PASSED (rejected: seven fractional digits) date_time_from_string("-01-01 00:00:00") ===> TEST PASSED (rejected: negative sign but only two-digit year) @@ -149,7 +148,6 @@ det_date_time_from_string("2024-12-31 00:00:00") ===> TEST PASSED (accepted: dat det_date_time_from_string("2024-01-01 00:00:00") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 0)) det_date_time_from_string("2024-01-01 23:59:59") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 23, 59, 59, 0)) det_date_time_from_string("2024-01-01 00:00:60") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 60, 0)) -det_date_time_from_string("2024-01-01 00:00:61") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 61, 0)) det_date_time_from_string("2024-01-01 00:00:00.1") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 100000)) det_date_time_from_string("2024-01-01 00:00:00.12") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 120000)) det_date_time_from_string("2024-01-01 00:00:00.123") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 123000)) @@ -158,7 +156,7 @@ det_date_time_from_string("2024-01-01 00:00:00.12345") ===> TEST PASSED (accepte det_date_time_from_string("2024-01-01 00:00:00.123456") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 123456)) det_date_time_from_string("2024-01-01 00:00:00.000001") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 1)) det_date_time_from_string("2024-01-01 00:00:00.999999") ===> TEST PASSED (accepted: date_time(2024, 1, 1, 0, 0, 0, 999999)) -det_date_time_from_string("2024-12-31 23:59:61.999999") ===> TEST PASSED (accepted: date_time(2024, 12, 31, 23, 59, 61, 999999)) +det_date_time_from_string("2024-12-31 23:59:60.999999") ===> TEST PASSED (accepted: date_time(2024, 12, 31, 23, 59, 60, 999999)) det_date_time_from_string("1970-01-01 00:00:00") ===> TEST PASSED (accepted: date_time(1970, 1, 1, 0, 0, 0, 0)) det_date_time_from_string("1582-10-15 00:00:00") ===> TEST PASSED (accepted: date_time(1582, 10, 15, 0, 0, 0, 0)) @@ -192,7 +190,7 @@ det_date_time_from_string("1900-02-29 00:00:00") ===> TEST PASSED (exception: Fe det_date_time_from_string("2024-04-31 00:00:00") ===> TEST PASSED (exception: day 31 in a 30-day month) det_date_time_from_string("2024-01-01 24:00:00") ===> TEST PASSED (exception: hour 24) det_date_time_from_string("2024-01-01 00:60:00") ===> TEST PASSED (exception: minute 60) -det_date_time_from_string("2024-01-01 00:00:62") ===> TEST PASSED (exception: second 62) +det_date_time_from_string("2024-01-01 00:00:61") ===> TEST PASSED (exception: second 61) det_date_time_from_string("2024-01-01 00:00:00.") ===> TEST PASSED (exception: trailing dot with no digits) det_date_time_from_string("2024-01-01 00:00:00.1234567") ===> TEST PASSED (exception: seven fractional digits) det_date_time_from_string("-01-01 00:00:00") ===> TEST PASSED (exception: negative sign but only two-digit year) diff --git a/tests/hard_coded/calendar_date_time_conv.m b/tests/hard_coded/calendar_date_time_conv.m index b680be6a8..21e7211c1 100644 --- a/tests/hard_coded/calendar_date_time_conv.m +++ b/tests/hard_coded/calendar_date_time_conv.m @@ -204,7 +204,6 @@ valid_date_times = [ dt_conv_test("midnight", "2024-01-01 00:00:00"), dt_conv_test("last second of the day", "2024-01-01 23:59:59"), dt_conv_test("leap second", "2024-01-01 00:00:60"), - dt_conv_test("double leap second", "2024-01-01 00:00:61"), dt_conv_test("one fractional digit", "2024-01-01 00:00:00.1"), dt_conv_test("two fractional digits", "2024-01-01 00:00:00.12"), @@ -215,7 +214,7 @@ valid_date_times = [ dt_conv_test("smallest nonzero microsecond", "2024-01-01 00:00:00.000001"), dt_conv_test("largest microsecond value", "2024-01-01 00:00:00.999999"), - dt_conv_test("all maximum", "2024-12-31 23:59:61.999999"), + dt_conv_test("all maximum", "2024-12-31 23:59:60.999999"), dt_conv_test("Unix epoch", "1970-01-01 00:00:00"), dt_conv_test("first day of the Gregorian calendar", "1582-10-15 00:00:00") @@ -287,7 +286,7 @@ invalid_date_times = [ dt_conv_test("day 31 in a 30-day month", "2024-04-31 00:00:00"), dt_conv_test("hour 24", "2024-01-01 24:00:00"), dt_conv_test("minute 60", "2024-01-01 00:60:00"), - dt_conv_test("second 62", "2024-01-01 00:00:62"), + dt_conv_test("second 61", "2024-01-01 00:00:61"), dt_conv_test("trailing dot with no digits", "2024-01-01 00:00:00."), dt_conv_test("seven fractional digits", "2024-01-01 00:00:00.1234567"),