[m-rev.] for review: additions for the calendar module
Julien Fischer
jfischer at opturion.com
Thu Dec 4 13:04:16 AEDT 2014
For review by anyone.
-----------------------
Additions for the calendar module.
library/calendar.m:
Add predicates and functions for converting between 1-based
and 0-based month numbers and values of the month/0 type.
The former are useful when dealing with human-provided dates
whereas the latter are useful when interacting with dates
provided from some foreign language (C and Java for example).
Add a predicate for testing for equality of dates ignoring
their time component.
Replace the locally defined predicate char_to_digit/2 with
calls to char.decimal_digit_to_int/2
Fix some formatting issues.
tests/hard_coded/calendar_test.{m,exp}:
Extend this test case to cover the functionality added
above.
NEWS:
Announce the additions.
Julien.
diff --git a/NEWS b/NEWS
index 34277bd..97cf360 100644
--- a/NEWS
+++ b/NEWS
@@ -115,6 +115,16 @@ Changes to the Mercury standard library:
* We have a added a new type, maybe_errors, to the maybe module.
+* The following predicates and functions have been added to the calendar module
+
+ - int_to_month/2
+ - det_int_to_month/1
+ - int0_to_month/2
+ - det_int0_to_month/1
+ - month_to_int/1
+ - month_to_int0/1
+ - same_date/1
+
Changes to the Mercury compiler:
* We have enabled stricter checking of non-ground final insts to reject more
diff --git a/library/calendar.m b/library/calendar.m
index a5eb100..f5ed62c 100644
--- a/library/calendar.m
+++ b/library/calendar.m
@@ -77,7 +77,41 @@
:- func second(date) = second.
:- func microsecond(date) = microsecond.
- % init_date(Year, Month, Day, Hour, Minute, Second, MicroSecond, Date).
+ % int_to_month(Int, Month):
+ % Int is the number of Month where months are numbered from 1-12.
+ %
+:- pred int_to_month(int, month).
+:- mode int_to_month(in, out) is semidet.
+:- mode int_to_month(out, in) is det.
+
+ % det_int_to_month(Int) returns the month corresponding to Int.
+ % Throws an exception if Int is not in 1-12.
+ %
+:- func det_int_to_month(int) = month.
+
+ % int_to_month(Int, Month):
+ % Int is the number of Month where months are numbered from 0-11.
+ %
+:- pred int0_to_month(int, month) is semidet.
+:- mode int0_to_month(in, out) is semidet.
+:- mode int0_to_month(out, in) is det.
+
+ % det_int0_to_month(Int) returns the month corresponding to Int.
+ % Throws an exception if Int is not in 0-11.
+ %
+:- func det_int0_to_month(int) = month.
+
+ % month_to_int(Month) returns the number of Month where months are
+ % numbered from 1-12.
+ %
+:- func month_to_int(month) = int.
+
+ % month_to_int0(Month) returns the number of Month where months are
+ % numbered from 0-11.
+ %
+:- func month_to_int0(month) = int.
+
+ % init_date(Year, Month, Day, Hour, Minute, Second, MicroSecond, Date):
% Initialize a new date. Fails if the given date is invalid.
%
:- pred init_date(year::in, month::in, day_of_month::in, hour::in,
@@ -125,6 +159,11 @@
%
:- func unix_epoch = date.
+%---------------------------------------------------------------------------%
+%
+% Durations.
+%
+
% A period of time measured in years, months, days, hours, minutes,
% seconds and microseconds. Internally a duration is represented
% using only months, days, seconds and microseconds components.
@@ -340,6 +379,14 @@
in, out, in, out, di, uo) is semidet.
%---------------------------------------------------------------------------%
+
+ % same_date(A, B):
+ % True iff A and B are equal with respect to only their date components.
+ % The time components are ignored.
+ %
+:- pred same_date(date::in, date::in) is semidet.
+
+%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
@@ -373,6 +420,7 @@
).
%---------------------------------------------------------------------------%
+%
% Parsing.
%
@@ -438,7 +486,7 @@ read_int_and_num_chars(Val, N, !Chars) :-
read_int_and_num_chars_2(!Val, !N, !Chars) :-
(
!.Chars = [Char | Rest],
- char_to_digit(Char, Digit)
+ decimal_digit_to_int(Char, Digit)
->
!:Val = !.Val * 10 + Digit,
read_int_and_num_chars_2(!Val, !.N + 1, !:N, Rest, !:Chars)
@@ -550,7 +598,7 @@ read_int(Val, !Chars) :-
read_int_2(!Val, !Chars) :-
(
!.Chars = [Char | Rest],
- char_to_digit(Char, Digit)
+ decimal_digit_to_int(Char, Digit)
->
!:Val = !.Val * 10 + Digit,
read_int_2(!Val, Rest, !:Chars)
@@ -619,6 +667,7 @@ det_duration_from_string(Str) = Duration :-
).
%---------------------------------------------------------------------------%
+%
% Serialization.
%
@@ -633,7 +682,7 @@ date_to_string(Date) = Str :-
),
MicroSecondStr = microsecond_string(MicroSecond),
Str = string.format("%s%04d-%02d-%02d %02d:%02d:%02d%s",
- [s(SignStr), i(Year), i(month_num(Month)), i(Day), i(Hour), i(Minute),
+ [s(SignStr), i(Year), i(month_to_int(Month)), i(Day), i(Hour), i(Minute),
i(Second), s(MicroSecondStr)]).
:- func microsecond_string(microseconds) = string.
@@ -1019,7 +1068,7 @@ det_day_of_week_from_mod(Mod) = DayOfWeek :-
%
year(Date) = Date ^ dt_year.
-month(Date) = det_month(Date ^ dt_month).
+month(Date) = det_int_to_month(Date ^ dt_month).
day_of_month(Date) = Date ^ dt_day.
hour(Date) = Date ^ dt_hour.
minute(Date) = Date ^ dt_minute.
@@ -1034,14 +1083,48 @@ minutes(Dur) = (Dur ^ dur_seconds rem 3600) // 60.
seconds(Dur) = Dur ^ dur_seconds rem 60.
microseconds(Dur) = Dur ^ dur_microseconds.
+int_to_month(1, january).
+int_to_month(2, february).
+int_to_month(3, march).
+int_to_month(4, april).
+int_to_month(5, may).
+int_to_month(6, june).
+int_to_month(7, july).
+int_to_month(8, august).
+int_to_month(9, september).
+int_to_month(10, october).
+int_to_month(11, november).
+int_to_month(12, december).
+
+det_int_to_month(Int) =
+ ( if int_to_month(Int, Month)
+ then Month
+ else func_error("det_int_to_month: invalid month: " ++ int_to_string(Int))
+ ).
+
+month_to_int(Month) = Int :-
+ int_to_month(Int, Month).
+
+int0_to_month(Int, Month) :-
+ int_to_month(Int + 1, Month).
+
+det_int0_to_month(Int) =
+ ( if int0_to_month(Int, Month)
+ then Month
+ else func_error("det_int0_to_month: invalid month: " ++ int_to_string(Int))
+ ).
+
+month_to_int0(Month) = Int :-
+ int0_to_month(Int, Month).
+
init_date(Year, Month, Day, Hour, Minute, Second, MicroSecond, Date) :-
Day >= 1,
- Day =< max_day_in_month_for(Year, month_num(Month)),
+ Day =< max_day_in_month_for(Year, month_to_int(Month)),
Hour < 24,
Minute < 60,
Second < 62,
MicroSecond < 1000000,
- Date = date(Year, month_num(Month), Day, Hour, Minute, Second,
+ Date = date(Year, month_to_int(Month), Day, Hour, Minute, Second,
MicroSecond).
det_init_date(Year, Month, Day, Hour, Minute, Second, MicroSecond)
@@ -1050,12 +1133,12 @@ det_init_date(Year, Month, Day, Hour, Minute, Second, MicroSecond)
Date = Date0
;
error(string.format("calendar.det_init_date: invalid date: " ++
- "%i-%i-%i %i:%i:%i", [i(Year), i(month_num(Month)), i(Day), i(Hour),
+ "%i-%i-%i %i:%i:%i", [i(Year), i(month_to_int(Month)), i(Day), i(Hour),
i(Minute), i(Second)]))
).
unpack_date(date(Year, Month, Day, Hour, Minute, Second, MicroSecond),
- Year, det_month(Month), Day, Hour, Minute, Second, MicroSecond).
+ Year, det_int_to_month(Month), Day, Hour, Minute, Second, MicroSecond).
current_local_time(Now, !IO) :-
time.time(TimeT, !IO),
@@ -1092,49 +1175,6 @@ negate(duration(Months, Days, Seconds, MicroSeconds)) =
zero_duration = duration(0, 0, 0, 0).
-:- func det_month(int) = month.
-
-det_month(N) = Month :-
- ( num_to_month(N, Month0) ->
- Month = Month0
- ;
- error("det_month: invalid month: " ++ int_to_string(N))
- ).
-
-:- func month_num(month) = int.
-
-month_num(Month) = N :- num_to_month(N, Month).
-
-:- pred num_to_month(int, month).
-:- mode num_to_month(in, out) is semidet.
-:- mode num_to_month(out, in) is det.
-
-num_to_month(1, january).
-num_to_month(2, february).
-num_to_month(3, march).
-num_to_month(4, april).
-num_to_month(5, may).
-num_to_month(6, june).
-num_to_month(7, july).
-num_to_month(8, august).
-num_to_month(9, september).
-num_to_month(10, october).
-num_to_month(11, november).
-num_to_month(12, december).
-
-:- pred char_to_digit(char::in, int::out) is semidet.
-
-char_to_digit('0', 0).
-char_to_digit('1', 1).
-char_to_digit('2', 2).
-char_to_digit('3', 3).
-char_to_digit('4', 4).
-char_to_digit('5', 5).
-char_to_digit('6', 6).
-char_to_digit('7', 7).
-char_to_digit('8', 8).
-char_to_digit('9', 9).
-
:- pred day_of_week_num(day_of_week, int).
:- mode day_of_week_num(in, out) is det.
:- mode day_of_week_num(out, in) is semidet.
@@ -1191,5 +1231,11 @@ foldl3_days(Pred, !.Curr, End, !Acc1, !Acc2, !Acc3) :-
).
%---------------------------------------------------------------------------%
+
+same_date(A, B) :-
+ A = date(Year, Month, Day, _, _, _, _),
+ B = date(Year, Month, Day, _, _, _, _).
+
+%---------------------------------------------------------------------------%
:- end_module calendar.
%---------------------------------------------------------------------------%
diff --git a/tests/hard_coded/calendar_test.exp b/tests/hard_coded/calendar_test.exp
index cc9b5aa..ae872fd 100644
--- a/tests/hard_coded/calendar_test.exp
+++ b/tests/hard_coded/calendar_test.exp
@@ -102,3 +102,66 @@ Day of the week:
Parse test:
P2Y6M100DT10H16M30.0003S
+
+Month-to-int (1-based):
+january -> 1
+february -> 2
+march -> 3
+april -> 4
+may -> 5
+june -> 6
+july -> 7
+august -> 8
+september -> 9
+october -> 10
+november -> 11
+december -> 12
+
+Month-to-int (0-based):
+january -> 0
+february -> 1
+march -> 2
+april -> 3
+may -> 4
+june -> 5
+july -> 6
+august -> 7
+september -> 8
+october -> 9
+november -> 10
+december -> 11
+
+Int-to-month (1-based):
+-1 -> out-of-range
+0 -> out-of-range
+1 -> january
+2 -> february
+3 -> march
+4 -> april
+5 -> may
+6 -> june
+7 -> july
+8 -> august
+9 -> september
+10 -> october
+11 -> november
+12 -> december
+13 -> out-of-range
+
+Int-to-month (0-based):
+-1 -> out-of-range
+0 -> january
+1 -> february
+2 -> march
+3 -> april
+4 -> may
+5 -> june
+6 -> july
+7 -> august
+8 -> september
+9 -> october
+10 -> november
+11 -> december
+12 -> out-of-range
+13 -> out-of-range
+
diff --git a/tests/hard_coded/calendar_test.m b/tests/hard_coded/calendar_test.m
index 254e1fc..1770d7e 100644
--- a/tests/hard_coded/calendar_test.m
+++ b/tests/hard_coded/calendar_test.m
@@ -1,5 +1,6 @@
-:- module calendar_test.
+% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
+:- module calendar_test.
:- interface.
:- import_module io.
@@ -74,8 +75,31 @@ main(!IO) :-
io.nl(!IO),
io.write_string("Parse test:\n", !IO),
io.write_string(duration_to_string(
- det_duration_from_string("P1Y18M100DT10H15M90.0003S")), !IO),
+ det_duration_from_string("P1Y18M100DT10H15M90.0003S")), !IO),
+ io.nl(!IO),
+ io.nl(!IO),
+ io.write_string("Month-to-int (1-based):\n", !IO),
+ list.foldl(test_month_to_int, all_months, !IO),
+ io.nl(!IO),
+ io.write_string("Month-to-int (0-based):\n", !IO),
+ list.foldl(test_month_to_int0, all_months, !IO),
+ io.nl(!IO),
+ io.write_string("Int-to-month (1-based):\n", !IO),
+ list.foldl(test_int_to_month, -1..13, !IO),
+ io.nl(!IO),
+ io.write_string("Int-to-month (0-based):\n", !IO),
+ list.foldl(test_int0_to_month, -1..13, !IO),
+ io.nl(!IO),
+ io.write_string("Same date:\n", !IO),
+ Date1 = det_date_from_string("2014-12-04 12:53:00"),
+ Date2 = det_date_from_string("2014-12-04 12:54:00"),
+ Date3 = det_date_from_string("2014-12-05 12:53:00"),
+ test_same_date(Date1, Date1, !IO),
+ test_same_date(Date1, Date2, !IO),
+ test_same_date(Date1, Date3, !IO),
+ test_same_date(Date2, Date3, !IO),
io.nl(!IO).
+
:- pred test_dur_leq(string::in, string::in, io::di, io::uo) is det.
@@ -153,3 +177,57 @@ test_day_of_week(DateStr, !IO) :-
io.write_string(DateStr ++ " : ", !IO),
io.write(day_of_week(det_date_from_string(DateStr)), !IO),
io.nl(!IO).
+
+:- pred test_month_to_int(month::in, io::di, io::uo) is det.
+
+test_month_to_int(Month, !IO) :-
+ Int = month_to_int(Month),
+ io.format("%s -> %d\n", [s(string(Month)), i(Int)], !IO).
+
+:- pred test_month_to_int0(month::in, io::di, io::uo) is det.
+
+test_month_to_int0(Month, !IO) :-
+ Int = month_to_int0(Month),
+ io.format("%s -> %d\n", [s(string(Month)), i(Int)], !IO).
+
+:- pred test_int_to_month(int::in, io::di, io::uo) is det.
+
+test_int_to_month(Int, !IO) :-
+ ( if int_to_month(Int, Month)
+ then Result = string(Month)
+ else Result = "out-of-range"
+ ),
+ io.format("%d -> %s\n", [i(Int), s(Result)], !IO).
+
+:- pred test_int0_to_month(int::in, io::di, io::uo) is det.
+
+test_int0_to_month(Int, !IO) :-
+ ( if int0_to_month(Int, Month)
+ then Result = string(Month)
+ else Result = "out-of-range"
+ ),
+ io.format("%d -> %s\n", [i(Int), s(Result)], !IO).
+
+:- pred test_same_date(date::in, date::in, io::di, io::uo) is det.
+
+test_same_date(A, B, !IO) :-
+ Result = ( if A `same_date` B then "==" else "!=" ),
+ io.format("%s %s %s\n",
+ [s(date_to_string(A)), s(Result), s(date_to_string(B))], !IO).
+
+:- func all_months = list(month).
+
+all_months = [
+ january,
+ february,
+ march,
+ april,
+ may,
+ june,
+ july,
+ august,
+ september,
+ october,
+ november,
+ december
+].
More information about the reviews
mailing list