
(********************************************************************)
(*                                                                  *)
(*  time.s7i      Time library                                      *)
(*  Copyright (C) 1991, 1992, 1993, 1994, 2005  Thomas Mertes       *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


const type: time is new object struct
    var integer: year is 0;
    var integer: month is 1;
    var integer: day is 1;
    var integer: hour is 0;
    var integer: minute is 0;
    var integer: second is 0;
    var integer: micro_second is 0;
    var integer: timeZone is 0;
    var boolean: daylightSavingTime is FALSE;
  end struct;


const proc: GET_TIME (inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout boolean param) is action "TIM_NOW";

const proc: AWAIT_TIME (ref integer param, ref integer param, ref integer param,
    ref integer param, ref integer param, ref integer param, ref integer param,
    ref integer param) is action "TIM_AWAIT";

const proc: GET_ATIME (in string param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout boolean param) is action "CMD_GET_ATIME";

const proc: GET_CTIME (in string param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout boolean param) is action "CMD_GET_CTIME";

const proc: GET_MTIME (in string param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout boolean param) is action "CMD_GET_MTIME";

const proc: SET_ATIME (in string param, in integer param, in integer param,
    in integer param, in integer param, in integer param, in integer param,
    in integer param, in integer param) is action "CMD_SET_ATIME";

const proc: SET_MTIME (in string param, in integer param, in integer param,
    in integer param, in integer param, in integer param, in integer param,
    in integer param, in integer param) is action "CMD_SET_MTIME";

const proc: FROM_TIMESTAMP (in integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout boolean param) is action "TIM_FROM_TIMESTAMP";

const proc: SET_LOCAL_TZ (in integer param, in integer param, in integer param,
    in integer param, in integer param, in integer param,
    inout integer param, inout boolean param) is action "TIM_SET_LOCAL_TZ";


(**
 *  Return TRUE if a given year is a leap year in the Gregorian
 *  calendar, FALSE otherwise.
 *)
const func boolean: isLeapYear (in integer: year) is
  return (year rem 4 = 0 and year rem 100 <> 0) or year rem 400 = 0;


(**
 *  Return the number of days in the 'year'.
 *)
const func integer: daysInYear (in integer: year) is func
  result
    var integer: days is 0;
  begin
    if isLeapYear(year) then
      days := 366;
    else
      days := 365;
    end if;
  end func;


(**
 *  Return the number of days in the 'month' of the given 'year'.
 *)
const func integer: daysInMonth (in integer: year, in integer: month) is func
  result
    var integer: leng is 0;
  local
    const set of integer: monthsOfLength31 is {1, 3, 5, 7, 8, 10, 12};
  begin
    if month in monthsOfLength31 then
      leng := 31;
    else
      if month = 2 then
        if isLeapYear(year) then
          leng := 29;
        else
          leng := 28;
        end if;
      else
        leng := 30;
      end if;
    end if;
  end func;


(**
 *  Return time as string with ISO 8601 YYYY-MM-DD date format.
 *)
const func string: strDate (in time: aTime) is
  return aTime.year  lpad0 4 <& "-" <&
         aTime.month lpad0 2 <& "-" <&
         aTime.day   lpad0 2;


(**
 *  Return time as string with ISO 8601 hh:mm:ss time format.
 *)
const func string: strTime (in time: aTime) is func
  result
    var string: result is "";
  begin
    result := aTime.hour   lpad0 2 <& ":" <&
              aTime.minute lpad0 2 <& ":" <&
              aTime.second lpad0 2;
    if aTime.micro_second <> 0 then
      result &:= "." <& aTime.micro_second lpad0 6;
    end if;
  end func;


const func string: strTimeZone (in time: aTime) is func
  result
    var string: result is "UTC";
  begin
    if aTime.timeZone <> 0 then
      if aTime.timeZone > 0 then
        result &:= "+";
      end if;
      result &:= str(aTime.timeZone div 60);
      if aTime.timeZone rem 60 <> 0 then
        result &:= ":" & str(abs(aTime.timeZone) rem 60);
      end if;
    end if;
    if aTime.daylightSavingTime then
      result &:= " (DST)";
    end if;
  end func;


const func string: str_yyyy_mm_dd (in time: aTime, in string: delimiter) is
  return aTime.year  lpad0 4 <& delimiter <&
         aTime.month lpad0 2 <& delimiter <&
         aTime.day   lpad0 2;


const func string: str_mm_dd_yy (in time: aTime, in string: delimiter) is
  return aTime.month        lpad0 2 <& delimiter <&
         aTime.day          lpad0 2 <& delimiter <&
         aTime.year rem 100 lpad0 2;


const func string: str_mm_dd_yyyy (in time: aTime, in string: delimiter) is
  return aTime.month lpad0 2 <& delimiter <&
         aTime.day   lpad0 2 <& delimiter <&
         aTime.year  lpad0 4;


const func string: str_dd_mm_yyyy (in time: aTime, in string: delimiter) is
  return aTime.day   lpad0 2 <& delimiter <&
         aTime.month lpad0 2 <& delimiter <&
         aTime.year  lpad0 4;


const func string: str_d_m_yy (in time: aTime, in string: delimiter) is
  return aTime.day   <& delimiter <&
         aTime.month <& delimiter <&
         aTime.year  rem 100 lpad0 2;


const func string: str_d_m_yyyy (in time: aTime, in string: delimiter) is
  return aTime.day   <& delimiter <&
         aTime.month <& delimiter <&
         aTime.year  lpad0 4;


const func string: str_hh_mm (in time: aTime, in string: delimiter) is
  return aTime.hour   lpad0 2 <& delimiter <&
         aTime.minute lpad0 2;


(**
 *  Return the conversion of a time to a string.
 *)
const func string: str (in time: aTime) is
  return strDate(aTime) <& " " <&
         strTime(aTime) <& " " <&
         strTimeZone(aTime);


(**
 *  Return the conversion of a string in the ISO 8601 date format to
 *  a time.
 *  The accepted ISO 8601 date formats are YYYY-MM, YYYY-MM-DD,
 *  YYYY-MM-DDTHH, YYYY-MM-DDTHH:MM, YYYY-MM-DDTHH:MM:SS
 *  Additionally a space is also allowed to seperate the date and the
 *  time representations: YYYY-MM-DD HH, YYYY-MM-DD HH:MM,
 *  YYYY-MM-DD HH:MM:SS
 *)
const func time: (attr time) parse (in var string: stri) is func
  result
    var time: result is time.value;
  begin
    result.year := integer parse getint(stri);
    if stri[1] <> '-' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.month := integer parse getint(stri);
    if stri <> "" then
      if stri[1] <> '-' then
        raise RANGE_ERROR;
      end if;
      stri := stri[2 ..];
      result.day := integer parse getint(stri);
      if stri <> "" then
        if stri[1] <> ' ' and stri[1] <> 'T' then
          raise RANGE_ERROR;
        end if;
        stri := stri[2 ..];
        result.hour := integer parse getint(stri);
        if stri <> "" then
          if stri[1] <> ':' then
            raise RANGE_ERROR;
          end if;
          stri := stri[2 ..];
          result.minute := integer parse getint(stri);
          if stri <> "" then
            if stri[1] <> ':' then
              raise RANGE_ERROR;
            end if;
            stri := stri[2 ..];
            result.second := integer parse getint(stri);
            if stri <> "" then
              raise RANGE_ERROR;
            end if;
          end if;
        end if;
      end if;
    end if;
    result.micro_second := 0;
    result.timeZone := 0;
    result.daylightSavingTime := FALSE;
    if result.month < 1 or result.month > 12 or
        result.day < 1 or result.day > daysInMonth(result.year, result.month) or
        result.hour < 0 or result.hour > 23 or
        result.minute < 0 or result.minute > 59 or
        result.second < 0 or result.second > 59 then
      raise RANGE_ERROR;
    end if;
  end func;


enable_io(time);


const func boolean: (in time: tim1) = (in time: tim2) is
  return
    tim1.year = tim2.year and
    tim1.month = tim2.month and
    tim1.day = tim2.day and
    tim1.hour = tim2.hour and
    tim1.minute = tim2.minute and
    tim1.second = tim2.second and
    tim1.micro_second = tim2.micro_second;


const func boolean: (in time: tim1) <> (in time: tim2) is
  return
    tim1.year <> tim2.year or
    tim1.month <> tim2.month or
    tim1.day <> tim2.day or
    tim1.hour <> tim2.hour or
    tim1.minute <> tim2.minute or
    tim1.second <> tim2.second or
    tim1.micro_second <> tim2.micro_second;


const func boolean: (in time: tim1) >= (in time: tim2) is func
  result
    var boolean: result is FALSE;
  begin
    if tim1.year > tim2.year then
      result := TRUE;
    elsif tim1.year = tim2.year then
      if tim1.month > tim2.month then
        result := TRUE;
      elsif tim1.month = tim2.month then
        if tim1.day > tim2.day then
          result := TRUE;
        elsif tim1.day = tim2.day then
          if tim1.hour > tim2.hour then
            result := TRUE;
          elsif tim1.hour = tim2.hour then
            if tim1.minute > tim2.minute then
              result := TRUE;
            elsif tim1.minute = tim2.minute then
              if tim1.second > tim2.second then
                result := TRUE;
              elsif tim1.second = tim2.second then
                if tim1.micro_second >= tim2.micro_second then
                  result := TRUE;
                end if;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
  end func;


const func boolean: (in time: tim1) <= (in time: tim2) is func
  result
    var boolean: result is FALSE;
  begin
    if tim1.year < tim2.year then
      result := TRUE;
    elsif tim1.year = tim2.year then
      if tim1.month < tim2.month then
        result := TRUE;
      elsif tim1.month = tim2.month then
        if tim1.day < tim2.day then
          result := TRUE;
        elsif tim1.day = tim2.day then
          if tim1.hour < tim2.hour then
            result := TRUE;
          elsif tim1.hour = tim2.hour then
            if tim1.minute < tim2.minute then
              result := TRUE;
            elsif tim1.minute = tim2.minute then
              if tim1.second < tim2.second then
                result := TRUE;
              elsif tim1.second = tim2.second then
                if tim1.micro_second <= tim2.micro_second then
                  result := TRUE;
                end if;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
  end func;


const func boolean: (in time: tim1) < (in time: tim2) is
  return not tim1 >= tim2;


const func boolean: (in time: tim1) > (in time: tim2) is
  return not tim1 <= tim2;


(**
 *  Returns -1, 0 or 1 if the first argument is considered to be
 *  respectively less than, equal to, or greater than the second.
 *)
const func integer: compare (in time: tim1, in time: tim2) is func
  result
    var integer: result is 0;
  begin
    if tim1.year < tim2.year then
      result := -1;
    elsif tim1.year > tim2.year then
      result := 1;
    elsif tim1.month < tim2.month then
      result := -1;
    elsif tim1.month > tim2.month then
      result := 1;
    elsif tim1.day < tim2.day then
      result := -1;
    elsif tim1.day > tim2.day then
      result := 1;
    elsif tim1.hour < tim2.hour then
      result := -1;
    elsif tim1.hour > tim2.hour then
      result := 1;
    elsif tim1.minute < tim2.minute then
      result := -1;
    elsif tim1.minute > tim2.minute then
      result := 1;
    elsif tim1.second < tim2.second then
      result := -1;
    elsif tim1.second > tim2.second then
      result := 1;
    elsif tim1.micro_second < tim2.micro_second then
      result := -1;
    elsif tim1.micro_second > tim2.micro_second then
      result := 1;
    end if;
  end func;


(**
 *  Return a hash value for 'aTime'.
 *)
const func integer: hashCode (in time: aTime) is
  return aTime.year + aTime.month + aTime.day +
      aTime.hour + aTime.minute + aTime.second + aTime.micro_second;


(**
 *  Returns the time 'aTime' truncated to the beginning of a second.
 *)
const func time: truncToSecond (in time: aTime) is func
  result
    var time: result is time.value;
  begin
    result := aTime;
    result.micro_second := 0;
  end func;


(**
 *  Returns the time 'aTime' truncated to the beginning of a minute.
 *)
const func time: truncToMinute (in time: aTime) is func
  result
    var time: result is time.value;
  begin
    result := aTime;
    result.second := 0;
    result.micro_second := 0;
  end func;


(**
 *  Returns the time 'aTime' truncated to the beginning of a hour.
 *)
const func time: truncToHour (in time: aTime) is func
  result
    var time: result is time.value;
  begin
    result := aTime;
    result.minute := 0;
    result.second := 0;
    result.micro_second := 0;
  end func;


(**
 *  Returns the time 'aTime' truncated to the beginning of a day.
 *)
const func time: truncToDay (in time: aTime) is func
  result
    var time: result is time.value;
  begin
    result := aTime;
    result.hour := 0;
    result.minute := 0;
    result.second := 0;
    result.micro_second := 0;
  end func;


(**
 *  Returns the time 'aTime' truncated to the beginning of the first
 *  day in the month.
 *)
const func time: truncToMonth (in time: aTime) is func
  result
    var time: result is time.value;
  begin
    result := aTime;
    result.day := 1;
    result.hour := 0;
    result.minute := 0;
    result.second := 0;
    result.micro_second := 0;
  end func;


(**
 *  Returns the time 'aTime' truncated to the beginning of the first
 *  day in the year.
 *)
const func time: truncToYear (in time: aTime) is func
  result
    var time: result is time.value;
  begin
    result := aTime;
    result.month := 1;
    result.day := 1;
    result.hour := 0;
    result.minute := 0;
    result.second := 0;
    result.micro_second := 0;
  end func;


(**
 *  Return the weekday number of 'aDate'.
 *  @return 1 for monday, 2 for tuesday, and so on up to 7 for sunday.
 *)
const func integer: dayOfWeek (in time: aDate) is func
  result
    var integer: result is 0;
  local
    var integer: year is 0;
    var integer: month is 0;
  begin
    year := aDate.year;
    month := aDate.month;
    if month <= 2 then
      decr(year);
      month +:= 12;
    end if;
    result := succ(pred(year + year mdiv 4 - year mdiv 100 + year mdiv 400 +
        (31 * (month - 2)) mdiv 12 + aDate.day) mod 7);
  end func;


(**
 *  Return the day number in the year of 'aDate'.
 *  @return 1 for the 1. of january and successive values up to 366.
 *)
const func integer: dayOfYear (in time: aDate) is func
  result
    var integer: result is 0;
  begin
    if isLeapYear(aDate.year) then
      result := [](0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)[aDate.month] + aDate.day;
    else
      result := [](0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)[aDate.month] + aDate.day;
    end if;
  end func;


(**
 *  Return the week number of the 'year' for the 'dayOfYear'.
 *  According to ISO 8601: Week number 1 of every year contains the
 *  4. of january. Since 1st to 3rd of january might be in the
 *  previous week there can be also a week number 0.
 *  @return week numbers from 0 to 53 for weeks belonging to the year
 *          of the given date.
 *)
const func integer: weekOfYear (in var integer: year, in integer: dayOfYear) is func
  result
    var integer: result is 0;
  local
    var integer: weekDayOfJan4 is 0;
  begin
    year := pred(year);
    weekDayOfJan4 := succ(pred(year + year mdiv 4 - year mdiv 100 + year mdiv 400 + 32) mod 7);
    result := (dayOfYear + weekDayOfJan4 - 5) mdiv 7 + 1;
  end func;


(**
 *  Return the week number of the year for 'aDate'.
 *  According to ISO 8601: Week number 1 of every year contains the
 *  4. of january. Since 1st to 3rd of january might be in the
 *  previous week there can be also a week number 0.
 *  @return week numbers from 0 to 53 for weeks belonging to the year
 *          of the given date.
 *)
const func integer: weekOfYear (in time: aDate) is
  return weekOfYear(aDate.year, dayOfYear(aDate));


(**
 *  Return the year of the ISO 8601 week date for 'aDate'.
 *  At the beginning and the end of an Gregorian calendar year there
 *  might be days which belong to a week of the previous or next year.
 *  For example 2005-01-01 is 2004-W53-6 and 2007-12-31 is 2008-W01-1.
 *  @return the year in the range 'pred(aDate.year)' to
 *          'succ(aDate.year)'.
 *)
const func integer: weekDateYear (in time: aDate) is func
  result
    var integer: weekDateYear is 0;
  local
    var integer: weekNum is 0;
  begin
    weekNum := weekOfYear(aDate.year, dayOfYear(aDate));
    if weekNum <= 0 then
      weekDateYear := pred(aDate.year);
    elsif weekNum >= 53 and aDate.day >= 29 and
        weekOfYear(succ(aDate.year), 31 - aDate.day) = 1 then
      weekDateYear := succ(aDate.year);
    else
      weekDateYear := aDate.year;
    end if;
  end func;


(**
 *  Return the week number of the ISO 8601 week date for 'aDate'.
 *  At the beginning and the end of an Gregorian calendar year there
 *  might be days which belong to a week of the previous or next year.
 *  For example 2005-01-01 is 2004-W53-6 and 2007-12-31 is 2008-W01-1.
 *  @return the week number in the range 1 to 53.
 *)
const func integer: weekDateWeek (in time: aDate) is func
  result
    var integer: weekNum is 0;
  begin
    weekNum := weekOfYear(aDate.year, dayOfYear(aDate));
    if weekNum <= 0 then
      weekNum := weekOfYear(pred(aDate.year), 366);
    elsif weekNum >= 53 and aDate.day >= 29 and
        weekOfYear(succ(aDate.year), 31 - aDate.day) = 1 then
      weekNum := 1;
    end if;
  end func;


const proc: NORMALIZE (inout time: tim) is func
  local
    var integer: month_length is 0;
  begin
    tim.second := tim.second + tim.micro_second mdiv 1000000;
    tim.micro_second := tim.micro_second mod 1000000;
    tim.minute := tim.minute + tim.second mdiv 60;
    tim.second := tim.second mod 60;
    tim.hour := tim.hour + tim.minute mdiv 60;
    tim.minute := tim.minute mod 60;
    tim.day := tim.day + tim.hour mdiv 24;
    tim.hour := tim.hour mod 24;
    tim.year := tim.year + pred(tim.month) mdiv 12;
    tim.month := succ(pred(tim.month) mod 12);
    month_length := daysInMonth(tim.year, tim.month);
    while tim.day > month_length do
      tim.day := tim.day - month_length;
      if tim.month < 12 then
        incr(tim.month);
      else
        tim.month := 1;
        incr(tim.year);
      end if;
      month_length := daysInMonth(tim.year, tim.month);
    end while;
    while tim.day < 1 do
      if tim.month > 1 then
        decr(tim.month);
      else
        tim.month := 12;
        decr(tim.year);
      end if;
      tim.day := tim.day + daysInMonth(tim.year, tim.month);
    end while;
  end func;


(**
 *  Convert a time to Coordinated Universal Time (UTC).
 *)
const func time: toUTC (in time: tim) is func
  result
    var time: result is time.value;
  begin
    result := tim;
    result.minute -:= tim.timeZone;
    result.timeZone := 0;
    NORMALIZE(result);
  end func;


(**
 *  Sets timeZone and daylightSavingTime for a given time.
 *)
const func time: setLocalTZ (in time: aTime) is func
  result
    var time: result is time.value;
  begin
    result := aTime;
    SET_LOCAL_TZ(result.year, result.month, result.day,
        result.hour, result.minute, result.second,
        result.timeZone, result.daylightSavingTime);
  end func;


(**
 *  Return the julian day number of 'aDate'.
 *  The julian day number is the number of days that have elapsed
 *  since January 1, 4713 BC in the proleptic Julian calendar.
 *)
const func integer: julianDayNumber (in time: aDate) is func
  result
    var integer: result is 0;
  local
    var integer: year is 0;
    var integer: month is 0;
  begin
    year := aDate.year;
    month := aDate.month;
    if month <= 2 then
      decr(year);
      month +:= 12;
    end if;
    result := (1461 * (year + 4800)) mdiv 4 +
          (367 * (month - 2)) mdiv 12 -
          (3 * ((year + 4900) mdiv 100)) mdiv 4 +
          aDate.day - 32075;
  end func;


(**
 *  Return the time of a 'julianDayNumber'.
 *  The julian day number is the number of days that have elapsed
 *  since January 1, 4713 BC in the proleptic Julian calendar.
 *  @return the time 0:00:00 at the day with the 'julianDayNumber'.
 *)
const func time: julianDayNumToTime (in integer: julianDayNumber) is func
  result
    var time: result is time.value;
  local
    var integer: l is 0;
    var integer: n is 0;
    var integer: i is 0;
    var integer: j is 0;
  begin
    l := julianDayNumber + 68569;
    n := (4 * l) mdiv 146097;
    l := l - (146097 * n + 3) mdiv 4;
    i := (4000 * (l + 1)) mdiv 1461001;
    l := l - (1461 * i) mdiv 4 + 31;
    j := (80 * l) mdiv 2447;
    result.day := l - (2447 * j) mdiv 80;
    l := j mdiv 11;
    result.month := j + 2 - (12 * l);
    result.year := 100 * (n - 49) + i + l;
  end func;


(**
 *  Return a time expressed in seconds since the Unix Epoch.
 *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
 *)
const func integer: timestamp1970 (in time: aTime) is func
  result
    var integer: result is 0;
  local
    var integer: yearBefore is 0;
  begin
    yearBefore := pred(aTime.year);
    result := (((yearBefore * 365 +
                 yearBefore mdiv 4 -
                 yearBefore mdiv 100 +
                 yearBefore mdiv 400 -
              719162 +
              pred(dayOfYear(aTime))) * 24 +
              aTime.hour) * 60 +
              aTime.minute) * 60 +
              aTime.second -
              aTime.timeZone * 60;
  end func;


(**
 *  Convert a timestamp into a time from the local time zone.
 *)
const func time: timestamp1970ToTime (in integer: timestamp) is func
  result
    var time: result is time.value;
  begin
    FROM_TIMESTAMP(timestamp, result.year, result.month, result.day,
        result.hour, result.minute, result.second,
        result.micro_second, result.timeZone,
        result.daylightSavingTime);
  end func;


(*
(**
 *  Convert a timestamp into Coordinated Universal Time (UTC).
 *)
const func time: timestamp1970ToUTC (in integer: timestamp) is func
  result
    var time: result is time.value;
  local
    const integer: SECS_DAY is 24 * 60 * 60;
    var integer: dayclock is 0;
    var integer: dayno is 0;
    var integer: year is 1970;
  begin
    dayclock := timestamp mod SECS_DAY;
    dayno    := timestamp mdiv SECS_DAY;
    result.second := dayclock rem 60;
    result.minute := (dayclock rem 3600) div 60;
    result.hour   := dayclock div 3600;
    if dayno >= 0 then
      while dayno >= daysInYear(year) do
        dayno -:= daysInYear(year);
        incr(year);
      end while;
      result.month := 1;
      while dayno >= daysInMonth(year, result.month) do
        dayno -:= daysInMonth(year, result.month);
        incr(result.month);
      end while;
      result.day := succ(dayno);
    else
      decr(year);
      dayno := pred(-dayno);
      while dayno >= daysInYear(year) do
        dayno -:= daysInYear(year);
        decr(year);
      end while;
      result.month := 12;
      while dayno >= daysInMonth(year, result.month) do
        dayno -:= daysInMonth(year, result.month);
        decr(result.month);
      end while;
      result.day := daysInMonth(year, result.month) - dayno;
    end if;
    result.year := year;
  end func;
*)


(**
 *  Return the current time.
 *)
const func time: time (NOW) is func
  result
    var time: result is time.value;
  begin
    GET_TIME(result.year, result.month, result.day,
        result.hour, result.minute, result.second,
        result.micro_second, result.timeZone,
        result.daylightSavingTime);
  end func;


(**
 *  Return the specified UTC time.
 *)
const func time: time (in integer: year, in integer: month, in integer: day,
    in integer: hour, in integer: minute, in integer: second) is func
  result
    var time: result is time.value;
  begin
    result.year   := year;
    result.month  := month;
    result.day    := day;
    result.hour   := hour;
    result.minute := minute;
    result.second := second;
  end func;


(**
 *  Return the specified time in the specified timeZone.
 *)
const func time: timeInTimeZone (in integer: year, in integer: month, in integer: day,
    in integer: hour, in integer: minute, in integer: second, in integer: timeZone) is func
  result
    var time: result is time.value;
  begin
    result.year     := year;
    result.month    := month;
    result.day      := day;
    result.hour     := hour;
    result.minute   := minute;
    result.second   := second;
    result.timeZone := timeZone;
  end func;


(**
 *  Return the specified UTC date.
 *)
const func time: date (in integer: year, in integer: month, in integer: day) is func
  result
    var time: result is time.value;
  begin
    result.year  := year;
    result.month := month;
    result.day   := day;
  end func;


(**
 *  Wait until 'aTime' is reached
 *)
const proc: await (ref time: aTime) is func
  begin
    AWAIT_TIME(aTime.year, aTime.month, aTime.day,
        aTime.hour, aTime.minute, aTime.second,
        aTime.micro_second, aTime.timeZone);
  end func;


(**
 *  Return the access time of the file described with 'filePath'.
 *)
const func time: getATime (in string: filePath) is func
  result
    var time: result is time.value;
  begin
    GET_ATIME(filePath, result.year, result.month, result.day,
        result.hour, result.minute, result.second,
        result.micro_second, result.timeZone,
        result.daylightSavingTime);
  end func;


(**
 *  Return the change time of the file described with 'filePath'.
 *)
const func time: getCTime (in string: filePath) is func
  result
    var time: result is time.value;
  begin
    GET_CTIME(filePath, result.year, result.month, result.day,
        result.hour, result.minute, result.second,
        result.micro_second, result.timeZone,
        result.daylightSavingTime);
  end func;


(**
 *  Return the modification time of the file described with 'filePath'.
 *)
const func time: getMTime (in string: filePath) is func
  result
    var time: result is time.value;
  begin
    GET_MTIME(filePath, result.year, result.month, result.day,
        result.hour, result.minute, result.second,
        result.micro_second, result.timeZone,
        result.daylightSavingTime);
  end func;


const proc: setATime (in string: fileName, in time: aTime) is func
  begin
    SET_ATIME(fileName, aTime.year, aTime.month, aTime.day,
        aTime.hour, aTime.minute, aTime.second,
        aTime.micro_second, aTime.timeZone);
  end func;


const proc: setMTime (in string: fileName, in time: aTime) is func
  begin
    SET_MTIME(fileName, aTime.year, aTime.month, aTime.day,
        aTime.hour, aTime.minute, aTime.second,
        aTime.micro_second, aTime.timeZone);
  end func;


const type: duration is new object struct
    var integer: year_365 is 0;
    var integer: year_366 is 0;
    var integer: month_28 is 0;
    var integer: month_29 is 0;
    var integer: month_30 is 0;
    var integer: month_31 is 0;
    var integer: day is 0;
    var integer: hour is 0;
    var integer: minute is 0;
    var integer: second is 0;
    var integer: micro_second is 0;
    var integer: year_correction is 0;
    var integer: month_correction is 0;
    var integer: day_correction is 0;
  end struct;


const func integer: sumOfYearFields (in duration: aDuration) is
  return aDuration.year_365 +
         aDuration.year_366;


const func integer: sumOfMonthFields (in duration: aDuration) is
  return aDuration.month_28 +
         aDuration.month_29 +
         aDuration.month_30 +
         aDuration.month_31;


(**
 *  Obtains the years of a duration.
 *)
const func integer: getYears (in duration: aDuration) is
  return aDuration.year_365 +
         aDuration.year_366 +
         aDuration.year_correction;


(**
 *  Obtains the months of a duration.
 *)
const func integer: getMonths (in duration: aDuration) is
  return aDuration.month_28 +
         aDuration.month_29 +
         aDuration.month_30 +
         aDuration.month_31 +
         aDuration.month_correction;


(**
 *  Obtains the days of a duration.
 *)
const func integer: getDays (in duration: aDuration) is
  return aDuration.day +
         aDuration.day_correction;


(**
 *  Obtains the hours of a duration.
 *)
const func integer: getHours (in duration: aDuration) is
  return aDuration.hour;


(**
 *  Obtains the minutes of a duration.
 *)
const func integer: getMinutes (in duration: aDuration) is
  return aDuration.minute;


(**
 *  Obtains the seconds of a duration.
 *)
const func integer: getSeconds (in duration: aDuration) is
  return aDuration.second;


(**
 *  Obtains the micro seconds of a duration.
 *)
const func integer: getMicroSeconds (in duration: aDuration) is
  return aDuration.micro_second;


(**
 *  Return the conversion of a duration to a string.
 *)
const func string: str (in duration: aDuration) is func
  result
    var string: stri is "";
  begin
    stri := getYears( aDuration)         <& "-" <&
            getMonths(aDuration) lpad0 2 <& "-" <&
            getDays(  aDuration) lpad0 2 <& " " <&
            aDuration.hour       lpad0 2 <& ":" <&
            aDuration.minute     lpad0 2 <& ":" <&
            aDuration.second     lpad0 2;
  end func;


(**
 *  Return the conversion of a string to a duration.
 *)
const func duration: (attr duration) parse (in var string: stri) is func
  result
    var duration: result is duration.value;
  begin
    result.year_365 := integer parse getint(stri);
    if stri[1] <> '-' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.month_30 := integer parse getint(stri);
    if stri[1] <> '-' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.day := integer parse getint(stri);
    if stri[1] <> ' ' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.hour := integer parse getint(stri);
    if stri[1] <> ':' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.minute := integer parse getint(stri);
    if stri[1] <> ':' then
      raise RANGE_ERROR;
    end if;
    stri := stri[2 ..];
    result.second := integer parse getint(stri);
    if stri <> "" then
      raise RANGE_ERROR;
    end if;
    result.micro_second := 0;
    if result.month_30 < 1 or result.month_30 > 12 or
        result.day < 1 or result.day > daysInMonth(result.year_365, result.month_30) or
        result.hour < 0 or result.hour > 23 or
        result.minute < 0 or result.minute > 59 or
        result.second < 0 or result.second > 59 then
      raise RANGE_ERROR;
    end if;
  end func;


enable_io(duration);


const func boolean: (in duration: dur1) = (in duration: dur2) is func
  result
    var boolean: result is FALSE;
  begin
    result :=
        dur1.year_365 = dur2.year_365 and
        dur1.year_366 = dur2.year_366 and
        dur1.month_28 = dur2.month_28 and
        dur1.month_29 = dur2.month_29 and
        dur1.month_30 = dur2.month_30 and
        dur1.month_31 = dur2.month_31 and
        dur1.day = dur2.day and
        dur1.hour = dur2.hour and
        dur1.minute = dur2.minute and
        dur1.second = dur2.second and
        dur1.micro_second = dur2.micro_second;
  end func;


const func boolean: (in duration: dur1) <> (in duration: dur2) is func
  result
    var boolean: result is FALSE;
  begin
    result :=
        dur1.year_365 <> dur2.year_365 and
        dur1.year_366 <> dur2.year_366 and
        dur1.month_28 <> dur2.month_28 and
        dur1.month_29 <> dur2.month_29 and
        dur1.month_30 <> dur2.month_30 and
        dur1.month_31 <> dur2.month_31 and
        dur1.day <> dur2.day or
        dur1.hour <> dur2.hour or
        dur1.minute <> dur2.minute or
        dur1.second <> dur2.second or
        dur1.micro_second <> dur2.micro_second;
  end func;


const func boolean: (in duration: dur1) >= (in duration: dur2) is func
  result
    var boolean: result is FALSE;
  begin
    if dur1.year_365 + dur1.year_366 > dur2.year_365 + dur2.year_366 then
      result := TRUE;
    elsif dur1.year_365 + dur1.year_366 = dur2.year_365 + dur2.year_366 then
      if dur1.month_28 + dur1.month_29 + dur1.month_30 + dur1.month_31 >
          dur2.month_28 + dur2.month_29 + dur2.month_30 + dur2.month_31 then
        result := TRUE;
      elsif dur1.month_28 + dur1.month_29 + dur1.month_30 + dur1.month_31 =
          dur2.month_28 + dur2.month_29 + dur2.month_30 + dur2.month_31 then
        if dur1.day > dur2.day then
          result := TRUE;
        elsif dur1.day = dur2.day then
          if dur1.hour > dur2.hour then
            result := TRUE;
          elsif dur1.hour = dur2.hour then
            if dur1.minute > dur2.minute then
              result := TRUE;
            elsif dur1.minute = dur2.minute then
              if dur1.second > dur2.second then
                result := TRUE;
              elsif dur1.second = dur2.second then
                if dur1.micro_second >= dur2.micro_second then
                  result := TRUE;
                end if;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
  end func;


const func boolean: (in duration: dur1) <= (in duration: dur2) is func
  result
    var boolean: result is FALSE;
  begin
    if dur1.year_365 + dur1.year_366 < dur2.year_365 + dur2.year_366 then
      result := TRUE;
    elsif dur1.year_365 + dur1.year_366 = dur2.year_365 + dur2.year_366 then
      if dur1.month_28 + dur1.month_29 + dur1.month_30 + dur1.month_31 <
          dur2.month_28 + dur2.month_29 + dur2.month_30 + dur2.month_31 then
        result := TRUE;
      elsif dur1.month_28 + dur1.month_29 + dur1.month_30 + dur1.month_31 =
          dur2.month_28 + dur2.month_29 + dur2.month_30 + dur2.month_31 then
        if dur1.day < dur2.day then
          result := TRUE;
        elsif dur1.day = dur2.day then
          if dur1.hour < dur2.hour then
            result := TRUE;
          elsif dur1.hour = dur2.hour then
            if dur1.minute < dur2.minute then
              result := TRUE;
            elsif dur1.minute = dur2.minute then
              if dur1.second < dur2.second then
                result := TRUE;
              elsif dur1.second = dur2.second then
                if dur1.micro_second <= dur2.micro_second then
                  result := TRUE;
                end if;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
  end func;


const func boolean: (in duration: dur1) < (in duration: dur2) is
  return not dur1 >= dur2;


const func boolean: (in duration: dur1) > (in duration: dur2) is
  return not dur1 <= dur2;


(**
 *  Returns -1, 0 or 1 if the first argument is considered to be
 *  respectively less than, equal to, or greater than the second.
 *)
const func integer: compare (in duration: dur1, in duration: dur2) is func
  result
    var integer: result is 0;
  begin
    if dur1.year_365 + dur1.year_366 < dur2.year_365 + dur2.year_366 then
      result := -1;
    elsif dur1.year_365 + dur1.year_366 > dur2.year_365 + dur2.year_366 then
      result := 1;
    elsif dur1.month_28 + dur1.month_29 + dur1.month_30 + dur1.month_31 <
          dur2.month_28 + dur2.month_29 + dur2.month_30 + dur2.month_31 then
      result := -1;
    elsif dur1.month_28 + dur1.month_29 + dur1.month_30 + dur1.month_31 >
          dur2.month_28 + dur2.month_29 + dur2.month_30 + dur2.month_31 then
      result := 1;
    elsif dur1.day < dur2.day then
      result := -1;
    elsif dur1.day > dur2.day then
      result := 1;
    elsif dur1.hour < dur2.hour then
      result := -1;
    elsif dur1.hour > dur2.hour then
      result := 1;
    elsif dur1.minute < dur2.minute then
      result := -1;
    elsif dur1.minute > dur2.minute then
      result := 1;
    elsif dur1.second < dur2.second then
      result := -1;
    elsif dur1.second > dur2.second then
      result := 1;
    elsif dur1.micro_second < dur2.micro_second then
      result := -1;
    elsif dur1.micro_second > dur2.micro_second then
      result := 1;
    end if;
  end func;


(**
 *  Return a hash value for 'aDuration'.
 *)
const func integer: hashCode (in duration: aDuration) is
  return aDuration.year_365 + aDuration.year_366 +
      aDuration.month_28 + aDuration.month_29 + aDuration.month_30 + aDuration.month_31 +
      aDuration.day + aDuration.hour + aDuration.minute + aDuration.second +
      aDuration.micro_second;


const func duration: (in integer: numYears) . YEARS is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365 := numYears;
  end func;


const func duration: (in integer: numMonths) . MONTHS is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.month_30 := numMonths;
  end func;


const func duration: (in integer: numDays) . DAYS is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.day := numDays;
  end func;


const func duration: (in integer: HRS) . HOURS is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.hour := HRS;
  end func;


const func duration: (in integer: MINS) . MINUTES is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.minute := MINS;
  end func;


const func duration: (in integer: SECS) . SECONDS is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.second := SECS;
  end func;


const func duration: (in integer: MY_SECS) . MICRO_SECONDS is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.micro_second := MY_SECS;
  end func;


(**
 *  Return the duration in years.
 *)
const func integer: toYears (in duration: aDuration) is
  return aDuration.year_365 +
         aDuration.year_366 +
         aDuration.year_correction;


(**
 *  Return the duration in months.
 *)
const func integer: toMonths (in duration: aDuration) is
  return (aDuration.year_365 + aDuration.year_366) * 12 +
      aDuration.month_28 +
      aDuration.month_29 +
      aDuration.month_30 +
      aDuration.month_31 +
      aDuration.month_correction;


(**
 *  Return the duration in days.
 *)
const func integer: toDays (in duration: aDuration) is
  return aDuration.year_365 * 365 +
      aDuration.year_366 * 366 +
      aDuration.month_28 * 28 +
      aDuration.month_29 * 29 +
      aDuration.month_30 * 30 +
      aDuration.month_31 * 31 +
      aDuration.day;


(**
 *  Return the duration in hours.
 *)
const func integer: toHours (in duration: aDuration) is
  return toDays(aDuration) * 24 + aDuration.hour;


(**
 *  Return the duration in minutes.
 *)
const func integer: toMinutes (in duration: aDuration) is
  return (toDays(aDuration) * 24 +
      aDuration.hour) * 60 +
      aDuration.minute;


(**
 *  Return the duration in seconds.
 *)
const func integer: toSeconds (in duration: aDuration) is
  return ((toDays(aDuration) * 24 +
      aDuration.hour) * 60 +
      aDuration.minute) * 60 +
      aDuration.second;


(**
 *  Return the duration in micro seconds.
 *)
const func integer: toMicroSeconds (in duration: aDuration) is
  return (((toDays(aDuration) * 24 +
      aDuration.hour) * 60 +
      aDuration.minute) * 60 +
      aDuration.second) * 1000000 +
      aDuration.micro_second;


const proc: NORMALIZE_DUR_TIME (inout duration: aDuration) is func
  begin
    aDuration.second       +:= aDuration.micro_second mdiv 1000000;
    aDuration.micro_second  := aDuration.micro_second mod  1000000;
    aDuration.minute       +:= aDuration.second       mdiv      60;
    aDuration.second        := aDuration.second       mod       60;
    aDuration.hour         +:= aDuration.minute       mdiv      60;
    aDuration.minute        := aDuration.minute       mod       60;
    aDuration.day          +:= aDuration.hour         mdiv      24;
    aDuration.hour          := aDuration.hour         mod       24;
  end func;


const proc: NORMALIZE (inout duration: aDuration) is func
  begin
    NORMALIZE_DUR_TIME(aDuration);
    aDuration.month_30     +:= aDuration.day          mdiv      30;
    aDuration.day           := aDuration.day          mod       30;
    aDuration.year_365     +:= aDuration.month_30     mdiv      12;
    aDuration.month_30      := aDuration.month_30     mod       12;
  end func;


const func duration: - (in duration: aDuration) is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365         := -aDuration.year_365;
    dur_val.year_366         := -aDuration.year_366;
    dur_val.month_28         := -aDuration.month_28;
    dur_val.month_29         := -aDuration.month_29;
    dur_val.month_30         := -aDuration.month_30;
    dur_val.month_31         := -aDuration.month_31;
    dur_val.day              := -aDuration.day;
    dur_val.hour             := -aDuration.hour;
    dur_val.minute           := -aDuration.minute;
    dur_val.second           := -aDuration.second;
    dur_val.micro_second     := -aDuration.micro_second;
    dur_val.year_correction  := -aDuration.year_correction;
    dur_val.month_correction := -aDuration.month_correction;
    dur_val.day_correction   := -aDuration.day_correction;
  end func;


const func duration: + (in duration: aDuration) is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365         := aDuration.year_365;
    dur_val.year_366         := aDuration.year_366;
    dur_val.month_28         := aDuration.month_28;
    dur_val.month_29         := aDuration.month_29;
    dur_val.month_30         := aDuration.month_30;
    dur_val.month_31         := aDuration.month_31;
    dur_val.day              := aDuration.day;
    dur_val.hour             := aDuration.hour;
    dur_val.minute           := aDuration.minute;
    dur_val.second           := aDuration.second;
    dur_val.micro_second     := aDuration.micro_second;
    dur_val.year_correction  := aDuration.year_correction;
    dur_val.month_correction := aDuration.month_correction;
    dur_val.day_correction   := aDuration.day_correction;
  end func;


const func duration: (in duration: aDuration1) + (in duration: aDuration2) is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365     := aDuration1.year_365     + aDuration2.year_365;
    dur_val.year_366     := aDuration1.year_366     + aDuration2.year_366;
    dur_val.month_28     := aDuration1.month_28     + aDuration2.month_28;
    dur_val.month_29     := aDuration1.month_29     + aDuration2.month_29;
    dur_val.month_30     := aDuration1.month_30     + aDuration2.month_30;
    dur_val.month_31     := aDuration1.month_31     + aDuration2.month_31;
    dur_val.day          := aDuration1.day          + aDuration2.day;
    dur_val.hour         := aDuration1.hour         + aDuration2.hour;
    dur_val.minute       := aDuration1.minute       + aDuration2.minute;
    dur_val.second       := aDuration1.second       + aDuration2.second;
    dur_val.micro_second := aDuration1.micro_second + aDuration2.micro_second;
    NORMALIZE(dur_val);
  end func;


const func duration: (in duration: aDuration1) - (in duration: aDuration2) is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365     := aDuration1.year_365     - aDuration2.year_365;
    dur_val.year_366     := aDuration1.year_366     - aDuration2.year_366;
    dur_val.month_28     := aDuration1.month_28     - aDuration2.month_28;
    dur_val.month_29     := aDuration1.month_29     - aDuration2.month_29;
    dur_val.month_30     := aDuration1.month_30     - aDuration2.month_30;
    dur_val.month_31     := aDuration1.month_31     - aDuration2.month_31;
    dur_val.day          := aDuration1.day          - aDuration2.day;
    dur_val.hour         := aDuration1.hour         - aDuration2.hour;
    dur_val.minute       := aDuration1.minute       - aDuration2.minute;
    dur_val.second       := aDuration1.second       - aDuration2.second;
    dur_val.micro_second := aDuration1.micro_second - aDuration2.micro_second;
    NORMALIZE(dur_val);
  end func;


const func duration: (in integer: number) * (in duration: aDuration) is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365     := number * aDuration.year_365;
    dur_val.year_366     := number * aDuration.year_366;
    dur_val.month_28     := number * aDuration.month_28;
    dur_val.month_29     := number * aDuration.month_29;
    dur_val.month_30     := number * aDuration.month_30;
    dur_val.month_31     := number * aDuration.month_31;
    dur_val.day          := number * aDuration.day;
    dur_val.hour         := number * aDuration.hour;
    dur_val.minute       := number * aDuration.minute;
    dur_val.second       := number * aDuration.second;
    dur_val.micro_second := number * aDuration.micro_second;
    NORMALIZE(dur_val);
  end func;


const func duration: (in duration: aDuration) * (in integer: number) is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365     := aDuration.year_365     * number;
    dur_val.year_366     := aDuration.year_366     * number;
    dur_val.month_28     := aDuration.month_28     * number;
    dur_val.month_29     := aDuration.month_29     * number;
    dur_val.month_30     := aDuration.month_30     * number;
    dur_val.month_31     := aDuration.month_31     * number;
    dur_val.day          := aDuration.day          * number;
    dur_val.hour         := aDuration.hour         * number;
    dur_val.minute       := aDuration.minute       * number;
    dur_val.second       := aDuration.second       * number;
    dur_val.micro_second := aDuration.micro_second * number;
    NORMALIZE(dur_val);
  end func;


const proc: (inout time: tim) +:= (in duration: aDuration) is func
  begin
    tim.year +:= sumOfYearFields(aDuration);
    tim.month +:= sumOfMonthFields(aDuration);
    tim.day +:= aDuration.day;
    tim.hour +:= aDuration.hour;
    tim.minute +:= aDuration.minute;
    tim.second +:= aDuration.second;
    tim.micro_second +:= aDuration.micro_second;
    NORMALIZE(tim);
  end func;


const proc: (inout time: tim) -:= (in duration: aDuration) is func
  begin
    tim.year -:= sumOfYearFields(aDuration);
    tim.month -:= sumOfMonthFields(aDuration);
    tim.day -:= aDuration.day;
    tim.hour -:= aDuration.hour;
    tim.minute -:= aDuration.minute;
    tim.second -:= aDuration.second;
    tim.micro_second -:= aDuration.micro_second;
    NORMALIZE(tim);
  end func;


const func time: (in time: tim) + (in duration: aDuration) is func
  result
    var time: result is time.value;
  begin
    result.year := tim.year + sumOfYearFields(aDuration);
    result.month := tim.month + sumOfMonthFields(aDuration);
    result.day := tim.day + aDuration.day;
    result.hour := tim.hour + aDuration.hour;
    result.minute := tim.minute + aDuration.minute;
    result.second := tim.second + aDuration.second;
    result.micro_second := tim.micro_second + aDuration.micro_second;
    result.timeZone := tim.timeZone;
    NORMALIZE(result);
  end func;


const func time: (in time: tim) - (in duration: aDuration) is func
  result
    var time: result is time.value;
  begin
    result.year := tim.year - sumOfYearFields(aDuration);
    result.month := tim.month - sumOfMonthFields(aDuration);
    result.day := tim.day - aDuration.day;
    result.hour := tim.hour - aDuration.hour;
    result.minute := tim.minute - aDuration.minute;
    result.second := tim.second - aDuration.second;
    result.micro_second := tim.micro_second - aDuration.micro_second;
    result.timeZone := tim.timeZone;
    NORMALIZE(result);
  end func;


const func duration: (in time: tim1) - (in time: tim2) is func
  result
    var duration: dur_val is duration.value;
  local
    var integer: year is 0;
    var integer: month is 1;
    var integer: tim_year is 1;
    var integer: tim_month is 1;
  begin
    if tim1 < tim2 then
      dur_val.day := tim2.day - tim1.day;
      dur_val.hour := tim2.hour - tim1.hour;
      dur_val.minute := tim2.minute - tim1.minute;
      dur_val.second := tim2.second - tim1.second;
      dur_val.micro_second := tim2.micro_second - tim1.micro_second;
      NORMALIZE_DUR_TIME(dur_val);
      dur_val.day          := -dur_val.day;
      dur_val.hour         := -dur_val.hour;
      dur_val.minute       := -dur_val.minute;
      dur_val.second       := -dur_val.second;
      dur_val.micro_second := -dur_val.micro_second;
      tim_month := tim1.month;
      tim_year := tim1.year;
      if dur_val.day > 0 then
        dur_val.day_correction := - daysInMonth(tim_year, tim_month);
      end if;
      if tim_month > tim2.month or tim_month = tim2.month and dur_val.day > 0 then
        if tim_month <= 2 then
          year := tim_year - 1;
        else
          year := tim_year;
        end if;
        incr(tim_year);
        tim_month -:= 12;
      end if;
      for month range tim_month to pred(tim2.month) do
        case daysInMonth(year, succ(pred(month) mod 12)) of
          when {28}: decr(dur_val.month_28);
          when {29}: decr(dur_val.month_29);
          when {30}: decr(dur_val.month_30);
          when {31}: decr(dur_val.month_31);
        end case;
      end for;
    else
      dur_val.day := tim1.day - tim2.day;
      dur_val.hour := tim1.hour - tim2.hour;
      dur_val.minute := tim1.minute - tim2.minute;
      dur_val.second := tim1.second - tim2.second;
      dur_val.micro_second := tim1.micro_second - tim2.micro_second;
      NORMALIZE_DUR_TIME(dur_val);
      tim_month := tim1.month;
      tim_year := tim1.year;
      if dur_val.day < 0 then
        dur_val.day_correction := daysInMonth(tim_year, succ((tim_month - 2) mod 12));
      end if;
      if tim_month < tim2.month or tim_month = tim2.month and dur_val.day < 0 then
        if tim_month <= 2 then
          year := tim_year - 1;
        else
          year := tim_year;
        end if;
        decr(tim_year);
        tim_month +:= 12;
      end if;
      for month range tim2.month to pred(tim_month) do
        case daysInMonth(year, succ(pred(month) mod 12)) of
          when {28}: incr(dur_val.month_28);
          when {29}: incr(dur_val.month_29);
          when {30}: incr(dur_val.month_30);
          when {31}: incr(dur_val.month_31);
        end case;
      end for;
    end if;
    dur_val.year_365 := tim_year - tim2.year;
    dur_val.year_366 :=
        pred(tim_year) mdiv 4 - pred(tim_year) mdiv 100 + pred(tim_year) mdiv 400 -
        pred(tim2.year) mdiv 4 + pred(tim2.year) mdiv 100 - pred(tim2.year) mdiv 400;
    dur_val.year_365 -:= dur_val.year_366;
    if dur_val.day_correction > 0 then
      if sumOfMonthFields(dur_val) = 0 then
        dur_val.month_correction := 11;
        dur_val.year_correction := -1;
      else
        dur_val.month_correction := -1;
      end if;
    elsif dur_val.day_correction < 0 then
      if sumOfMonthFields(dur_val) = 12 then
        dur_val.month_correction := -11;
        dur_val.year_correction := 1;
      else
        dur_val.month_correction := 1;
      end if;
    end if;
  end func;


(**
 *  Wait for a given duration.
 *)
const proc: wait (in duration: aDuration) is func
  begin
    await(time(NOW) + aDuration);
  end func;


(*
const type: MONTH_ENUM is new enum
    JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
    JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER
  end enum;


const string: str (JANUARY) is "JANUARY";
const MONTH_ENUM: pred (JANUARY) is DECEMBER;
const MONTH_ENUM: succ (JANUARY) is FEBRUARY;
const integer: ord (JANUARY) is 1;
const integer: length (JANUARY) is 31;

const string: str (FEBRUARY) is "FEBRUARY";
const MONTH_ENUM: pred (FEBRUARY) is JANUARY;
const MONTH_ENUM: succ (FEBRUARY) is MARCH;
const integer: ord (FEBRUARY) is 2;
const integer: length (FEBRUARY) is 28;

const string: str (MARCH) is "MARCH";
const MONTH_ENUM: pred (MARCH) is FEBRUARY;
const MONTH_ENUM: succ (MARCH) is APRIL;
const integer: ord (MARCH) is 3;
const integer: length (MARCH) is 31;

const string: str (APRIL) is "APRIL";
const MONTH_ENUM: pred (APRIL) is MARCH;
const MONTH_ENUM: succ (APRIL) is MAY;
const integer: ord (APRIL) is 4;
const integer: length (APRIL) is 30;

const string: str (MAY) is "MAY";
const MONTH_ENUM: pred (MAY) is APRIL;
const MONTH_ENUM: succ (MAY) is JUNE;
const integer: ord (MAY) is 5;
const integer: length (MAY) is 31;

const string: str (JUNE) is "JUNE";
const MONTH_ENUM: pred (JUNE) is MAY;
const MONTH_ENUM: succ (JUNE) is JULY;
const integer: ord (JUNE) is 6;
const integer: length (JUNE) is 30;

const string: str (JULY) is "JULY";
const MONTH_ENUM: pred (JULY) is JUNE;
const MONTH_ENUM: succ (JULY) is AUGUST;
const integer: ord (JULY) is 7;
const integer: length (JULY) is 31;

const string: str (AUGUST) is "AUGUST";
const MONTH_ENUM: pred (AUGUST) is JULY;
const MONTH_ENUM: succ (AUGUST) is SEPTEMBER;
const integer: ord (AUGUST) is 8;
const integer: length (AUGUST) is 31;

const string: str (SEPTEMBER) is "SEPTEMBER";
const MONTH_ENUM: pred (SEPTEMBER) is AUGUST;
const MONTH_ENUM: succ (SEPTEMBER) is OCTOBER;
const integer: ord (SEPTEMBER) is 9;
const integer: length (SEPTEMBER) is 30;

const string: str (OCTOBER) is "OCTOBER";
const MONTH_ENUM: pred (OCTOBER) is SEPTEMBER;
const MONTH_ENUM: succ (OCTOBER) is NOVEMBER;
const integer: ord (OCTOBER) is 10;
const integer: length (OCTOBER) is 31;

const string: str (NOVEMBER) is "NOVEMBER";
const MONTH_ENUM: pred (NOVEMBER) is OCTOBER;
const MONTH_ENUM: succ (NOVEMBER) is DECEMBER;
const integer: ord (NOVEMBER) is 11;
const integer: length (NOVEMBER) is 30;

const string: str (DECEMBER) is "DECEMBER";
const MONTH_ENUM: pred (DECEMBER) is NOVEMBER;
const MONTH_ENUM: succ (DECEMBER) is JANUARY;
const integer: ord (DECEMBER) is 12;
const integer: length (DECEMBER) is 31;


const proc: MONTH_ENUM.val.(in integer: number) is func
  result
    var MONTH_ENUM: MON_VAL is JANUARY;
  begin
    case number of
      when { 1}: MON_VAL := JANUARY;
      when { 2}: MON_VAL := FEBRUARY;
      when { 3}: MON_VAL := MARCH;
      when { 4}: MON_VAL := APRIL;
      when { 5}: MON_VAL := MAY;
      when { 6}: MON_VAL := JUNE;
      when { 7}: MON_VAL := JULY;
      when { 8}: MON_VAL := AUGUST;
      when { 9}: MON_VAL := SEPTEMBER;
      when {10}: MON_VAL := OCTOBER;
      when {11}: MON_VAL := NOVEMBER;
      when {12}: MON_VAL := DECEMBER;
    end case;
  end func;


const type: WEEKDAYS is new enum
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
    FRIDAY, SATURDAY, SUNDAY
  end enum;


const string: str (MONDAY) is "MONDAY";
const WEEKDAYS: pred (MONDAY) is SUNDAY;
const WEEKDAYS: succ (MONDAY) is TUESDAY;
const integer: ord (MONDAY) is 1;

const string: str (TUESDAY) is "TUESDAY";
const WEEKDAYS: pred (TUESDAY) is MONDAY;
const WEEKDAYS: succ (TUESDAY) is WEDNESDAY;
const integer: ord (TUESDAY) is 2;

const string: str (WEDNESDAY) is "WEDNESDAY";
const WEEKDAYS: pred (WEDNESDAY) is TUESDAY;
const WEEKDAYS: succ (WEDNESDAY) is THURSDAY;
const integer: ord (WEDNESDAY) is 3;

const string: str (THURSDAY) is "THURSDAY";
const WEEKDAYS: pred (THURSDAY) is WEDNESDAY;
const WEEKDAYS: succ (THURSDAY) is FRIDAY;
const integer: ord (THURSDAY) is 4;

const string: str (FRIDAY) is "FRIDAY";
const WEEKDAYS: pred (FRIDAY) is THURSDAY;
const WEEKDAYS: succ (FRIDAY) is SATURDAY;
const integer: ord (FRIDAY) is 5;

const string: str (SATURDAY) is "SATURDAY";
const WEEKDAYS: pred (SATURDAY) is FRIDAY;
const WEEKDAYS: succ (SATURDAY) is SUNDAY;
const integer: ord (SATURDAY) is 6;

const string: str (SUNDAY) is "SUNDAY";
const WEEKDAYS: pred (SUNDAY) is SATURDAY;
const WEEKDAYS: succ (SUNDAY) is MONDAY;
const integer: ord (SUNDAY) is 7;
*)
