
(********************************************************************)
(*                                                                  *)
(*  time.s7i      Time and date 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.                       *)
(*                                                                  *)
(********************************************************************)


include "enable_io.s7i";


(**
 *  Describes times and dates.
 *  For dates the proleptic Gregorian calendar is used (which assumes
 *  that the Gregorian calendar was even in effect at dates preceding
 *  its official introduction). This convention is used according to
 *  ISO 8601 which also defines that positive and negative years exist
 *  and that the year preceding 1 is 0. Time is measured in hours,
 *  minutes, seconds and micro seconds. Additionally information about
 *  the difference to UTC and a flag indicating daylight saving time
 *  is maintained also.
 *)
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: 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";


(**
 *  Determine if a year is a leap year in the Gregorian calendar.
 *  @return TRUE if the year is a leap year, 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;


(**
 *  Determine the number of days in a 'year'.
 *  @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;


(**
 *  Determine the number of days in the 'month' of the given 'year'.
 *  @return the number of days in the month.
 *)
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;


(**
 *  Determine the number of days in the month of a given date.
 *  @return the number of days in the month.
 *)
const func integer: daysInMonth (in time: date) is
  return daysInMonth(date.year, date.month);


(**
 *  Convert a time to a string with ISO 8601 YYYY-MM-DD date format.
 *  @return the string result of the conversion.
 *)
const func string: strDate (in time: aTime) is
  return aTime.year  lpad0 4 <& "-" <&
         aTime.month lpad0 2 <& "-" <&
         aTime.day   lpad0 2;


(**
 *  Convert a time to a string with ISO 8601 hh:mm:ss time format.
 *  Convert a time to a string with ISO 8601 hh:mm:ss time format.
 *  @return the string result of the conversion.
 *)
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 str(aTime.day)   <& delimiter <&
         str(aTime.month) <& delimiter <&
         aTime.year  rem 100 lpad0 2;


const func string: str_d_m_yyyy (in time: aTime, in string: delimiter) is
  return str(aTime.day)   <& delimiter <&
         str(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;


(**
 *  Convert a time to a string with ISO 8601 date and time format.
 *  The string has the format YYYY-MM-DD hh:mm:ss.uuuuuu followed by a
 *  time zone in the format UTC+n and (DST) when it is a daylight
 *  saving time. The microseconds (uuuuuu) are omitted when they are
 *  zero. A time zone of UTC+0 is also omitted.
 *  @return the string result of the conversion.
 *)
const func string: str (in time: aTime) is
  return strDate(aTime) <& " " <&
         strTime(aTime) <& " " <&
         strTimeZone(aTime);


(**
 *  Convert 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
 *  @return the time result of the conversion.
 *  @exception RANGE_ERROR When stri contains not a valid time value.
 *)
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: isGreaterEqual is FALSE;
  begin
    if tim1.year > tim2.year then
      isGreaterEqual := TRUE;
    elsif tim1.year = tim2.year then
      if tim1.month > tim2.month then
        isGreaterEqual := TRUE;
      elsif tim1.month = tim2.month then
        if tim1.day > tim2.day then
          isGreaterEqual := TRUE;
        elsif tim1.day = tim2.day then
          if tim1.hour > tim2.hour then
            isGreaterEqual := TRUE;
          elsif tim1.hour = tim2.hour then
            if tim1.minute > tim2.minute then
              isGreaterEqual := TRUE;
            elsif tim1.minute = tim2.minute then
              if tim1.second > tim2.second then
                isGreaterEqual := TRUE;
              elsif tim1.second = tim2.second then
                if tim1.micro_second >= tim2.micro_second then
                  isGreaterEqual := 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: isLessEqual is FALSE;
  begin
    if tim1.year < tim2.year then
      isLessEqual := TRUE;
    elsif tim1.year = tim2.year then
      if tim1.month < tim2.month then
        isLessEqual := TRUE;
      elsif tim1.month = tim2.month then
        if tim1.day < tim2.day then
          isLessEqual := TRUE;
        elsif tim1.day = tim2.day then
          if tim1.hour < tim2.hour then
            isLessEqual := TRUE;
          elsif tim1.hour = tim2.hour then
            if tim1.minute < tim2.minute then
              isLessEqual := TRUE;
            elsif tim1.minute = tim2.minute then
              if tim1.second < tim2.second then
                isLessEqual := TRUE;
              elsif tim1.second = tim2.second then
                if tim1.micro_second <= tim2.micro_second then
                  isLessEqual := 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;


(**
 *  Compares two times.
 *  @return -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;


(**
 *  Compute the hash value of a time.
 *  @return the hash value.
 *)
const func integer: hashCode (in time: aTime) is
  return aTime.year + aTime.month + aTime.day +
      aTime.hour + aTime.minute + aTime.second + aTime.micro_second;


(**
 *  Truncate 'aTime' to the beginning of a second.
 *  @return the truncated time.
 *)
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;


(**
 *  Truncate 'aTime' to the beginning of a minute.
 *  @return the truncated time.
 *)
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;


(**
 *  Truncate 'aTime' to the beginning of a hour.
 *  @return the truncated time.
 *)
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;


(**
 *  Truncate 'aTime' to the beginning of a day.
 *  @return the truncated time.
 *)
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;


(**
 *  Truncate 'aTime' to the beginning of the first day in the month.
 *  @return the truncated time.
 *)
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;


(**
 *  Truncate 'aTime' to the beginning of the first day in the year.
 *  @return the truncated time.
 *)
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: weekday 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;
    weekday := 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: dayOfYear is 0;
  begin
    if isLeapYear(aDate.year) then
      dayOfYear := [](0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)[aDate.month] + aDate.day;
    else
      dayOfYear := [](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 a week number 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: weekNumber 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);
    weekNumber := (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 a week number 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: weekNumber is 0;
  begin
    weekNumber := weekOfYear(aDate.year, dayOfYear(aDate));
    if weekNumber <= 0 then
      weekDateYear := pred(aDate.year);
    elsif weekNumber >= 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: weekNumber is 0;
  begin
    weekNumber := weekOfYear(aDate.year, dayOfYear(aDate));
    if weekNumber <= 0 then
      weekNumber := weekOfYear(pred(aDate.year), 366);
    elsif weekNumber >= 53 and aDate.day >= 29 and
        weekOfYear(succ(aDate.year), 31 - aDate.day) = 1 then
      weekNumber := 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).
 *  @return the time in UTC.
 *)
const func time: toUTC (in time: aTime) is func
  result
    var time: timeInUTC is time.value;
  begin
    timeInUTC := aTime;
    timeInUTC.minute -:= aTime.timeZone;
    timeInUTC.timeZone := 0;
    NORMALIZE(timeInUTC);
  end func;


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


(**
 *  Compute 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.
 *  @return the julian day number.
 *)
const func integer: julianDayNumber (in time: aDate) is func
  result
    var integer: julianDayNumber 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;
    julianDayNumber := (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.
 *  @return the seconds since the Unix Epoch.
 *)
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.
 *  The timestamp is expressed in seconds since the Unix Epoch.
 *  The Unix Epoch (1970-01-01 00:00:00 UTC) corresponds to 0.
 *  @return the local time that corresponds to the timestamp.
 *)
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;
*)


(**
 *  Determine the current local time.
 *  @return the current local 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;
