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.
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.
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.
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
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.
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?
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.