Implementation of Dates Package

Go to Package Interface

WITH Ada.Calendar;
PACKAGE BODY Dates IS
------------------------------------------------------------------
--|
--| body for package to represent calendar dates
--|
--| Author: Michael B. Feldman, The George Washington University
--| Last Modified: September 1995
--|
------------------------------------------------------------------

-- body for package to represent calendar dates

  -- tables containing the Julian day of the last day of each month
  NonLeapDayEndOfMonth: ARRAY(MonthNumber) OF JulianDay :=
  -- Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
    (31,  59,  90,  120, 151, 181, 212, 243, 273, 304, 334, 365);

  LeapDayEndOfMonth: ARRAY(MonthNumber) OF JulianDay :=
  -- Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
    (31,  60,  91,  121, 152, 182, 213, 244, 274, 305, 335, 366);

  FUNCTION IsLeap(Year: YearNumber) RETURN Boolean IS
  -- Pre:  Year is defined
  -- Post: returns True if and only if Year is a leap year
  BEGIN
    RETURN  (Year REM 4 = 0) AND
            ((Year REM 100 /= 0) OR (Year REM 400 = 0));
  END IsLeap;

  FUNCTION MakeDate(Year  : YearNumber;
                    Month : MonthNumber;
                    Day   : DayNumber) RETURN Date IS

    TempTime: Ada.Calendar.Time;
    Result:   Date;

  BEGIN -- MakeDate

    TempTime := Ada.Calendar.Time_Of(Year=>Year, Month=>Month, Day=>Day);
    -- assert: date is valid if and only if Time_Error is not raised

    Result.Year := Year;

    -- If it's January, finding the day is easy. If not,
    -- look up days to end of previous month in table
    IF Month = MonthNumber'First THEN -- it's January
      Result.DayOfYear := Day;
    ELSIF IsLeap(Year) THEN        -- leap year
      Result.DayOfYear := LeapDayEndOfMonth(Month-1) + Day;
    ELSE                           -- not leap year
      Result.DayOfYear := NonLeapDayEndOfMonth(Month-1) + Day;
    END IF;

    RETURN Result;

  EXCEPTION

    WHEN Ada.Calendar.Time_Error =>
      RAISE Date_Error;

  END MakeDate;

  FUNCTION Today RETURN Date IS
  -- Finds today's date and returns it as a record of type Date
  -- Today's date is gotten from PACKAGE Ada.Calendar

    RightNow  : Ada.Calendar.Time;           -- holds internal clock value

  BEGIN -- Today

    -- Get the current time value from the computer's clock
    RightNow := Ada.Calendar.Clock;

    -- Extract the current month, day, and year from the time value
    -- and call date constructor to put it in our form
    RETURN MakeDate(Month => Ada.Calendar.Month(RightNow),
                    Day   => Ada.Calendar.Day  (RightNow),
                    Year  => Ada.Calendar.Year (RightNow));

  END Today;

  FUNCTION Year (Right: Date) RETURN YearNumber IS
  BEGIN
    RETURN Right.Year;
  END Year;

  FUNCTION DayOfYear (Right: Date) RETURN JulianDay IS
  BEGIN
    RETURN Right.DayOfYear;
  END DayOfYear;

  FUNCTION Month (Right: Date) RETURN MonthNumber IS

    DayOfYear: JulianDay;
    Result   : MonthNumber;

  BEGIN -- Month

    DayOfYear := Right.DayOfYear;

    -- search table until a quantity > Right.Day is found
    IF IsLeap(Right.Year) THEN          -- leap year
      FOR WhichMonth IN MonthNumber LOOP
        Result := WhichMonth;
        EXIT WHEN LeapDayEndOfMonth(WhichMonth) >= DayOfYear;
      END LOOP;
    ELSE                                -- not leap year
      FOR WhichMonth IN MonthNumber LOOP
        Result := WhichMonth;
        EXIT WHEN NonLeapDayEndOfMonth(WhichMonth) >= DayOfYear;
      END LOOP;
    END IF;

    RETURN Result;

  END Month;

  FUNCTION DayOfMonth (Right: Date) RETURN DayNumber IS

    WhichMonth: MonthNumber;
    Result    : DayNumber;

  BEGIN -- DayOfMonth

    WhichMonth := Month(Right);            -- call routine above
    IF WhichMonth = MonthNumber'First THEN -- it's January
      Result := Right.DayOfYear;
    ELSIF IsLeap(Right.Year) THEN          -- leap year
      Result := Right.DayOfYear - LeapDayEndOfMonth(WhichMonth - 1);
    ELSE
      Result := Right.DayOfYear - NonLeapDayEndOfMonth(WhichMonth - 1);
    END IF;

    RETURN Result;

  END DayOfMonth;

  FUNCTION DayOfWeek  (Right: Date) RETURN WeekDay IS

    SUBTYPE Code IS Natural RANGE 0..6;

    Result    : WeekDay;
    MonthCode : Code;
    Century   : Code;
    ThisMonth : MonthNumber;
    ThisYear  : YearNumber;

  BEGIN -- DayOfWeek

    ThisMonth := Month(Right);
    ThisYear  := Year(Right);

    CASE ThisMonth IS

      WHEN 1 =>  IF IsLeap(ThisYear) THEN
                   MonthCode := 5;
                 ELSE
                   MonthCode := 6;
                 END IF;
      WHEN 2 =>  IF IsLeap(ThisYear) THEN
                   MonthCode := 1;
                 ELSE
                   MonthCode := 2;
                 END IF;
      WHEN 3 =>  MonthCode := 2;
      WHEN 4 =>  MonthCode := 5;
      WHEN 5 =>  MonthCode := 0;
      WHEN 6 =>  MonthCode := 3;
      WHEN 7 =>  MonthCode := 5;
      WHEN 8 =>  MonthCode := 1;
      WHEN 9 =>  MonthCode := 4;
      WHEN 10 => MonthCode := 6;
      WHEN 11 => MonthCode := 2;
      WHEN 12 => MonthCode := 4;

    END CASE;

    IF ThisYear/100 = 19 THEN
      Century := 0;
    ELSE
      Century := 6;
    END IF;

    Result := (((ThisYear REM 100) + ((ThisYear REM 100) / 4)
               + DayOfMonth(Right) +    MonthCode +   Century)
               REM 7) + 1;

    RETURN Result;

  END DayOfWeek;

  -- comparison operators

  FUNCTION "<"  (Left, Right: Date) RETURN Boolean IS
  BEGIN
    IF Left.Year = Right.Year THEN
      RETURN Left.DayOfYear < Right.DayOfYear;
    ELSE
      RETURN Left.Year < Right.Year;
    END IF;
  END "<";

  FUNCTION "<="  (Left, Right: Date) RETURN Boolean IS
  BEGIN
    IF Left.Year = Right.Year THEN
      RETURN Left.DayOfYear <= Right.DayOfYear;
    ELSE
      RETURN Left.Year < Right.Year;
    END IF;
  END "<=";

  FUNCTION ">"  (Left, Right: Date) RETURN Boolean IS
  BEGIN
    IF Left.Year = Right.Year THEN
      RETURN Left.DayOfYear > Right.DayOfYear;
    ELSE
      RETURN Left.Year > Right.Year;
    END IF;
  END ">";

  FUNCTION ">="  (Left, Right: Date) RETURN Boolean IS
  BEGIN
    IF Left.Year = Right.Year THEN
      RETURN Left.DayOfYear >= Right.DayOfYear;
    ELSE
      RETURN Left.Year > Right.Year;
    END IF;
  END ">=";

  -- arithmetic operators

  FUNCTION "+"  (Left: Date; Right: JulianDay) RETURN Date IS

    Result : Date;
    Temp   : Positive;
    YearMax: JulianDay;

  BEGIN

    IF IsLeap(Left.Year) THEN               -- leap year
      YearMax := 366;
    ELSE
      YearMax := 365;
    END IF;

    IF (Right = 366) AND THEN               -- special case, adding
       (NOT IsLeap(Left.Year + 1)) AND THEN -- 366 to Dec 31 when
       Left.DayOfYear = YearMax THEN        -- next year not leap

      Result := (Left.Year + 2, DayOfYear => 1);

    ELSE                                    -- normal case

      Temp := Left.DayOfYear + Right;
      IF Temp > YearMax THEN                    -- into next year
        Result := (Year => Left.Year + 1, DayOfYear => Temp - YearMax);
      ELSE
        Result := (Year => Left.Year,     DayOfYear => Temp);
      END IF;

    END IF;

    RETURN Result;

  EXCEPTION
    WHEN Constraint_Error =>                -- next year out of range
      RAISE Date_Error;
  END "+";

  FUNCTION "+"  (Left: JulianDay; Right: Date) RETURN Date IS
  BEGIN
    RETURN Right + Left; -- use the other "+" above
  END "+";

  FUNCTION "-"  (Left: Date; Right: JulianDay) RETURN Date IS

    Difference: Integer; -- to hold difference between day fields
    Result: Date;

  BEGIN

    IF (Right = 366) AND THEN               -- special case, subtracting
       (NOT IsLeap(Left.Year - 1)) AND THEN -- 366 from Jan 1 when
       Left.DayOfYear = 1 THEN              -- previous year not leap

      Result := MakeDate(Year => Left.Year - 2, Month => 12, Day => 31);

    ELSE

      Difference := Left.DayOfYear - Right;
      IF Difference > 0 THEN  -- result is in the same year
        Result := (Year => Left.Year, DayOfYear => Difference);
      ELSE                    -- result is in previous year
        IF IsLeap(Left.Year - 1) THEN
          Result := (Year => Left.Year-1, DayOfYear => 366+Difference);
        ELSE
          Result := (Year => Left.Year-1, DayOfYear => 365+Difference);
        END IF;
      END IF;

    END IF;

    RETURN Result;

  EXCEPTION
    WHEN Constraint_Error =>                -- previous year out of range
      RAISE Date_Error;
  END "-";

END Dates;