mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 01:13:30 +00:00
Add is_leap_year/1 and days_in_month/2.
Add a predicate to the calendar module for testing if a year is a leap year.
Use a more efficient method for determining this than the existing code
in the implementation of this module used (and replace that code with
a call to the new predicate).
Add the function days_to_month/2, which is a strongly typed wrapper
for the implementation function max_day_in_month_for/2.
Add a new test case covering basic operations in the calendar module,
together with the newly added operations.
library/calendar.m:
As above.
NEWS.md:
Announce the new additions.
tests/hard_coded/Mmakefile:
tests/hard_coded/calendar_basics.{m,exp}:
Add the new test case.
This commit is contained in:
7
NEWS.md
7
NEWS.md
@@ -224,6 +224,13 @@ Changes to the Mercury standard library
|
||||
- func `promise_only_solution/1`
|
||||
- pred `promise_only_solution_io/4`
|
||||
|
||||
### Changes to the `calendar` module
|
||||
|
||||
* The following function and predicate have been added:
|
||||
|
||||
- func `days_in_month/2`
|
||||
- pred `is_leap_year/1`
|
||||
|
||||
### Changes to the `char` module
|
||||
|
||||
* The following type has had its typeclass memberships changed:
|
||||
|
||||
@@ -124,6 +124,24 @@
|
||||
%
|
||||
:- func month_to_int0(month) = int.
|
||||
|
||||
% days_in_month(Year, Month) = Days:
|
||||
%
|
||||
% Return the number of days in Month of Year in the proleptic
|
||||
% Gregorian calendar.
|
||||
%
|
||||
:- func days_in_month(year, month) = int.
|
||||
|
||||
% is_leap_year(Year):
|
||||
%
|
||||
% Succeed if-and-only-if Year is a leap year in the proleptic
|
||||
% Gregorian calendar.
|
||||
%
|
||||
% A year is a leap year if it is divisible by 4, except that years
|
||||
% divisible by 100 are not leap years, unless they are also divisible
|
||||
% by 400.
|
||||
%
|
||||
:- pred is_leap_year(year::in) is semidet.
|
||||
|
||||
%---------------------%
|
||||
|
||||
% init_date(Year, Month, Day, Hour, Minute, Second, MicroSecond, Date):
|
||||
@@ -670,11 +688,37 @@ month_to_int(Month) = Int :-
|
||||
month_to_int0(Month) = Int :-
|
||||
int0_to_month(Int, Month).
|
||||
|
||||
days_in_month(Year, Month) =
|
||||
max_day_in_month_for(Year, month_to_int(Month)).
|
||||
|
||||
is_leap_year(Year) :-
|
||||
( if Year /\ 3 = 0 then
|
||||
% Year is divisible by 4.
|
||||
( if Year `unchecked_rem` 25 \= 0 then
|
||||
% Year is not divisible by 25. Since it is divisible by 4
|
||||
% but not by 25, it is not divisible by lcm(4, 25) = 100,
|
||||
% so it is not a century year. All non-century years that are
|
||||
% multiples of 4 are leap years.
|
||||
true
|
||||
else
|
||||
% Year is divisible by both 4 and 25, therefore it is
|
||||
% divisible by lcm(4, 25) = 100: it is a century year.
|
||||
% A century year is a leap year only if it is divisible
|
||||
% by 400. Since Year is already divisible by 100,
|
||||
% it is divisible by 400 iff it is also divisible by
|
||||
% lcm(100, 16) = 400, i.e. iff it is divisible by 16.
|
||||
Year /\ 15 = 0
|
||||
)
|
||||
else
|
||||
% Year is not divisible by 4, so it is not a leap year.
|
||||
fail
|
||||
).
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
init_date(Year, Month, Day, Hour, Minute, Second, MicroSecond, Date) :-
|
||||
Day >= 1,
|
||||
Day =< max_day_in_month_for(Year, month_to_int(Month)),
|
||||
Day =< days_in_month(Year, Month),
|
||||
Hour >= 0,
|
||||
Hour < 24,
|
||||
Minute >= 0,
|
||||
@@ -1089,7 +1133,7 @@ max_day_in_month_for(YearValue, MonthValue) = Max :-
|
||||
Max0 = 30
|
||||
;
|
||||
M = 2,
|
||||
( if ( Y mod 400 = 0 ; ( Y mod 100 \= 0, Y mod 4 = 0 ) ) then
|
||||
( if is_leap_year(Y) then
|
||||
Max0 = 29
|
||||
else
|
||||
Max0 = 28
|
||||
|
||||
@@ -791,6 +791,7 @@ ifeq "$(findstring profdeep,$(GRADE))" ""
|
||||
bitwise_uint32 \
|
||||
bitwise_uint64 \
|
||||
bitwise_uint8 \
|
||||
calendar_basics \
|
||||
calendar_init_date \
|
||||
char_to_string \
|
||||
clamp_int \
|
||||
|
||||
109
tests/hard_coded/calendar_basics.exp
Normal file
109
tests/hard_coded/calendar_basics.exp
Normal file
@@ -0,0 +1,109 @@
|
||||
=== Test det_int_to_month/2 ===
|
||||
|
||||
det_int_to_month(-1) ==> EXCEPTION
|
||||
det_int_to_month(0) ==> EXCEPTION
|
||||
det_int_to_month(1) ==> january
|
||||
det_int_to_month(2) ==> february
|
||||
det_int_to_month(11) ==> november
|
||||
det_int_to_month(12) ==> december
|
||||
det_int_to_month(13) ==> EXCEPTION
|
||||
|
||||
=== Test det_int0_to_month/2 ===
|
||||
|
||||
det_int0_to_month(-1) ==> EXCEPTION
|
||||
det_int0_to_month(0) ==> january
|
||||
det_int0_to_month(1) ==> february
|
||||
det_int0_to_month(2) ==> march
|
||||
det_int0_to_month(11) ==> december
|
||||
det_int0_to_month(12) ==> EXCEPTION
|
||||
det_int0_to_month(13) ==> EXCEPTION
|
||||
|
||||
=== Test int_to_month/2 ===
|
||||
|
||||
int_to_month(-1) ==> FAILED
|
||||
int_to_month(0) ==> FAILED
|
||||
int_to_month(1) ==> january
|
||||
int_to_month(2) ==> february
|
||||
int_to_month(11) ==> november
|
||||
int_to_month(12) ==> december
|
||||
int_to_month(13) ==> FAILED
|
||||
|
||||
=== Test int0_to_month/2 ===
|
||||
|
||||
int0_to_month(-1) ==> FAILED
|
||||
int0_to_month(0) ==> january
|
||||
int0_to_month(1) ==> february
|
||||
int0_to_month(2) ==> march
|
||||
int0_to_month(11) ==> december
|
||||
int0_to_month(12) ==> FAILED
|
||||
int0_to_month(13) ==> FAILED
|
||||
|
||||
=== Test month_to_int/1 ===
|
||||
|
||||
month_to_int(january) = 1
|
||||
month_to_int(february) = 2
|
||||
month_to_int(march) = 3
|
||||
month_to_int(april) = 4
|
||||
month_to_int(may) = 5
|
||||
month_to_int(june) = 6
|
||||
month_to_int(july) = 7
|
||||
month_to_int(august) = 8
|
||||
month_to_int(september) = 9
|
||||
month_to_int(october) = 10
|
||||
month_to_int(november) = 11
|
||||
month_to_int(december) = 12
|
||||
|
||||
=== Test month_to_int0/1 ===
|
||||
|
||||
month_to_int0(january) = 0
|
||||
month_to_int0(february) = 1
|
||||
month_to_int0(march) = 2
|
||||
month_to_int0(april) = 3
|
||||
month_to_int0(may) = 4
|
||||
month_to_int0(june) = 5
|
||||
month_to_int0(july) = 6
|
||||
month_to_int0(august) = 7
|
||||
month_to_int0(september) = 8
|
||||
month_to_int0(october) = 9
|
||||
month_to_int0(november) = 10
|
||||
month_to_int0(december) = 11
|
||||
|
||||
=== Test days_in_month/2 ===
|
||||
|
||||
days_in_month(1977, january) = 31
|
||||
days_in_month(1977, february) = 28
|
||||
days_in_month(1977, march) = 31
|
||||
days_in_month(1977, april) = 30
|
||||
days_in_month(1977, may) = 31
|
||||
days_in_month(1977, june) = 30
|
||||
days_in_month(1977, july) = 31
|
||||
days_in_month(1977, august) = 31
|
||||
days_in_month(1977, september) = 30
|
||||
days_in_month(1977, october) = 31
|
||||
days_in_month(1977, november) = 30
|
||||
days_in_month(1977, december) = 31
|
||||
|
||||
days_in_month(2000, january) = 31
|
||||
days_in_month(2000, february) = 29
|
||||
days_in_month(2000, march) = 31
|
||||
days_in_month(2000, april) = 30
|
||||
days_in_month(2000, may) = 31
|
||||
days_in_month(2000, june) = 30
|
||||
days_in_month(2000, july) = 31
|
||||
days_in_month(2000, august) = 31
|
||||
days_in_month(2000, september) = 30
|
||||
days_in_month(2000, october) = 31
|
||||
days_in_month(2000, november) = 30
|
||||
days_in_month(2000, december) = 31
|
||||
|
||||
=== Test is_leap_year/1 ===
|
||||
|
||||
Year 2000 is a leap year.
|
||||
Year 1900 is a common year.
|
||||
Year 2024 is a leap year.
|
||||
Year 2023 is a common year.
|
||||
Year 0 is a leap year.
|
||||
Year -1 is a common year.
|
||||
Year -4 is a leap year.
|
||||
Year -100 is a common year.
|
||||
|
||||
179
tests/hard_coded/calendar_basics.m
Normal file
179
tests/hard_coded/calendar_basics.m
Normal file
@@ -0,0 +1,179 @@
|
||||
%---------------------------------------------------------------------------%
|
||||
% vim: ft=mercury ts=4 sw=4 et
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- module calendar_basics.
|
||||
:- interface.
|
||||
|
||||
:- import_module io.
|
||||
|
||||
:- pred main(io::di, io::uo) is cc_multi.
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- implementation.
|
||||
|
||||
:- import_module calendar.
|
||||
:- import_module list.
|
||||
:- import_module string.
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
main(!IO) :-
|
||||
test_det_int_to_month("det_int_to_month", det_int_to_month, !IO),
|
||||
test_det_int_to_month("det_int0_to_month", det_int0_to_month, !IO),
|
||||
test_int_to_month("int_to_month",
|
||||
(pred(I::in, M::out) is semidet :- int_to_month(I, M)), !IO),
|
||||
test_int_to_month("int0_to_month",
|
||||
(pred(I::in, M::out) is semidet :- int0_to_month(I, M)), !IO),
|
||||
test_month_to_int("month_to_int", month_to_int, !IO),
|
||||
test_month_to_int("month_to_int0", month_to_int0, !IO),
|
||||
test_days_in_month(!IO),
|
||||
test_is_leap_year(!IO).
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- pred test_det_int_to_month(string::in,
|
||||
(func(int) = month)::in, io::di, io::uo) is cc_multi.
|
||||
|
||||
test_det_int_to_month(Desc, Func, !IO) :-
|
||||
io.format("=== Test %s/2 ===\n\n", [s(Desc)], !IO),
|
||||
list.foldl(do_test_det_int_to_month(Desc, Func), ints, !IO),
|
||||
io.nl(!IO).
|
||||
|
||||
:- pred do_test_det_int_to_month(string::in,
|
||||
(func(int) = month)::in, int::in, io::di, io::uo) is cc_multi.
|
||||
|
||||
do_test_det_int_to_month(Desc, Func, Int, !IO) :-
|
||||
io.format("%s(%d) ==> ", [s(Desc), i(Int)], !IO),
|
||||
( try []
|
||||
Month = Func(Int)
|
||||
then
|
||||
io.format("%s\n", [s(string(Month))], !IO)
|
||||
catch_any _ ->
|
||||
io.write_string("EXCEPTION\n", !IO)
|
||||
).
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- pred test_int_to_month(string::in,
|
||||
pred(int, month)::in(pred(in, out) is semidet), io::di, io::uo) is det.
|
||||
|
||||
test_int_to_month(Desc, Pred, !IO) :-
|
||||
io.format("=== Test %s/2 ===\n\n", [s(Desc)], !IO),
|
||||
list.foldl(do_test_int_to_month(Desc, Pred), ints, !IO),
|
||||
io.nl(!IO).
|
||||
|
||||
:- pred do_test_int_to_month(string::in,
|
||||
pred(int, month)::in(pred(in, out) is semidet), int::in,
|
||||
io::di, io::uo) is det.
|
||||
|
||||
do_test_int_to_month(Desc, Pred, Int, !IO) :-
|
||||
io.format("%s(%d) ==> ", [s(Desc), i(Int)], !IO),
|
||||
( if Pred(Int, Month) then
|
||||
io.format("%s\n", [s(string(Month))], !IO)
|
||||
else
|
||||
io.write_string("FAILED\n", !IO)
|
||||
).
|
||||
|
||||
:- func ints = list(int).
|
||||
|
||||
ints = [
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
11,
|
||||
12,
|
||||
13
|
||||
].
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- pred test_month_to_int(string::in, (func(month) = int)::in,
|
||||
io::di, io::uo) is det.
|
||||
|
||||
test_month_to_int(Desc, Func, !IO) :-
|
||||
io.format("=== Test %s/1 ===\n\n", [s(Desc)], !IO),
|
||||
list.foldl(do_test_month_to_int(Desc, Func), months, !IO),
|
||||
io.nl(!IO).
|
||||
|
||||
:- pred do_test_month_to_int(string::in, (func(month) = int)::in, month::in,
|
||||
io::di, io::uo) is det.
|
||||
|
||||
do_test_month_to_int(Desc, Func, Month, !IO) :-
|
||||
Int = Func(Month),
|
||||
io.format("%s(%s) = %d\n", [s(Desc), s(string(Month)), i(Int)], !IO).
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- pred test_days_in_month(io::di, io::uo) is det.
|
||||
|
||||
test_days_in_month(!IO) :-
|
||||
io.write_string("=== Test days_in_month/2 ===\n\n", !IO),
|
||||
list.foldl(do_test_days_in_month, [1977, 2000], !IO).
|
||||
|
||||
:- pred do_test_days_in_month(year::in, io::di, io::uo) is det.
|
||||
|
||||
do_test_days_in_month(Year, !IO) :-
|
||||
list.foldl(do_test_days_in_month_2(Year), months, !IO),
|
||||
io.nl(!IO).
|
||||
|
||||
:- pred do_test_days_in_month_2(year::in, month::in, io::di, io::uo) is det.
|
||||
|
||||
do_test_days_in_month_2(Year, Month, !IO) :-
|
||||
DaysInMonth = days_in_month(Year, Month),
|
||||
io.format("days_in_month(%d, %s) = %d\n",
|
||||
[i(Year), s(string(Month)), i(DaysInMonth)], !IO).
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- pred test_is_leap_year(io::di, io::uo) is det.
|
||||
|
||||
test_is_leap_year(!IO) :-
|
||||
io.write_string("=== Test is_leap_year/1 ===\n\n", !IO),
|
||||
list.foldl(do_test_is_leap_year, test_years, !IO),
|
||||
io.nl(!IO).
|
||||
|
||||
:- pred do_test_is_leap_year(year::in, io::di, io::uo) is det.
|
||||
|
||||
do_test_is_leap_year(Year, !IO) :-
|
||||
Desc = ( if is_leap_year(Year) then "leap" else "common" ),
|
||||
io.format("Year %d is a %s year.\n", [i(Year), s(Desc)], !IO).
|
||||
|
||||
:- func test_years = list(year).
|
||||
|
||||
test_years = [
|
||||
2000, % Divisible by 400: leap year.
|
||||
1900, % Divisible by 100, but not by 100: common year.
|
||||
2024, % Divisible by 4, but not by 1000: leap year.
|
||||
2023, % Not divisible by 4: common year.
|
||||
0, % Divisible by 400: leap year.
|
||||
-1, % Not divisible by 4: common year.
|
||||
-4, % Divisible by 4: leap year.
|
||||
-100 % Divisible by 100, but not 400: common year.
|
||||
].
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
|
||||
:- func months = list(month).
|
||||
|
||||
months = [
|
||||
january,
|
||||
february,
|
||||
march,
|
||||
april,
|
||||
may,
|
||||
june,
|
||||
july,
|
||||
august,
|
||||
september,
|
||||
october,
|
||||
november,
|
||||
december
|
||||
].
|
||||
|
||||
%---------------------------------------------------------------------------%
|
||||
:- end_module calendar_basics.
|
||||
%---------------------------------------------------------------------------%
|
||||
Reference in New Issue
Block a user