[m-rev.] diff: add a test for date creation

Julien Fischer jfischer at opturion.com
Tue Mar 10 11:54:50 AEDT 2026


Add a test for date creation.

Add a test for date creation, which is missing from existing calendar test.
In particular, test for the presence of the init_date argument lower
bound checks
that were added in commit 5512b1be7.

tests/hard_coded/Mmakefile:
tests/hard_coded/calendar_init_date.{m,exp}:
     Add the new test case.

Julien.

diff --git a/tests/hard_coded/Mmakefile b/tests/hard_coded/Mmakefile
index 097e2769d..6ed7aec99 100644
--- a/tests/hard_coded/Mmakefile
+++ b/tests/hard_coded/Mmakefile
@@ -790,6 +790,7 @@ ifeq "$(findstring profdeep,$(GRADE))" ""
  bitwise_uint32 \
  bitwise_uint64 \
  bitwise_uint8 \
+ calendar_init_date \
  char_to_string \
  clamp_int \
  clamp_int16 \
diff --git a/tests/hard_coded/calendar_init_date.exp
b/tests/hard_coded/calendar_init_date.exp
new file mode 100644
index 000000000..80a55a59c
--- /dev/null
+++ b/tests/hard_coded/calendar_init_date.exp
@@ -0,0 +1,33 @@
+PASS: valid date (ordinary date)
+PASS: valid date (minimum components)
+PASS: valid date (max hour)
+PASS: valid date (max minute)
+PASS: valid date (max second)
+PASS: valid date (leap second)
+PASS: valid date (max microsecond)
+PASS: valid date (jan 31)
+PASS: valid date (feb 28 non-leap)
+PASS: valid date (feb 29 leap)
+PASS: valid date (mar 31)
+PASS: valid date (apr 30)
+PASS: valid date (dec 31)
+PASS: valid date (feb 28 century)
+PASS: valid date (feb 29 400-year)
+PASS: valid date (negative year)
+PASS: valid date (year zero)
+PASS: init_date/8 failed for invalid date (day zero)
+PASS: init_date/8 failed for invalid date (day 32 in january)
+PASS: init_date/8 failed for invalid date (feb 29 non-leap)
+PASS: init_date/8 failed for invalid date (feb 29 century)
+PASS: init_date/8 failed for invalid date (apr 31)
+PASS: init_date/8 failed for invalid date (negative hour)
+PASS: init_date/8 failed for invalid date (hour 24)
+PASS: init_date/8 failed for invalid date (negative minute)
+PASS: init_date/8 failed for invalid date (minute 60)
+PASS: init_date/8 failed for invalid date (negative second)
+PASS: init_date/8 failed for invalid date (second 62)
+PASS: init_date/8 failed for invalid date (negative microsecond)
+PASS: init_date/8 failed for invalid date (microsecond 1000000)
+PASS: det_init_date threw exception for (det: day 32)
+PASS: det_init_date threw exception for (det: hour 24)
+PASS: det_init_date threw exception for (det: minute 60)
diff --git a/tests/hard_coded/calendar_init_date.m
b/tests/hard_coded/calendar_init_date.m
new file mode 100644
index 000000000..54c2255cc
--- /dev/null
+++ b/tests/hard_coded/calendar_init_date.m
@@ -0,0 +1,201 @@
+%---------------------------------------------------------------------------%
+% vim: ft=mercury ts=4 sw=4 et
+%---------------------------------------------------------------------------%
+% Test calendar.init_data/8 and calendar.det_init_date/7.
+%---------------------------------------------------------------------------%
+
+:- module calendar_init_date.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is cc_multi.
+
+%---------------------------------------------------------------------------%
+%---------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module calendar.
+:- import_module exception.
+:- import_module list.
+:- import_module string.
+
+%---------------------------------------------------------------------------%
+
+main(!IO) :-
+    test_valid_dates(!IO),
+    test_invalid_dates(!IO),
+    test_det_init_date(!IO).
+
+%---------------------------------------------------------------------------%
+
+:- type test_date
+    --->    test_date(
+                description  :: string,
+                year         :: year,
+                month        :: month,
+                day_of_month :: day_of_month,
+                hour         :: hour,
+                minute       :: minute,
+                second       :: second,
+                microsecond  :: microsecond
+            ).
+
+%---------------------------------------------------------------------------%
+%
+% Valid dates.
+%
+
+:- pred test_valid_dates(io::di, io::uo) is det.
+
+test_valid_dates(!IO) :-
+    list.foldl(test_valid_case, valid_dates, !IO).
+
+:- pred test_valid_case(test_date::in, io::di, io::uo) is det.
+
+test_valid_case(TestDate, !IO) :-
+    TestDate  = test_date(Desc, Y, M, D, H, Min, S, Us),
+    ( if init_date(Y, M, D, H, Min, S, Us, Date) then
+        % Round-trip: verify that components are preserved by unpack_date/8.
+        unpack_date(Date, Y2, M2, D2, H2, Min2, S2, Us2),
+        ( if
+            Y = Y2, M = M2, D = D2, H = H2, Min = Min2, S = S2, Us = Us2
+        then
+            io.format("PASS: valid date (%s)\n", [s(Desc)], !IO)
+        else
+            io.format("FAIL: round-trip mismatch for valid date (%s)\n",
+                [s(Desc)], !IO)
+        )
+    else
+        io.format("FAIL: init_date/8 failed for valid date (%s)\n",
+            [s(Desc)], !IO)
+    ).
+
+:- func valid_dates = list(test_date).
+
+valid_dates = [
+    % Ordinary dates.
+    test_date("ordinary date",      2024, january,  15, 12, 30,  0,  0),
+
+    % Minimum component values.
+    test_date("minimum components", 2024, january,   1,  0,  0,  0,  0),
+
+    % Maximum time-of-day values.
+    test_date("max hour",           2024, january,   1, 23,  0,  0,  0),
+    test_date("max minute",         2024, january,   1,  0, 59,  0,  0),
+    test_date("max second",         2024, january,   1,  0,  0, 59,  0),
+    test_date("leap second",        2024, january,   1,  0,  0, 60,  0),
+    test_date("max microsecond",    2024, january,   1,  0,  0,  0,  999999),
+
+    % Month boundary days.
+    test_date("jan 31",             2024, january,  31,  0,  0,  0,  0),
+    test_date("feb 28 non-leap",    2023, february, 28,  0,  0,  0,  0),
+    test_date("feb 29 leap",        2024, february, 29,  0,  0,  0,  0),
+    test_date("mar 31",             2024, march,    31,  0,  0,  0,  0),
+    test_date("apr 30",             2024, april,    30,  0,  0,  0,  0),
+    test_date("dec 31",             2024, december, 31,  0,  0,  0,  0),
+
+    % Century year (non-leap).
+    test_date("feb 28 century",     1900, february, 28,  0,  0,  0,  0),
+
+    % 400-year (leap).
+    test_date("feb 29 400-year",    2000, february, 29,  0,  0,  0,  0),
+
+    % Negative years.
+    test_date("negative year",        -1, january,   1,  0,  0,  0,  0),
+
+    % Year zero (1 BC).
+    test_date("year zero",             0, march,     1,  0,  0,  0,  0)
+].
+
+%---------------------------------------------------------------------------%
+%
+% Invalid dates.
+%
+
+:- pred test_invalid_dates(io::di, io::uo) is det.
+
+test_invalid_dates(!IO) :-
+    list.foldl(test_invalid_case, invalid_dates, !IO).
+
+:- pred test_invalid_case(test_date::in, io::di, io::uo) is det.
+
+test_invalid_case(TestDate, !IO) :-
+    TestDate = test_date(Desc, Y, M, D, H, Min, S, Us),
+    ( if init_date(Y, M, D, H, Min, S, Us, _Date) then
+        io.format("FAIL: init_date/8 succeeded for invalid date (%s)\n",
+            [s(Desc)], !IO)
+    else
+        io.format("PASS: init_date/8 failed for invalid date (%s)\n",
+            [s(Desc)], !IO)
+    ).
+
+:- func invalid_dates = list(test_date).
+
+invalid_dates = [
+    % Day-of-month out of range.
+    test_date("day zero",             2024, january,   0,  0,  0,   0,  0),
+    test_date("day 32 in january",    2024, january,  32,  0,  0,   0,  0),
+    test_date("feb 29 non-leap",      2023, february, 29,  0,  0,   0,  0),
+    test_date("feb 29 century",       1900, february, 29,  0,  0,   0,  0),
+    test_date("apr 31",               2024, april,    31,  0,  0,   0,  0),
+
+    % Hour out of range.
+    test_date("negative hour",        2024, january,   1, -1,  0,   0,  0),
+    test_date("hour 24",              2024, january,   1, 24,  0,   0,  0),
+
+    % Minute out of range.
+    test_date("negative minute",      2024, january,   1,  0, -1,   0,  0),
+    test_date("minute 60",            2024, january,   1,  0, 60,   0,  0),
+
+    % Second out of range.
+    test_date("negative second",      2024, january,   1,  0,  0,  -1,  0),
+    test_date("second 62",            2024, january,   1,  0,  0,  62,  0),
+
+    % Microsecond out of range.
+    test_date("negative microsecond", 2024, january,   1,  0,   0,  0, -1),
+    test_date("microsecond 1000000",  2024, january,   1,  0,   0,  0, 1000000)
+].
+
+%---------------------------------------------------------------------------%
+%
+% Test det_init_date/7.
+%
+
+:- pred test_det_init_date(io::di, io::uo) is cc_multi.
+
+test_det_init_date(!IO) :-
+    list.foldl(test_det_exception_case, exception_dates, !IO).
+
+:- pred test_det_exception_case(test_date::in, io::di, io::uo) is cc_multi.
+
+test_det_exception_case(TestDate, !IO) :-
+    TestDate = test_date(Desc, Y, M, D, H, Min, S, Us),
+    ( try []
+        Date = det_init_date(Y, M, D, H, Min, S, Us)
+    then
+        use_date(Date, !IO),
+        io.format("FAIL: det_init_date did not throw for (%s)\n",
+            [s(Desc)], !IO)
+    catch_any _ ->
+        io.format("PASS: det_init_date threw exception for (%s)\n",
+            [s(Desc)], !IO)
+    ).
+
+:- func exception_dates = list(test_date).
+
+exception_dates = [
+    test_date("det: day 32",    2024, january, 32, 0, 0, 0, 0),
+    test_date("det: hour 24",   2024, january,  1, 24, 0, 0, 0),
+    test_date("det: minute 60", 2024, january,  1, 0, 60, 0, 0)
+].
+
+:- pragma no_inline(pred(use_date/3)).
+:- pred use_date(date::in, io::di, io::uo) is det.
+
+use_date(_, !IO).
+
+%---------------------------------------------------------------------------%
+:- end_module calendar_init_date.
+%---------------------------------------------------------------------------%


More information about the reviews mailing list