Next: , Previous: builtin, Up: Top


14 calendar

     %--------------------------------------------------%
     % vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
     %--------------------------------------------------%
     % Copyright (C) 2009-2010 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.
     %
     % The Gregorian calendar is the calendar that is currently used by most of
     % the world. In this calendar a year is a leap year if it is divisible by
     % 4, but not divisible by 100. The only exception is if the year is divisible
     % by 400, in which case it is a leap year. For example 1900 is not leap year,
     % while 2000 is. The proleptic Gregorian calendar is an extension of the
     % Gregorian calendar backward in time to before it was first introduced in
     % 1582.
     %
     %--------------------------------------------------%
     %--------------------------------------------------%
     
     :- module calendar.
     :- interface.
     
     :- import_module io.
     
     %--------------------------------------------------%
     
         % A point on the Proleptic Gregorian calendar, to the nearest microsecond.
         %
     :- type date.
     
         % 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 microsecond == int.  % 0..999999
     
     :- type month
         --->    january
         ;       february
         ;       march
         ;       april
         ;       may
         ;       june
         ;       july
         ;       august
         ;       september
         ;       october
         ;       november
         ;       december.
     
     :- type day_of_week
         --->    monday
         ;       tuesday
         ;       wednesday
         ;       thursday
         ;       friday
         ;       saturday
         ;       sunday.
     
         % 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.
     :- func microsecond(date) = microsecond.
     
         % 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,
         minute::in, second::in, microsecond::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,
         microsecond) = date.
     
         % Retrieve all the components of a date.
         %
     :- pred unpack_date(date::in,
         year::out, month::out, day_of_month::out, hour::out, minute::out,
         second::out, microsecond::out) is det.
     
         % Convert a string of the form "YYYY-MM-DD HH:MM:SS.mmmmmm" to a date.
         % The microseconds component (.mmmmmm) is optional.
         %
     :- 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:MM:SS.mmmmmm".
         % If the microseconds component of the date is zero, then the
         % ".mmmmmm" part is omitted.
         %
     :- func date_to_string(date) = 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.
     
         % Calculate the Julian day number for a date on the Gregorian calendar.
         %
     :- func julian_day_number(date) = int.
     
         % Returns 1970/01/01 00:00:00.
         %
     :- func unix_epoch = date.
     
         % 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.
         %
     :- type duration.
     
         % Duration components.
         %
     :- type years == int.
     :- type months == int.
     :- type days == int.
     :- type hours == int.
     :- type minutes == int.
     :- type seconds == int.
     :- type microseconds == int.
     
         % 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.
     :- func microseconds(duration) = microseconds.
     
         % init_duration(Years, Months, Days, Hours, Minutes,
         %   Seconds, MicroSeconds) = Duration.
         % Create a new duration. All of the components should either be
         % non-negative or non-positive (they can all be zero).
         %
     :- func init_duration(years, months, days, hours, minutes, seconds,
         microseconds) = duration.
     
         % Retrieve all the components of a duration.
         %
     :- pred unpack_duration(duration::in, years::out, months::out,
         days::out, hours::out, minutes::out, seconds::out, microseconds::out)
         is det.
     
         % Return the zero length duration.
         %
     :- func zero_duration = duration.
     
         % Negate a duration.
         %
     :- func negate(duration) = duration.
     
         % Parse a duration string.
         %
         % The string should be of the form "PnYnMnDTnHnMnS" where each "n" is a
         % non-negative integer representing the number of years (Y), months (M),
         % days (D), hours (H), minutes (M) or seconds (S). The duration string
         % always starts with 'P' and the 'T' separates the date and time components
         % of the duration. A component may be omitted if it is zero, and the 'T'
         % separator is not required if all the time components are zero.
         % The second component may include a fraction component using a period.
         % This fraction component should not have a resolution higher than a
         % microsecond.
         %
         % For example the duration 1 year, 18 months, 100 days, 10 hours, 15
         % minutes 90 seconds and 300 microseconds can be written as:
         %   P1Y18M100DT10H15M90.0003S
         % while the duration 1 month and 2 days can be written as:
         %   P1M2D
         %
         % Note that internally the duration is represented using only months,
         % days, seconds and microseconds, so that
         % duration_to_string(det_duration_from_string("P1Y18M100DT10H15M90.0003S"))
         % will result in the string "P2Y6M100DT10H16M30.0003S".
         %
     :- pred duration_from_string(string::in, duration::out) is semidet.
     
         % Same as above, but aborts if the duration string is invalid.
         %
     :- func det_duration_from_string(string) = duration.
     
         % Convert a duration to a string using the same representation
         % parsed by duration_from_string.
         %
     :- func duration_to_string(duration) = string.
     
         % 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 month and year components of the date to change again.
         %
     :- pred add_duration(duration::in, date::in, date::out) is det.
     
         % This predicate implements a partial order relation on durations.
         % DurationA is less than or equal to DurationB iff for all of the
         % dates list below, adding DurationA to the date results in a date
         % less than or equal to the date obtained by adding DurationB.
         %
         %    1696-09-01 00:00:00
         %    1697-02-01 00:00:00
         %    1903-03-01 00:00:00
         %    1903-07-01 00:00:00
         %
         % There is only a partial order on durations, because some durations
         % cannot be said to be less than, equal to or greater than another duration
         % (e.g. 1 month vs. 30 days).
         %
     :- pred duration_leq(duration::in, duration::in) is semidet.
     
         % Get the difference between local 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 occurred at the same instant.
         %
         % To convert UTC time to local time, add the result of local_time_offset/3
         % to UTC (using add_duration/3). To compute UTC given the local time,
         % first negate the result of local_time_offset/3 (using negate/1) and then
         % add it to the local time.
         %
     :- pred local_time_offset(duration::out, io::di, io::uo) is det.
     
         % duration(DateA, DateB) = Duration.
         % 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, microseconds.
         % The returned duration is positive if DateB is after DateA and negative
         % if DateB is before DateA.
         % Any leap seconds that occurred between the two dates are ignored.
         % The dates should be in the same timezone and in the same daylight
         % savings phase. To work out the duration between dates in different
         % timezones or daylight savings phases, first convert the dates to UTC.
         %
         % If the seconds components of DateA and DateB are < 60 then
         % add_duration(DateA, duration(DateA, DateB), DateB) will hold, but
         % add_duration(DateB, negate(duration(DateA, DateB)), DateA) may not hold.
         % For example if:
         %   DateA = 2001-01-31
         %   DateB = 2001-02-28
         %   Duration = 1 month
         % then the following holds:
         %   add_duration(duration(DateA, DateB), DateA, DateB)
         % but the following does not:
         %   add_duration(negate(duration(DateA, DateB), DateB, DateA)
         % (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, seconds and microseconds only.
         %
     :- func day_duration(date, date) = duration.
     
     %--------------------------------------------------%
     %
     % Folds over ranges of dates.
     %
     
         % foldl_days(Pred, Start, End, !Acc):
         % Calls Pred for each day in the range of dates from Start to End
         % with an accumulator.
         % Each date in the range is generated by adding a duration of one day
         % to the previous date using the add_duration/3 predicate.
         % Consequently, the time components of the dates in the range may
         % differ if the time components of the given start and end times
         % include leap seconds.
         %
     :- pred foldl_days(pred(date, A, A), date, date, A, A).
     :- mode foldl_days(pred(in, in, out) is det, in, in, in, out) is det.
     :- mode foldl_days(pred(in, mdi, muo) is det, in, in, mdi, muo) is det.
     :- mode foldl_days(pred(in, di, uo) is det, in, in, di, uo) is det.
     :- mode foldl_days(pred(in, in, out) is semidet, in, in, in, out) is semidet.
     :- mode foldl_days(pred(in, mdi, muo) is semidet, in, in, mdi, muo) is semidet.
     :- mode foldl_days(pred(in, di, uo) is semidet, in, in, di, uo) is semidet.
     
         % foldl2_days(Pred, Start, End, !Acc1, !Acc2):
         % As above, but with two accumulators.
         %
     :- pred foldl2_days(pred(date, A, A, B, B), date, date, A, A, B, B).
     :- mode foldl2_days(pred(in, in, out, in, out) is det, in, in, in, out,
         in, out) is det.
     :- mode foldl2_days(pred(in, in, out, mdi, muo) is det, in, in, in, out,
         mdi, muo) is det.
     :- mode foldl2_days(pred(in, in, out, di, uo) is det, in, in, in, out,
         di, uo) is det.
     :- mode foldl2_days(pred(in, in, out, in, out) is semidet, in, in, in, out,
         in, out) is semidet.
     :- mode foldl2_days(pred(in, in, out, mdi, muo) is semidet, in, in, in, out,
         mdi, muo) is semidet.
     :- mode foldl2_days(pred(in, in, out, di, uo) is semidet, in, in, in, out,
         di, uo) is semidet.
     
         % foldl3_days(Pred, Start, End, !Acc1, !Acc2, !Acc3):
         % As above, but with three accumulators.
         %
     :- pred foldl3_days(pred(date, A, A, B, B, C, C), date, date,
         A, A, B, B, C, C).
     :- mode foldl3_days(pred(in, in, out, in, out, in, out) is det, in, in,
         in, out, in, out, in, out) is det.
     :- mode foldl3_days(pred(in, in, out, in, out, mdi, muo) is det, in, in,
         in, out, in, out, mdi, muo) is det.
     :- mode foldl3_days(pred(in, in, out, in, out, di, uo) is det, in, in,
         in, out, in, out, di, uo) is det.
     :- mode foldl3_days(pred(in, in, out, in, out, in, out) is semidet, in, in,
         in, out, in, out, in, out) is semidet.
     :- mode foldl3_days(pred(in, in, out, in, out, mdi, muo) is semidet, in, in,
         in, out, in, out, mdi, muo) is semidet.
     :- mode foldl3_days(pred(in, in, out, in, out, di, uo) is semidet, in, in,
         in, out, in, out, di, uo) is semidet.
     
     %--------------------------------------------------%
     %--------------------------------------------------%