[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