Previous | Next | Table of Contents | Index | Program List | Copyright

10.2 System Structures: Using Package Ada.Calendar as an ADT

Before learning to write ADTs, it is helpful to study an existing one in detail. We have used the predefined package Ada.Calendar in a number of previous examples in this book, without paying much attention to the fact that Ada.Calendar serves as an excellent example of a well-thought-out ADT. It happens that Ada.Calendar is always provided with an Ada compiler (indeed, it must be provided), and our own ADTs will often be written in the style of Ada.Calendar. Systematic study of Ada.Calendar will teach you a lot about the design of ADT's and prepare you to start writing your own.

Resources Provided by Ada.Calendar

Package Ada.Calendar uses a type Duration, which is actually defined in Standard, not here. Duration is a measure of elapsed time: One duration unit is exactly equal to one elapsed second. Note that this is not the same as the time of day. Time of day, often called "wall clock" time in computing applications, gives a particular instant of time: 12:05 P.M. on January 25, 1980, for example. Duration measures the passage of time: Two minutes, or 120 seconds, elapse between 12:05 P.M. and 12:07 P.M. on the same day. Time of day is one of the resources provided by Ada.Calendar, in the form of a type Time.

The purpose of Ada.Calendar is to provide a useful number of operations on time-of-day values. Figure 10.3 shows the entire specification of package Ada.Calendar, which we have copied straight from the Ada standard, making changes only in the formatting and comments in the specification.

Figure 10.3.
Full Specification of Package Ada.Calendar

PACKAGE Ada.Calendar IS

  -- standard Ada package, must be supplied with compilers
  -- provides useful services for dates and times

  -- type definitions

  TYPE Time IS PRIVATE;

  SUBTYPE Year_Number  IS Integer  RANGE 1901..2099;
  SUBTYPE Month_Number IS Integer  RANGE 1..12;
  SUBTYPE Day_Number   IS Integer  RANGE 1..31;
  SUBTYPE Day_Duration IS Duration RANGE 0.0..86_400;
  -- Duration is a predefined (standard) fixed-point type;
  -- Day_Duration range is the number of seconds in 24 hours

  -- constructor operation

  -- constructs a Time value from its components; note that the
  -- default for Seconds is 0.0, so if Seconds value isn't given,
  -- the time is assumed to be at midnight

  FUNCTION Time_Of (Year : Year_Number;
                   Month : Month_Number;
                   Day : Day_Number;
                   Seconds  : Day_Duration:=0.0) RETURN Time;

  -- selector operations

  FUNCTION Year  (Date : Time)  RETURN Year_Number;
  FUNCTION Month (Date : Time)  RETURN Month_Number;
  FUNCTION Day  (Date : Time)   RETURN Day_Number;
  FUNCTION Seconds (Date : Time) RETURN Day_Duration;

  -- splits a Time value into its component parts

  PROCEDURE Split (Date    : IN  Time;
                  Year     : OUT Year_Number;
                  Month    : OUT Month_Number;
                  Day : OUT Day_Number;
                  Seconds  : OUT Day_Duration);

  -- read the computer's clock to get the current time of day

  FUNCTION Clock RETURN Time;

  -- arithmetic and comparison operations

  -- note that only the "sensible" operations are defined.
  -- this is possible because Time is a private type with no
  -- predefined operations except := and =

  FUNCTION "<"  (Left, Right : Time)      RETURN Boolean;
  FUNCTION "<=" (Left, Right : Time)      RETURN Boolean;
  FUNCTION ">"  (Left, Right : Time)      RETURN Boolean;
  FUNCTION ">=" (Left, Right : Time)      RETURN Boolean;

  FUNCTION "+" (Left : Time;     Right : Duration)  RETURN Time;
  FUNCTION "+" (Left : Duration; Right : Time)      RETURN Time;
  FUNCTION "-" (Left : Time;     Right : Duration)  RETURN Time;
  FUNCTION "-" (Left : Time;     Right : Time)      RETURN Duration;

  -- exported exceptions

  -- Time_Error is raised by Time_Of if its actual parameters
  -- don't form a proper date, and also by "+" and "-" if they
  -- can't return a date whose year number is in range,
  -- or if "-" can't return a value that is in the
  -- range of the type Duration.

  Time_Error : EXCEPTION;

PRIVATE

  -- implementation-dependent (the details depend on the computer's
  -- internal clock structure, and are not important because Ada.Calendar
  -- provides all the operations we need)

END Ada.Calendar;

The first line of code in Ada.Calendar is a partial type definition:

    TYPE Time IS PRIVATE;
The definition is completed at the bottom of the figure, below the word PRIVATE. Ada provides certain rules for the use of private types. First, variables of the type may be declared; for example,
    MyBirthDay : Ada.Calendar.Time;
    LastWeek : Ada.Calendar.Time;
are permissible declarations. Second, one variable of a private type may be assigned the value of another variable of the same type, and two variables of a private type may be compared for equality or inequality. For example,
    LastWeek := MyBirthday;
    IF LastWeek /= MyBirthday THEN...
are both valid operations. No other operations are predefined. Indeed, one of the purposes of private types is to allow the writer of a package to define exactly those operations he or she deems appropriate.

Following the definition of Time are four subtype declarations. Three of these give the acceptable ranges for year, month, and day values; the fourth specifies the number of duration units, or seconds, in a 24-hour day: 86,400. The Ada standard says that any time value from midnight on January 1, 1901, to midnight on December 31, 2099, must be treated as a unique valid value by Ada.Calendar; furthermore two consecutive time values must not differ by more than 20 milliseconds.

Time is treated as a private type for two reasons. First, the internal representation of a time value is dependent on the form the hardware clock uses for time values. Second, not all operations make sense for time values. If Time were treated as just some sort of integer value, for example, we could multiply two times together; however, multiplying 3 P.M. by 4 P.M. is meaningless! Making Time a private type allowed the designers of Ada to control precisely the set of sensible operations on Time values. What are these operations?

To use time values well, the client program must be able to create time values, for example, by supplying a month, a day, and a year. Ada.Calendar provides a function Time_Of for this purpose. An operation like Time_Of, which constructs a value of the new type from its component parts, is called a constructor operation. There are also five selector operations, Year, Month, Day, Seconds, and Split, which allow the client program to select various components of a time value in a useful form (integer and duration values). The first four of these operations are functions that return individual components; Split is a procedure that produces all four components in a single call. The next operation is Clock, which returns the current time of day as a Time value.

We know from the discussion above that each time value is unique; also, time values are monotonically increasing; that is, as time progresses, each new value is greater than the previous one. This conforms to our real-world view of time and the concepts of "earlier" and "later." Because time is monotonically increasing--totally ordered is another mathematical term with similar meaning--we can confidently compare two values. As for any private type, Ada already provides equality and inequality operators, so Ada.Calendar provides the others: <, <=, >, and >=. Notice that these are specified as functions; they can be used in function form, for example,

    IF Ada.Calendar."<="(RightNow, AnotherTime) THEN...
or as normal infix operators, for example,
    IF RightNow <= AnotherTime THEN... 
(The latter form is permitted only if a USE Ada.Calendar appears at the top of the program.)

To do computations with time values, Ada provides some arithmetic operations. Only those operations that make sense are provided by the package, as follows:

    FUNCTION "+" (Left : Time; Right : Duration) RETURN Time;
    FUNCTION "+" (Left : Duration; Right : Time) RETURN Time;
    FUNCTION "-" (Left : Time; Right : Duration) RETURN Time;
    FUNCTION "-" (Left : Time; Right : Time)     RETURN Duration;
For example, adding two times together makes no sense (what does it mean to add 3 P.M. to 4 P.M.?); it is therefore not possible to do so with Ada.Calendar operations. It does make sense to add a duration to a time; for example, 3 P.M. plus one hour is 4 P.M. The two "+" operations are provided to ensure that the time value can appear on the right or the left. Finally, the subtraction operations are sensible ones: Subtracting 3 P.M. from 4 P.M. gives one elapsed hour; subtracting two hours from 7 A.M. gives 5 A.M. These operations serve as an excellent example of the usefulness of private types in ensuring that a client cannot perform meaningless operations or operations that do not make physical sense.

The final line of code in the specification defines an exception Time_Error. This exception is raised whenever a Time_Of call would return an invalid time value, for example, if 2 (February), 30, and 1990 were supplied as parameters: February 30 does not exist. Ada.Calendar also understands leap years, so Time_Error would be raised if 2, 29, and 1995 were supplied to Time_Of, because 1995 is not a leap year. Time_Error is also raised if the subtraction operator is given two times that are so far apart that the computer cannot represent the number of elapsed seconds that separate them.


Case Study: World Times

As an example of the use of Ada.Calendar, consider the problem of determining the time in other time zones around the world.

PROBLEM SPECIFICATION

Write a program to allow the user to enter the abbreviation of one of a set of cities and display the current time in that city.

ANALYSIS

Given a table of city codes and the number of time zones separating each from the user's home time zone, we can use Ada.Calendar to find the current local time, then add or subtract the appropriate number of seconds to find the time elsewhere.

Data Requirements

Problem Inputs

City : Cities

DESIGN

Algorithm

1. Read the value of City from the keyboard.

2. Find the current local time.

3. Find the time in City by using the time zone offset table.

4. Display the local time and the time in City.

TEST PLAN

Since you can easily look up the number of hours of offset, test the program for the different allowed cities, and be certain that the time is computed properly. Also test, as usual, for invalid input, that is, a token that is not a city code.

IMPLEMENTATION

Program 10.1 gives the program for World_Time. Type Cities gives a list of city names or abbreviations; a procedure ReadCity reads a city name robustly, refusing to permit an invalid city to be entered; and a procedure DisplayTime is used to display a time value in a useful form. DisplayTime is a modification of TimeOfDay, developed earlier in Program 7.3.

Program 10.1
Time Around the World

WITH Ada.Text_IO;
WITH Ada.Integer_Text_IO;
WITH Ada.Calendar;
PROCEDURE World_Time IS
------------------------------------------------------------------------
--| Finds the current time in any of several time zones
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: July 1995                                     
------------------------------------------------------------------------
  
  TYPE Cities IS (Paris, London, Rio, Caracas, DC,
                  Chicago, Denver, Seattle, Honolulu);

  PACKAGE City_IO IS NEW Ada.Text_IO.Enumeration_IO(Cities);

  TYPE TimeDiffs IS ARRAY (Cities) OF Integer;

  -- table of time differences from DC; modify this table if you are
  -- not located in the Eastern U.S. time zone
  Offsets : CONSTANT TimeDiffs := 
    (Paris => +6, London => +5, Rio => +2, Caracas => -1, DC => 0, 
     Chicago => -1, Denver => -2, Seattle => -3,  Honolulu => -5);

  TimeHere  : Ada.Calendar.Time;
  TimeThere : Ada.Calendar.Time;
  There     : Cities;

  FUNCTION AdjustTime(T: Ada.Calendar.Time; City: Cities;
                      OffsetTable: TimeDiffs) 
    RETURN Ada.Calendar.Time IS

  -- given a time value, finds the corresponding time 
  -- in a given time zone

  BEGIN -- AdjustTime

    RETURN Ada.Calendar."+"(T, Duration(OffsetTable(City) * 3600));

  END AdjustTime;


  PROCEDURE ReadCity(City : OUT Cities) IS

  -- reads a city name from the terminal, robustly

  BEGIN -- ReadCity

    LOOP
      BEGIN     -- exception handler block
        Ada.Text_IO.Put_Line
          (Item => "Please enter one of the following:");
        Ada.Text_IO.Put_Line
          (Item => "Paris, London, Rio, Caracas, DC, ");
        Ada.Text_IO.Put
          (Item => "Chicago, Denver, Seattle, Honolulu >");

        City_IO.Get(Item => City);
        EXIT;   -- good input data
      EXCEPTION -- bad input data
        WHEN Ada.Text_IO.Data_Error =>
          Ada.Text_IO.Skip_Line;
          Ada.Text_IO.Put
            (Item => "Invalid city name; please try again.");
          Ada.Text_IO.New_Line;
      END;      -- exception handler block
    END LOOP;

  END ReadCity;

  
  PROCEDURE DisplayTime(T: Ada.Calendar.Time) IS

  -- displays a Ada.Calendar.Time value in hh:mm:ss form

    TYPE DayInteger IS RANGE 0..86400;

    SecsPastMidnight : DayInteger;  -- could be larger than 32767
    MinsPastMidnight : Natural;
    Secs             : Natural;
    Mins             : Natural;
    Hrs              : Natural;

  BEGIN -- DisplayTime

    SecsPastMidnight := DayInteger(Ada.Calendar.Seconds(T));
    MinsPastMidnight := Natural(SecsPastMidnight/60);
    Secs             := Natural(SecsPastMidnight REM 60);
    Mins             := MinsPastMidnight REM 60;
    Hrs              := MinsPastMidnight / 60;

    Ada.Integer_Text_IO.Put (Item => Hrs, Width => 1);
    Ada.Text_IO.Put (Item => ':');
    IF Mins < 10 THEN
      Ada.Text_IO.Put (Item => '0');
    END IF;
    Ada.Integer_Text_IO.Put (Item => Mins, Width => 1);
    Ada.Text_IO.Put (Item => ':');
    IF Secs < 10 THEN
      Ada.Text_IO.Put (Item => '0');
    END IF;
    Ada.Integer_Text_IO.Put (Item => Secs, Width => 1);

  END DisplayTime;

BEGIN -- World_Time

  ReadCity(City => There);
  TimeHere := Ada.Calendar.Clock;
  TimeThere := AdjustTime
    (T=>TimeHere, City=>There, OffsetTable=>Offsets);

  Ada.Text_IO.Put(Item => "Current local time is ");
  DisplayTime(T => TimeHere);
  Ada.Text_IO.New_Line;
  Ada.Text_IO.Put(Item => "Current time in ");
  City_IO.Put(Item => There, Width => 1);
  Ada.Text_IO.Put(Item => " is ");
  DisplayTime(T => TimeThere);
  Ada.Text_IO.New_Line;

END World_Time;
Sample Run
Please enter one of the following:
Paris, London, Rio, Caracas, DC, 
Chicago, Denver, Seattle, Honolulu >xxx
Invalid city name; please try again.
Please enter one of the following:
Paris, London, Rio, Caracas, DC, 
Chicago, Denver, Seattle, Honolulu >paris
Current local time is 22:46:44
Current time in PARIS is 4:46:44

The function AdjustTime does the work of computing the new time. It contains a table of offsets, or number of time zones away from local time. Ada.Calendar."+" is used to add or subtract the appropriate number of seconds:

    RETURN Ada.Calendar."+"(T, Duration(Offsets(City) * 3600));
The array Offsets gives the time zone differences; the number of seconds is computed by multiplying the number of time zones by 3600 (the number of seconds in an hour), then converting to type Duration.

It is important to note that on most computers, Ada.Calendar.Clock gives the current local time, not some universal time value. The array Offsets is initialized to the offsets from the authors' home time zone, the Eastern zone; you will have to change the table values if you are running this program in another zone. An exercise suggests an approach to solving this problem in a more robust manner.


Exercises for Section 10.2

Programming

  1. Write a program that tests the operations in package Ada.Calendar. Try to add two times together, for example. Also investigate what happens when Time_Of is called with parameters that would lead to an invalid time value (February 30, for example, or February 29, 1991). Does Ada.Calendar behave correctly, as the specification suggests?


Previous | Next | Table of Contents | Index | Program List | Copyright

Copyright © 1996 by Addison-Wesley Publishing Company, Inc.