
(********************************************************************)
(*                                                                  *)
(*  duration.s7i  Time and date duration support 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 "time.s7i";
include "enable_io.s7i";


(**
 *  Describes time and date durations.
 *)
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;
  end struct;


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


(**
 *  Obtain the months of a duration.
 *  @return 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;


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


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


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


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


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


(**
 *  Convert a duration to a string.
 *  The duration format is P[nY][nM][nD][T[nH][nM][n[.n]S]], where
 *  n is a signed decimal of a date or time element.
 *  @return the result of the conversion.
 *)
const func string: str (in duration: aDuration) is func
  result
    var string: stri is "P";
  local
    var integer: years is 0;
    var integer: months is 0;
   var string: micro_seconds is "";
  begin
    years  := getYears(aDuration);
    months := getMonths(aDuration);
    if years <> 0 then
      stri &:= years <& "Y";
    end if;
    if months <> 0 then
      stri &:= months <& "M";
    end if;
    if aDuration.day <> 0 or (years = 0 and months = 0 and
        aDuration.hour = 0 and aDuration.minute = 0 and
        aDuration.second = 0 and aDuration.micro_second = 0) then
      stri &:= aDuration.day <& "D";
    end if;
    if aDuration.hour <> 0 or aDuration.minute <> 0 or
        aDuration.second <> 0 or aDuration.micro_second <> 0 then
      stri &:= "T";
      if aDuration.hour <> 0 then
        stri &:= aDuration.hour <& "H";
      end if;
      if aDuration.minute <> 0 then
        stri &:= aDuration.minute <& "M";
      end if;
      if aDuration.second <> 0 or aDuration.micro_second <> 0 then
        if aDuration.second = 0 and aDuration.micro_second < 0 then
          stri &:= "-0";
        else
          stri &:= str(aDuration.second);
        end if;
        if aDuration.micro_second <> 0 then
          stri &:= ".";
          micro_seconds := abs(aDuration.micro_second) lpad0 6;
          while endsWith(micro_seconds, "0") do
            micro_seconds := micro_seconds[.. pred(length(micro_seconds))];
          end while;
          stri &:= micro_seconds;
        end if;
        stri &:= "S";
      end if;
    end if;
  end func;


(**
 *  Convert a duration to a duration literal.
 *  @return the duration literal.
 *)
const func string: literal (in duration: aDuration) is
  return "duration(" & literal(str(aDuration)) & ")";


(**
 *  Convert a string to a ''duration''.
 *  @param stri A duration in the format P[nY][nM][nD][T[nH][nM][n[.n]S]]
 *  @return the ''duration'' result of the conversion.
 *  @exception RANGE_ERROR When stri contains not a valid duration value.
 *)
const func duration: (attr duration) parse (in var string: stri) is func
  result
    var duration: aDuration is duration.value;
  local
    var string: numStri is "";
    var boolean: datePart is TRUE;
    var boolean: secondsPart is TRUE;
    var integer: seconds is 0;
    var boolean: negativeSeconds is FALSE;
  begin
    if not startsWith(stri, "P") then
      raise RANGE_ERROR;
    else
      stri := stri[2 ..];
      while stri <> "" do
        if startsWith(stri, "-") then
          stri := stri[2 ..];
          numStri := "-" & getint(stri);
        else
          numStri := getint(stri);
        end if;
        if stri <> "" then
          case stri[1] of
            when {'Y'}:
              aDuration.year_365 := integer(numStri);
              stri := stri[2 ..];
            when {'M'}:
              if datePart then
                aDuration.month_30 := integer(numStri);
              else
                aDuration.minute := integer(numStri);
              end if;
              stri := stri[2 ..];
            when {'D'}:
              aDuration.day := integer(numStri);
              stri := stri[2 ..];
            when {'T'}:
              noop;
            when {'H'}:
              aDuration.hour := integer(numStri);
              stri := stri[2 ..];
            when {'S'}:
              if secondsPart then
                aDuration.second := integer(numStri);
              else
                aDuration.second := seconds;
                numStri := numStri[.. 6];
                numStri &:= "0" mult (6 - length(numStri));
                aDuration.micro_second := integer parse numStri;
                if negativeSeconds then
                  aDuration.micro_second := -aDuration.micro_second;
                end if;
              end if;
              stri := stri[2 ..];
            when {'.'}:
              secondsPart := FALSE;
              negativeSeconds := startsWith(numStri, "-");
              seconds := integer(numStri);
              stri := stri[2 ..];
            otherwise:
              raise RANGE_ERROR;
          end case;
          if startsWith(stri, "T") then
            if datePart then
              datePart := FALSE;
              stri := stri[2 ..];
            else
              raise RANGE_ERROR;
            end if;
          end if;
        else
          raise RANGE_ERROR;
        end if;
      end while;
    end if;
    if aDuration.month_30 < -11 or aDuration.month_30 > 11 or
        aDuration.day < -31 or aDuration.day > 31 or
        aDuration.hour < -23 or aDuration.hour > 23 or
        aDuration.minute < -59 or aDuration.minute > 59 or
        aDuration.second < -59 or aDuration.second > 59 or
        aDuration.micro_second < -999999 or aDuration.micro_second > 999999 then
      raise RANGE_ERROR;
    end if;
  end func;


enable_io(duration);


(**
 *  Convert a string to a ''duration''.
 *  @param stri A duration in the format P[nY][nM][nD][T[nH][nM][n[.n]S]]
 *  @return the ''duration'' result of the conversion.
 *  @exception RANGE_ERROR When stri contains not a valid duration value.
 *)
const func duration: duration (in string: stri) is
    return duration parse stri;


(**
 *  Check if two ''duration'' values are equal.
 *  @return TRUE if both durations are equal, FALSE otherwise.
 *)
const func boolean: (in duration: aDuration1) = (in duration: aDuration2) is
  return
    getYears(aDuration1) = getYears(aDuration2) and
    getMonths(aDuration1) = getMonths(aDuration2) and
    aDuration1.day = aDuration2.day and
    aDuration1.hour = aDuration2.hour and
    aDuration1.minute = aDuration2.minute and
    aDuration1.second = aDuration2.second and
    aDuration1.micro_second = aDuration2.micro_second;


(**
 *  Check if two ''duration'' values are not equal.
 *  @return FALSE if both durations are equal, TRUE otherwise.
 *)
const func boolean: (in duration: aDuration1) <> (in duration: aDuration2) is
  return
    getYears(aDuration1) <> getYears(aDuration2) or
    getMonths(aDuration1) <> getMonths(aDuration2) or
    aDuration1.day <> aDuration2.day or
    aDuration1.hour <> aDuration2.hour or
    aDuration1.minute <> aDuration2.minute or
    aDuration1.second <> aDuration2.second or
    aDuration1.micro_second <> aDuration2.micro_second;


(**
 *  Check if ''aDuration1'' is less than or equal to ''aDuration2''.
 *  @return TRUE if ''aDuration1'' is less than or equal to ''aDuration2'',
 *          FALSE otherwise.
 *)
const func boolean: (in duration: aDuration1) <= (in duration: aDuration2) is func
  result
    var boolean: isLessEqual is FALSE;
  begin
    if aDuration1.year_365 + aDuration1.year_366 < aDuration2.year_365 + aDuration2.year_366 then
      isLessEqual := TRUE;
    elsif aDuration1.year_365 + aDuration1.year_366 = aDuration2.year_365 + aDuration2.year_366 then
      if aDuration1.month_28 + aDuration1.month_29 + aDuration1.month_30 + aDuration1.month_31 <
          aDuration2.month_28 + aDuration2.month_29 + aDuration2.month_30 + aDuration2.month_31 then
        isLessEqual := TRUE;
      elsif aDuration1.month_28 + aDuration1.month_29 + aDuration1.month_30 + aDuration1.month_31 =
          aDuration2.month_28 + aDuration2.month_29 + aDuration2.month_30 + aDuration2.month_31 then
        if aDuration1.day < aDuration2.day then
          isLessEqual := TRUE;
        elsif aDuration1.day = aDuration2.day then
          if aDuration1.hour < aDuration2.hour then
            isLessEqual := TRUE;
          elsif aDuration1.hour = aDuration2.hour then
            if aDuration1.minute < aDuration2.minute then
              isLessEqual := TRUE;
            elsif aDuration1.minute = aDuration2.minute then
              if aDuration1.second < aDuration2.second then
                isLessEqual := TRUE;
              elsif aDuration1.second = aDuration2.second then
                if aDuration1.micro_second <= aDuration2.micro_second then
                  isLessEqual := TRUE;
                end if;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
  end func;


(**
 *  Check if ''aDuration1'' is greater than or equal to ''aDuration2''.
 *  @return TRUE if ''aDuration1'' is greater than or equal to ''aDuration2'',
 *          FALSE otherwise.
 *)
const func boolean: (in duration: aDuration1) >= (in duration: aDuration2) is func
  result
    var boolean: isGreaterEqual is FALSE;
  begin
    if aDuration1.year_365 + aDuration1.year_366 > aDuration2.year_365 + aDuration2.year_366 then
      isGreaterEqual := TRUE;
    elsif aDuration1.year_365 + aDuration1.year_366 = aDuration2.year_365 + aDuration2.year_366 then
      if aDuration1.month_28 + aDuration1.month_29 + aDuration1.month_30 + aDuration1.month_31 >
          aDuration2.month_28 + aDuration2.month_29 + aDuration2.month_30 + aDuration2.month_31 then
        isGreaterEqual := TRUE;
      elsif aDuration1.month_28 + aDuration1.month_29 + aDuration1.month_30 + aDuration1.month_31 =
          aDuration2.month_28 + aDuration2.month_29 + aDuration2.month_30 + aDuration2.month_31 then
        if aDuration1.day > aDuration2.day then
          isGreaterEqual := TRUE;
        elsif aDuration1.day = aDuration2.day then
          if aDuration1.hour > aDuration2.hour then
            isGreaterEqual := TRUE;
          elsif aDuration1.hour = aDuration2.hour then
            if aDuration1.minute > aDuration2.minute then
              isGreaterEqual := TRUE;
            elsif aDuration1.minute = aDuration2.minute then
              if aDuration1.second > aDuration2.second then
                isGreaterEqual := TRUE;
              elsif aDuration1.second = aDuration2.second then
                if aDuration1.micro_second >= aDuration2.micro_second then
                  isGreaterEqual := TRUE;
                end if;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
  end func;


(**
 *  Check if ''aDuration1'' is less than ''aDuration2''.
 *  @return TRUE if ''aDuration1'' is less than ''aDuration2'',
 *          FALSE otherwise.
 *)
const func boolean: (in duration: aDuration1) < (in duration: aDuration2) is
  return not aDuration1 >= aDuration2;


(**
 *  Check if ''aDuration1'' is greater than ''aDuration2''.
 *  @return TRUE if ''aDuration1'' is greater than ''aDuration2'',
 *          FALSE otherwise.
 *)
const func boolean: (in duration: aDuration1) > (in duration: aDuration2) is
  return not aDuration1 <= aDuration2;


(**
 *  Compares two durations.
 *  @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 duration: aDuration1, in duration: aDuration2) is func
  result
    var integer: signumValue is 0;
  begin
    if aDuration1.year_365 + aDuration1.year_366 < aDuration2.year_365 + aDuration2.year_366 then
      signumValue := -1;
    elsif aDuration1.year_365 + aDuration1.year_366 > aDuration2.year_365 + aDuration2.year_366 then
      signumValue := 1;
    elsif aDuration1.month_28 + aDuration1.month_29 + aDuration1.month_30 + aDuration1.month_31 <
          aDuration2.month_28 + aDuration2.month_29 + aDuration2.month_30 + aDuration2.month_31 then
      signumValue := -1;
    elsif aDuration1.month_28 + aDuration1.month_29 + aDuration1.month_30 + aDuration1.month_31 >
          aDuration2.month_28 + aDuration2.month_29 + aDuration2.month_30 + aDuration2.month_31 then
      signumValue := 1;
    elsif aDuration1.day < aDuration2.day then
      signumValue := -1;
    elsif aDuration1.day > aDuration2.day then
      signumValue := 1;
    elsif aDuration1.hour < aDuration2.hour then
      signumValue := -1;
    elsif aDuration1.hour > aDuration2.hour then
      signumValue := 1;
    elsif aDuration1.minute < aDuration2.minute then
      signumValue := -1;
    elsif aDuration1.minute > aDuration2.minute then
      signumValue := 1;
    elsif aDuration1.second < aDuration2.second then
      signumValue := -1;
    elsif aDuration1.second > aDuration2.second then
      signumValue := 1;
    elsif aDuration1.micro_second < aDuration2.micro_second then
      signumValue := -1;
    elsif aDuration1.micro_second > aDuration2.micro_second then
      signumValue := 1;
    end if;
  end func;


(**
 *  Compute the hash value of ''aDuration''.
 *  @return the hash value.
 *)
const func integer: hashCode (in duration: aDuration) is
  return (aDuration.year_365 + aDuration.year_366) << 6 +
      (aDuration.month_28 + aDuration.month_29 + aDuration.month_30 + aDuration.month_31) << 5 +
      aDuration.day << 4 +
      aDuration.hour << 3 + aDuration.minute << 2 + aDuration.second << 1 +
      aDuration.micro_second;


(**
 *  Compute the years of ''aDuration''.
 *  @return the duration in years.
 *)
const func integer: toYears (in duration: aDuration) is
  return aDuration.year_365 +
         aDuration.year_366;


(**
 *  Compute the months of ''aDuration''.
 *  @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;


(**
 *  Compute the days of ''aDuration''.
 *  @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;


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


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


(**
 *  Compute the seconds of ''aDuration''.
 *  @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;


(**
 *  Compute the micro seconds of ''aDuration''.
 *  @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 integer: numYears) . YEARS is func
  result
    var duration: dur_val is duration.value;
  begin
    dur_val.year_365 := numYears;
    NORMALIZE(dur_val);
  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;
    NORMALIZE(dur_val);
  end func;


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


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


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


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


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


(**
 *  Plus sign for durations.
 *  @return its operand unchanged.
 *)
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;
  end func;


(**
 *  Minus sign, negate a duration.
 *  @return the negated duration.
 *)
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;
  end func;


(**
 *  Add two durations.
 *  @return the sum of the two durations.
 *)
const func duration: (in duration: aDuration1) + (in duration: aDuration2) is func
  result
    var duration: sum is duration.value;
  begin
    sum.year_365     := aDuration1.year_365     + aDuration2.year_365;
    sum.year_366     := aDuration1.year_366     + aDuration2.year_366;
    sum.month_28     := aDuration1.month_28     + aDuration2.month_28;
    sum.month_29     := aDuration1.month_29     + aDuration2.month_29;
    sum.month_30     := aDuration1.month_30     + aDuration2.month_30;
    sum.month_31     := aDuration1.month_31     + aDuration2.month_31;
    sum.day          := aDuration1.day          + aDuration2.day;
    sum.hour         := aDuration1.hour         + aDuration2.hour;
    sum.minute       := aDuration1.minute       + aDuration2.minute;
    sum.second       := aDuration1.second       + aDuration2.second;
    sum.micro_second := aDuration1.micro_second + aDuration2.micro_second;
    NORMALIZE(sum);
  end func;


(**
 *  Compute the subtraction of two durations.
 *  @return the difference of the two durations.
 *)
const func duration: (in duration: aDuration1) - (in duration: aDuration2) is func
  result
    var duration: difference is duration.value;
  begin
    difference.year_365     := aDuration1.year_365     - aDuration2.year_365;
    difference.year_366     := aDuration1.year_366     - aDuration2.year_366;
    difference.month_28     := aDuration1.month_28     - aDuration2.month_28;
    difference.month_29     := aDuration1.month_29     - aDuration2.month_29;
    difference.month_30     := aDuration1.month_30     - aDuration2.month_30;
    difference.month_31     := aDuration1.month_31     - aDuration2.month_31;
    difference.day          := aDuration1.day          - aDuration2.day;
    difference.hour         := aDuration1.hour         - aDuration2.hour;
    difference.minute       := aDuration1.minute       - aDuration2.minute;
    difference.second       := aDuration1.second       - aDuration2.second;
    difference.micro_second := aDuration1.micro_second - aDuration2.micro_second;
    NORMALIZE(difference);
  end func;


(**
 *  Multiply a duration by a number.
 *  @return the multiplied duration.
 *)
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;


(**
 *  Multiply a duration by a number.
 *  @return the multiplied duration.
 *)
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;


(**
 *  Increment a duration by a delta.
 *)
const proc: (inout duration: aDuration) +:= (in duration: delta) is func
  begin
    aDuration.year_365     +:= delta.year_365;
    aDuration.year_366     +:= delta.year_366;
    aDuration.month_28     +:= delta.month_28;
    aDuration.month_29     +:= delta.month_29;
    aDuration.month_30     +:= delta.month_30;
    aDuration.month_31     +:= delta.month_31;
    aDuration.day          +:= delta.day;
    aDuration.hour         +:= delta.hour;
    aDuration.minute       +:= delta.minute;
    aDuration.second       +:= delta.second;
    aDuration.micro_second +:= delta.micro_second;
    NORMALIZE(aDuration);
  end func;


(**
 *  Decrement a duration by a delta.
 *)
const proc: (inout duration: aDuration) -:= (in duration: delta) is func
  begin
    aDuration.year_365     -:= delta.year_365;
    aDuration.year_366     -:= delta.year_366;
    aDuration.month_28     -:= delta.month_28;
    aDuration.month_29     -:= delta.month_29;
    aDuration.month_30     -:= delta.month_30;
    aDuration.month_31     -:= delta.month_31;
    aDuration.day          -:= delta.day;
    aDuration.hour         -:= delta.hour;
    aDuration.minute       -:= delta.minute;
    aDuration.second       -:= delta.second;
    aDuration.micro_second -:= delta.micro_second;
    NORMALIZE(aDuration);
  end func;


(**
 *  Increment a [[time]] by a duration.
 *)
const proc: (inout time: tim) +:= (in duration: aDuration) is func
  begin
    tim.year +:= getYears(aDuration);
    tim.month +:= getMonths(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;


(**
 *  Decrement a [[time]] by a duration.
 *)
const proc: (inout time: tim) -:= (in duration: aDuration) is func
  begin
    tim.year -:= getYears(aDuration);
    tim.month -:= getMonths(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;


(**
 *  Add a duration to a [[time]].
 *  @return the time result of the addition.
 *)
const func time: (in time: tim) + (in duration: aDuration) is func
  result
    var time: laterTime is time.value;
  begin
    laterTime.year := tim.year + getYears(aDuration);
    laterTime.month := tim.month + getMonths(aDuration);
    laterTime.day := tim.day + aDuration.day;
    laterTime.hour := tim.hour + aDuration.hour;
    laterTime.minute := tim.minute + aDuration.minute;
    laterTime.second := tim.second + aDuration.second;
    laterTime.micro_second := tim.micro_second + aDuration.micro_second;
    laterTime.timeZone := tim.timeZone;
    laterTime.daylightSavingTime := tim.daylightSavingTime;
    NORMALIZE(laterTime);
  end func;


(**
 *  Subtract a duration from a [[time]].
 *  @return the time result of the subtraction.
 *)
const func time: (in time: tim) - (in duration: aDuration) is func
  result
    var time: formerTimer is time.value;
  begin
    formerTimer.year := tim.year - getYears(aDuration);
    formerTimer.month := tim.month - getMonths(aDuration);
    formerTimer.day := tim.day - aDuration.day;
    formerTimer.hour := tim.hour - aDuration.hour;
    formerTimer.minute := tim.minute - aDuration.minute;
    formerTimer.second := tim.second - aDuration.second;
    formerTimer.micro_second := tim.micro_second - aDuration.micro_second;
    formerTimer.timeZone := tim.timeZone;
    formerTimer.daylightSavingTime := tim.daylightSavingTime;
    NORMALIZE(formerTimer);
  end func;


(**
 *  Subtract two [[time|times]].
 *  @return the duration between the two points in time.
 *)
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;
    var integer: monthSum is 0;
  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 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 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;
(*
    if dur_val.day >= 31 then
      dur_val.day -:= 31;
      incr(dur_val.month_31);
    end if;
    if dur_val.day <= -31 then
      dur_val.day +:= 31;
      decr(dur_val.month_31);
    end if;
*)
    monthSum := getMonths(dur_val);
    if monthSum = 12 then
      incr(tim_year);
      dur_val.month_28 := 0;
      dur_val.month_29 := 0;
      dur_val.month_30 := 0;
      dur_val.month_31 := 0;
    elsif monthSum = -12 then
      decr(tim_year);
      dur_val.month_28 := 0;
      dur_val.month_29 := 0;
      dur_val.month_30 := 0;
      dur_val.month_31 := 0;
    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;
  end func;


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