[m-rev.] calendar module proposal

Julien Fischer juliensf at csse.unimelb.edu.au
Wed Jan 28 17:30:01 AEDT 2009


On Tue, 27 Jan 2009, Ian MacLarty wrote:

> Index: library/calendar.m
> ===================================================================
> RCS file: library/calendar.m
> diff -N library/calendar.m
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ library/calendar.m	27 Jan 2009 03:51:25 -0000
> @@ -0,0 +1,961 @@
> +%-----------------------------------------------------------------------------%
> +% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
> +%-----------------------------------------------------------------------------%
> +% Copyright (C) 2009 The University of Melbourne.
> +% This file may only be copied under the terms of the GNU Library General
> +% Public License - see the file COPYING.LIB in the Mercury distribution.
> +%-----------------------------------------------------------------------------%
> +%
> +% File: calendar.m.
> +% Main authors: maclarty
> +% Stability: low.
> +%
> +% Proleptic Gregorian calendar utilities.

A little more explanation is warranted here.  I didn't know what the
proleptic Gregorian calendar was without looking it up.  I suspect most
users will be in the same boat.

> +%-----------------------------------------------------------------------------%
> +
> +:- module calendar.
> +:- interface.
> +
> +:- import_module io.
> +
> +%-----------------------------------------------------------------------------%
> +
> +    % A point on the Proleptic Gregorian calendar, to the nearest second.
> +    %
> +:- type date.
> +
> +    % A period of time measured in years, months, days, hours, minutes and
> +    % seconds.
> +    %
> +:- type duration.
> +
> +:- type month
> +    --->    january
> +    ;       february
> +    ;       march
> +    ;       april
> +    ;       may
> +    ;       june
> +    ;       july
> +    ;       august
> +    ;       september
> +    ;       october
> +    ;       november
> +    ;       december.
> +
> +
> +    % Date components.
> +    %
> +:- type year == int.         % Year 0 is 1 BC, -1 is 2 BC, etc.
> +:- 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 day_of_week
> +    --->    sunday
> +    ;       monday
> +    ;       tuesday
> +    ;       wednesday
> +    ;       thursday
> +    ;       friday
> +    ;       saturday.
> +
> +    % Duration components.
> +    %
> +:- type years == int.
> +:- type months == int.
> +:- type days == int.
> +:- type hours == int.
> +:- type minutes == int.
> +:- type seconds == int.
> +
> +    % Functions to retrieve the components of a date.
> +    %
> +:- func year(date) = year.
> +:- func month(date) = month.
> +:- func day_of_month(date) = day_of_month.
> +:- func day_of_week(date) = day_of_week.
> +:- func hour(date) = hour.
> +:- func minute(date) = minute.
> +:- func second(date) = second.
> +
> +    % init_date(Year, Month, Day, Hour, Minute, Second) = DT.
> +    % Initialize a new date.  Fails if the given date is invalid.
> +    %
> +:- pred init_date(year::in, month::in, day_of_month::in, hour::in,
> +    minute::in, second::in, date::out) is semidet.
> +
> +    % Same as above, but aborts if the date is invalid.
> +    %
> +:- func det_init_date(year, month, day_of_month, hour, minute, second) =
> +    date.
> +
> +    % Convert a string of the form "YYYY-MM-DD HH:MI:SS" to a date.
> +    %
> +:- pred date_from_string(string::in, date::out) is semidet.
> +
> +    % Same as above, but aborts if the string is not a valid date.
> +    %
> +:- func det_date_from_string(string) = date.
> +
> +    % Convert a date to a string of the form "YYYY-MM-DD HH:MI:SS".
> +    %
> +:- func date_to_string(date) = string.
> +
> +    % Parse a duration string conforming to the representation
> +    % described at http://www.w3.org/TR/xmlschema-2/#duration.
> +    %
> +:- pred duration_from_string(string::in, duration::out) is semidet.
> +
> +    % Same as above, but aborts if the string does not represent
> +    % a valid duration.
> +    %
> +:- func det_duration_from_string(string) = duration.
> +
> +    % Convert a duration to the string representation
> +    % described at http://www.w3.org/TR/xmlschema-2/#duration.
> +    %
> +:- func duration_to_string(duration) = string.
> +
> +    % Get the current local time.
> +    %
> +:- pred current_local_time(date::out, io::di, io::uo) is det.
> +
> +    % Get the current UTC time.
> +    %
> +:- pred current_utc_time(date::out, io::di, io::uo) is det.
> +
> +    % Get the difference between the local time and utc time
> +    % as a duration.
> +    % local_time_offset(TZ, !IO) is equivalent to:
> +    %   current_local_time(Local, !IO),
> +    %   current_utc_time(UTC, !IO),
> +    %   TZ = duration(UTC, Local)
> +    % except that it is as if the calls to current_utc_time and
> +    % current_local_time occured at the same instant.
> +    %
> +:- pred local_time_offset(duration::out, io::di, io::uo) is det.
> +
> +    % Functions to retrieve duration components.
> +    %
> +:- func years(duration) = years.
> +:- func months(duration) = months.
> +:- func days(duration) = days.
> +:- func hours(duration) = hours.
> +:- func minutes(duration) = minutes.
> +:- func seconds(duration) = seconds.
> +
> +    % duration(Date1, Date2) = Duration.

I would refer to "DateA" and "DateB" rather than using numbers, since
the latter frequently implies a sequence of values in Mercury.
(It's not so bad in documentation but it is quite confusing in some of
the code in this module.)


> +    % Find the duration between two dates using a "greedy" algorithm.  The
> +    % algorithm is greedy in the sense that it will try to maximise each
> +    % component in the returned duration in the following order: years, months,
> +    % days, hours, minutes, seconds.
> +    % The returned duration is positive if Date2 is after Date1 and negative
> +    % if Date2 is before Date1.
> +    % Any leap seconds that occured between the two dates are ignored.
> +    %
> +    % If the seconds components of Date1 and Date2 are < 60 then
> +    % add_duration(Date1, duration(Date1, Date2), Date2) will hold, but
> +    % add_duration(Date2, negate(duration(Date1, Date2)), Date1) may not
> +    % hold.  For example if:
> +    %   Date1 = 2001-01-31
> +    %   Date2 = 2001-02-28
> +    %   Duration = 1 month
> +    % then the following holds:
> +    %   add_duration(duration(Date1, Date2), Date1, Date2)
> +    % but the following does not:
> +    %   add_duration(negate(duration(Date1, Date2), Date2, Date1)
> +    % (Adding -1 month to 2001-02-28 will yield 2001-01-28).
> +    %
> +:- func duration(date, date) = duration.
> +
> +    % Same as above, except that the year and month components of the
> +    % returned duration will always be zero.  The duration will be
> +    % in terms of days, hours, minutes and seconds only.
> +    %
> +:- func day_duration(date, date) = duration.
> +
> +    % Add a duration to a date.
> +    % First the years and months are added to the date.
> +    % If this causes the day to be out of range (e.g. April 31), then it is
> +    % decreased until it is in range (e.g. April 30).  Next the remaining
> +    % days, hours, minutes and seconds components are added.  These could
> +    % in turn cause the months and years values to change again.
> +    % The algorithm used is described in detail at
> +    % http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes.
> +    %

The link to the detailed description of the algorithm should be in
the implementation section.  The comment here should provide sufficient
detail about what it does, so that users shouldn't have to look
elsewhere.  (Remember that this the bit that is included in the library
reference guide.)

> +:- pred add_duration(duration::in, date::in, date::out) is det.
> +
> +    % This predicate implements a partial order relation on durations.
> +    % The algorithm it uses is described at
> +    % http://www.w3.org/TR/xmlschema-2/#duration.
> +    % Note that if duration_leq(X, Y) fails, then this does NOT imply
> +    % that duration_leq(Y, X) is true.  For example a duration of 30 days
> +    % is not comparable to a duration of 1 month, so duration_leq will
> +    % fail if these are given as inputs in any order.
> +    %
> +:- pred duration_leq(duration::in, duration::in) is semidet.
> +
> +    % init_{positive|negative}_duration(Years, Months, Days, Hours, Minutes,
> +    %   Seconds)
> +    % Create a new positive or negative duration.  All the supplied dimensions
> +    % should be non-negative. If they are not the function aborts.
> +    %
> +:- func init_positive_duration(years, months, days, hours, minutes, seconds) =
> +    duration.
> +:- func init_negative_duration(years, months, days, hours, minutes, seconds) =
> +    duration.
> +
> +%----------------------------------------------------------------------------%

Use a double dashed lines in front of the implementation declaration.

> +
> +:- implementation.
> +
> +:- import_module char.
> +:- import_module float.
> +:- import_module int.
> +:- import_module list.
> +:- import_module require.
> +:- import_module string.
> +:- import_module time.
> +

...

> +:- pred is_digit(char::in, int::out) is semidet.
> +
> +is_digit('0', 0).
> +is_digit('1', 1).
> +is_digit('2', 2).
> +is_digit('3', 3).
> +is_digit('4', 4).
> +is_digit('5', 5).
> +is_digit('6', 6).
> +is_digit('7', 7).
> +is_digit('8', 8).
> +is_digit('9', 9).

The char module in the stdlib already provides this functionality.
(Reverse mode of char.int_to_digit/2).

Julien.
--------------------------------------------------------------------------
mercury-reviews mailing list
Post messages to:       mercury-reviews at csse.unimelb.edu.au
Administrative Queries: owner-mercury-reviews at csse.unimelb.edu.au
Subscriptions:          mercury-reviews-request at csse.unimelb.edu.au
--------------------------------------------------------------------------



More information about the reviews mailing list