STRUCTURED PROGRAMMING

Yes, Virginia, There Is a Xerox!

Jeff Duntemann K16RA

I damned near choked on my Cheerios the morning the Mercury News announced that Xerox was suing Apple over the Macintosh UI copyrights. Land o' Goshen! There is a Xerox after all! I had just about given up hope.

I have reason to feel specially about this whole situation. I spent ten years at Xerox (1974-1984) when much of their seminal research into user interface concepts took place. I was not directly involved with that research, but I spoke regularly with PARC people who were I played with Smalltalk on the Alto prototype workstation, and I was one of the very first in Xerox MIS/DP trained on the Star workstation in early 1981. Unfortunately, I was one of the very few who ever saw the Star, much less worked on it. Had the Star caught on, Apple's suit against Microsoft would have been laughed out of court.

The Apple vs. Xerox Equation

For those of you who have been living in a Dewar's flask since 1985, let me recap: Last year Apple sued Microsoft and Hewlett Packard, claiming that Microsoft Windows 2.0 and HP New Wave violated Apple's visual copyrights on the Macintosh user interface. Earlier this year, parts of the suit concerning contractual technicalities were dismissed by the judge, leaving only the very large question of whether Windows 2.0 and New Wave indeed infringed on the Macintosh visual copyright.

Now Xerox, in its suit, claims that Apple appropriated the visual copyrights expressed in the Star workstation before the Macintosh was born, and that Xerox, as owner of said copyrights, is due royalties from the use of its interface.

There is a wonderful word to apply here, as ancient and ineluctable as fate itself: Checkmate. No matter which way Apple moves, it loses. Why? Because if Windows 2.0 infringes on the Macintosh, then the Macintosh infringes on the Star. I had to laugh at Apple's immediate protest that it was protecting its expression of the ideas pioneered by Xerox, and not the ideas themselves. Any three-legged salamander blind in one eye could tell you that Windows 2.0 and the Macintosh UI are not identical expressions, but instead are separate expressions of a common philosophy of user interaction. And if the similarities of expression between Windows 2.0 and the Mac are close enough to infringe on the Mac, then the Mac is similar enough to the Star to infringe upon the Star. Poof! Apple loses its claims of ownership.

It gets better. If Apple changes its mind and withdraws its Windows suit, Microsoft and HP have excellent grounds to complain that Apple brought the suit merely to stifle competition, leaving it open to prosecution under antitrust laws.

Couldn't happen to a nicer bunch of guys, eh?

Many people are saying that Xerox can't win its suit against Apple (or anyone else), because it waited far too long to bring the suit to begin with. This is true, but it may not matter. The scrutiny that Xerox will bring to bear on the whole question of who authored what and when will finally lay to rest the pernicious lie that Apple has some kind of ownership of anything with pulldown menus and overlapping windows.

Most people haven't seen the Star, so public opinion hasn't really gone up against Apple. I was there, however, when it happened, and I don't lie about these things: The Xerox Star is closer to the Mac than Windows is. That's the golden equation whose solution will, with luck, put windowing environments in the public domain and end this damned foolishness now and forever.

Thinking About Object Design

Just because you can swing a hammer doesn't mean you can design a house. That's the message that seems to be emerging from our first year as Object Pascal programmers. People are catching on to the syntactic nuances of object-oriented programming. Creating objects and methods is no longer quite the mystery it was in 1989. What does seem to be a mystery is the big picture of it all, containing sizeable questions like, "What should be an object? How do you divide your application into objects, and how do you apportion functionality to the objects in an object hierarchy?" This is the old architecture vs. carpentry duality made new again by the appearance of mass-market OOP languages. So put down that hammer for a bit, and let's talk about larger issues.

What Should be an Object?

Just after Turbo Pascal 5.5 and Quick Pascal appeared last May (almost simultaneously) people predictably began jumping on the OOP concept and taking it to absurd lengths, just for the exhilarating fun of it all. I recall hearing about guys who were creating objects out of ASCII characters, or even integers, because it seemed like the politically correct thing to do at the time.

As the vanguard soon discovered, making Pascal integers and characters into objects bought them almost nothing, and cost like hell. The first lesson we learned about OOP melted into conventional wisdom in record time: Atoms are atoms for a reason. Objects are a means of organizing complexity. Don't go looking for complexity where there isn't any. Molecules have to be made out of something.

My own rule of thumb comes down to this: Don't make objects out of anything that the compiler special-cases. This includes all fundamental types such as Boolean, Char, all numerics, and strings. In a language like Modula-2 where strings have no special status to the compiler, a string object might make sense. Certainly a long string type (in which you can keep more than 255 characters) is a prime candidate for objecthood. Strings, however, have special privileges with respect to text files, Readln and Writeln, and a whole slew of built-in string-handling routines. Furthermore, strings may be returned as function results, a capability I would be extremely reluctant to give up. Encasing a Pascal string within an object wrapper actually adds complexity to an application, and that's not how the game is supposed to be played.

An Exercise in Modeling Reality

When I wrote the tutorials in the Turbo Pascal 5.5 OOP Guide, this sort of question hadn't occurred to me, as I was nearly as new to Object Pascal as the rest of the PC world and was running but a step and a half ahead of the Comprehension Wolves through the whole project. What I knew of objects came to me from Smalltalk, in which everything is an object, not excluding atomic particles such as characters and integers. But my Smalltalk experience kept me in good stead when I had to confront questions from readers like, "How should I divide an application up into objects?" and, "How do I define the boundaries between objects?" There's no easy answer this time, but I've worked with a principle that I consider very effective: Whenever possible, reflect in your objects the divisions and relationships seen in reality. In other words, make your OOP programs simulations even when they're not really simulations.

There I go again, getting Eastern on you. Time to Get Real.

Artifacts, Not Actions

Objects were originally conceived by Simula's architects as models of elements of reality. Reality is not soup. Reality is lumpy, and the lumps, seen apart, are identifiable things with identifiable functions. A lamp is a thing that emits light when you hit its buttons correctly. A carpet keeps the oak floors from getting scraped. Your Peter Norton mug is a mechanism that cooperates with gravity to keep your coffee out of your lap. Every lump in reality has a name and a job, though the necessity of some, like city planners and the rock band Metallica, is seriously open to question.

All of this is second nature to anyone who has survived his or her Terrible Twos, but I remind you of the nature of reality because I want you to apply it to the way you arrange the concept of a program in your mind. Too many programmers make their programs like you'd make soup, by tossing in a pinch of this and half a cup of that and stirring it around until it becomes relatively uniform in color and texture and ceases to be lethal.

Because programming has always been an action-oriented process, programmers often begin by asking what steps constitute the larger action of the program. If you have gotten to this point, the cooking has already begun, and you're halfway to soup already. Never forget this: A program is not an action. A program is an artifact. Once you throw the artifact into your conceptual duck-press and squeeze it down to a statement of action, you've lost a great deal of valuable information about the idea of the program. To recap in a slightly different way: A statement of what a program does is far less useful than a statement of what the program is. The best example has been staring you in the face for years. Tell me first what a spreadsheet program does, and then when you've given up on that, tell me what a spreadsheet is. A spreadsheet does a whole host of things, but what a spreadsheet is is a model of the classic accountant's paper ledger sheet. By now, the lights should be coming on.

Collecting Stamps

Seeing artifacts where you used to see actions is perhaps the most important skill to be learned in picking up OOP. Let's practice a little by identifying a program artifact and giving it reality as an object.

The artifact in question is the humble moment, the point in time at which something happens. In certain kinds of programming, particularly business or financial programming, the time and date when a transaction happens can be almost as important as the transaction itself.

You've seen evidence of a moment in every DOS directory listing: The date and time when the file was last modified. Every directory entry on a DOS disk has a time stamp and a date stamp, which are sometimes lumped together and simply called the time stamp.

That's your artifact. To avoid confusion between its time and date components, (because it incorporates both) I call it a "when stamp." Any time you need to retain an expression of the moment that something happened in your program, you can create a when stamp object for it.

Objects embody both data (the object's "state") and the actions necessary to manipulate that data. The data in a when stamp is straightforward: Some single, unambiguous expression of both clock time and calendar date.

DOS has such an expression in its directory time and date stamps. It makes sense to use what DOS uses, (especially when DOS can be made to do some of our work for us) so let's agree to keep our when stamp as DOS-compatible as possible. The DOS time stamp and date stamp are both 16-bit words, so we can combine them into a single 32-bit type in Object Pascal. The obvious candidate is LongInt, the 32-bit long integer type.

Having the when stamp stored in a single numeric type carries the side benefit that two when stamps can be compared for time priority just by comparing with the numeric greater than and less than operators.

Time values are never negative, so you might begin to worry about the sign bit in a long integer, which is a signed type. As it happens, you will have to worry, but not for a very long time. (More on this later -- nonetheless, I still wish that Pascal had a Long-Word type.)

Once we get the expression of time and date, we'll need methods to manipulate it. But before you start thinking about methods, get your data and its representation in order.

Encoding Time and Date, DOS-style

Understanding how our When object works depends heavily on knowing how DOS encodes its time and date stamps in their 16-bit words.

If we used neat decimal star dates like Captain Kirk, we'd be better off, but alas, a date is a set of three separate numeric quantities: Year, an ascending value that never repeats, month, a value that repeats regularly through a cycle of 12, and day, a value that repeats through a cycle of 28, 29, 30, or 31. This makes date arithmetic ugly unless the date is encoded as a single numeric value. The means is this: Express year, month, and date as binary quantities, then line them up in a single 16-bit word such that the year portion occupies the highest-order bits, the month portion occupies the next-highest order bits, and the day portion occupies the lowest-order bits. Done this way, two date stamps with different years will always compare correctly regardless of month or date; two stamps with identical years will always compare correctly on the basis of month, regardless of date, and so on. Which bits relate to year, month, and date is shown in Figure 1.

Note that the year is encoded in a peculiar (and I think unfortunate) way: As an offset from the year 1980. 1980 is thus year 0, 1981 year 1, and so on. This allows the year to be encoded in only a few bits, leaving plenty of room for the month and day in a 16-bit word. The downside is that you can't encode your birthday as a time stamp, since you were probably born considerably prior to 1980. (I was, and have the hairline to prove it.) Seriously, this limits the use of when stamps to things that occur in true calendar time while the computer is in use, and not to encode events that happened long ago. I was a little concerned about the use of a signed type to hold the when stamp. At some point, the bit encoding of the time and date will set the high bit in the long integer, turning the value negative in the eyes of the run-time library. This blows any possibility of valid comparisons out the window, because a later value that is negative will be seen as less (and hence earlier) than any positive stamp. However, when I did the math I found that the stamp does not turn negative until December 31, 2043, by which time I will either be dead or perfecting zero-G lovemaking techniques out past the orbit of Mars.

17 Pounds of Kitty Litter in a 16-pound Bag

The DOS time stamp presents a problem. No matter how you encode the hours, minutes, and seconds (and forget hundredths here) you end up with a minimum of 17 bits: Six for minutes (0 - 59); six for seconds (0 - 59), and five for hours (0 - 23).

You can usually cram 17 pounds of kitty litter into a 16-pound bag by shaking things around a little. Not so in the bit game -- we pack 'em tight. Something has to give a little, so what we do is ignore every other second. This cuts the number of bits required to encode seconds to five, and 16 bits will suddenly hold the stamp. See Figure 2.

Note the way the stamp is encoded in the figure: Whereas the hours and minutes values are shifted left to move them toward the high end of the word, the seconds value is shifted right by one bit, which bumps that bit off into Peoria and out of our hair. Truncating the seconds by half means that two events occurring less than two seconds apart will probably resolve to identical time stamps, depending on their synchronization with the system clock. You have to keep this in mind and avoid designing time stamps encoded this way into applications where stampable events happen in quick succession. Note also that your seconds value will always be an even number, because the 1 bit that can make it odd is not stored in the stamp.

To Represent or Calculate?

At the heart of it, then, a When object consists of a long integer time/date stamp encoded as explained earlier. If you only needed to compare two stamps for time order, this would be enough. However, you may need to access the seconds value separately, or the hours or months, and you may want to display time or date or both in some generally understood ASCII form. There are two ways to do this:

The first option buys speed at the expense of space, and the second buys space at the expense of speed. Which do you use? Well, do you need more speed or space?

I'm not just being glib here. There's no single answer. You as the object's designer have to keep track of your own particular needs. I tend to write small programs, and I like fast ones, so my own first impulse is to throw memory at problems to make them faster. This is the design choice I made in implementing the When object, as shown in Listing One, page 150.

The When object has as its central data item the WhenStamp long integer field. It also has separate fields for year, month, day, day of week, hours, minutes, and seconds. I added an ASCII representation of time identical to that used in the DOS DIR command, as well as two ASCII date representations: One in the form yy/mm/dd, and the other in the form "Thursday, June 29, 1989." This adds 94 bytes to the size of the object, but I did it with eyes open and decided that the benefits were worth the cost. There was one exception. I chose not to provide a separate 16-bit time and date stamp, as I had originally considered doing, because returning one half or the other of a long integer can be done without any significant calculation overhead, just by typecasting. Notice the definition of WhenUnion, private to the unit:

  WhenUnion =
        RECORD
               TimePart: Word;
               DatePart: Word;
        END;

WhenUnion is the same size as Long-Int, so you can use a value typecast to access either the time or date portion of the combined time/date stamp:

TimeStamp := WhenUnion(WhenStamp).TimePart;

It's that easy, and saves you 4 bytes of memory at the cost of no calculation at all. I like that sort of deal -- kind of like insider trading in Silicon Valley.

Choosing Your Methods

Once you've decided what your object is, you can begin working out what it does; that is, design its suite of methods. I wanted my when stamp object to be easily updated, so I provided numerous methods for changing the value of the WhenStamp field. All the methods beginning with Put change the value of WhenStamp, and all the methods beginning with Get return some value from the object. This is a good naming convention when designing objects, and I recommend it. The PutNow method is simple but extremely useful: It reads the current value of the PC's clock/calendar and applies the current time and date to the when stamp. The other Put methods alter the value of the when stamp by providing a new value for either the whole stamp (PutWhenStamp) or some component value of the stamp, such as year, month, or hours. How many such methods you build into an object depends on how you intend to use the object. My recommendation is to build more into the object than you may need, and reexamine the design some time down the road. You can always strip out what you haven't used ... but you never know when some flash of insight will allow you to find a use for a method that you hadn't imagined when you originally wrote it!

The Encapsulation Question

The "pure" object-oriented languages such as Smalltalk and Actor both allow and enforce total encapsulation of an object's data. You cannot directly reference an object's data from outside the object or its descendants. Object Pascal allows total encapsulation, in that you can voluntarily provide a separate method to return the value of each field in the object. Nothing enforces this, however -- you can reference any field in any object from anywhere in your program. Purists will say, "So what?" Give 'em the methods anyway, and say hands off the data. Such hardnosedness gives you the considerable benefit of being able to change the actual representation of the data within an object without breaking the code that uses the object. For example, if I forbade direct references to the data inside When, I could come up with my own time stamp format that was truly universal and did not ignore the existence of years prior to 1980 as well as every other tick of the clock.

If you needed to provide stamps for old financial records, birthdays, and such (as in a life insurance application) you'd be far better off going this route. My own need for time stamps, however, was limited to stamping transactions occurring at the current moment, so I made the decision not to add the additional level of complication to When. I also wanted to retain full compatibility with the DOS directory time and date stamps, and the easiest way to do that is simply to represent the time and date the same way DOS does. Given these assumptions, I created When to allow direct read access to all the data fields.

Note that I said read. Writing directly to the fields is not a good idea, because all the fields but WhenStamp are actually component values of WhenStamp, and if you were to change the Month field without changing WhenStamp, the two would be "out of sync." The Put methods were designed so that changing any part of the time stamp recalculates all component values relating to that part of the time stamp. For example, changing the date half of the when stamp recalculates the Year, Month, Day, DayOfWeek, DateString, and LongDateString fields, without affecting Hours, Minutes, Seconds, or TimeString.

You need to be aware of such dependencies when deciding how to allow updating of an object's data. Calculating component values from a master field such as WhenStamp whenever they are needed avoids problems like this, if you can tolerate the loss of performance.

Private Parts

For reasons unknown, many newcomers to OOP get the notion in their noggins that all of an object's processing must be confined to its methods, and that while methods can call one another, methods somehow cannot call other procedures and functions unrelated to the object. Not true! A method can call any routine within its scope, just as any nonmethod procedure or function can. Furthermore, there is no way to declare a "private" method in Object Pascal. A private method would be one that could be called by other methods within the object, but that could not be accessed from outside the object. If there is any private processing to be done by an object, you can do what I've done with When and place the object definition in a unit -- with private procedures and functions fully declared and defined within the implementation section. Such procedures and functions may be called by the object's methods but are not available to anyone outside the unit itself. Similarly, the MonthTags and DayTags constants exist for the convenience of the object and are not needed by users of the object, so I've declared them privately, within the implementation section. Ditto with WhenUnion. As long as you provide functions to return separate 16-bit values for the time stamp and date stamp portions of the when stamp (as I did with GetTimeStamp and GetDateStamp), there's no need to give the user the WhenUnion definition. Half the battle of programming is masking complexity, so keep an object's private parts under a bushel basket where they won't be stepped on or misused by the ignorant and the unwary.

One of the private routines is a day-of-the-week calculator using an algorithm called "Zeller's Congruence." I've had this routine in my files for so long I no longer remember who coded it up and gave it to me, and I categorically do not understand how it works ... but it does work.

Homework Assignments

That's how I went about designing a very simple object. To make sure it sinks in, do the following things for practice:

Object Design Recap

To summarize:

An object is an artifact, not an action. Look at the way the universe is broken down into components, and learn to think of your programs as built of component parts in much that same way.

Once you've identified such a software artifact, decide how to represent its data first. Only once you have the data design down should you begin to ponder what methods it needs to serve that data. Remember, Data is Boss in object land.

It's possible and often desirable to disallow any direct access to an object's data. This frees you to change the way the data is stored within the object without breaking code that uses the object. Remember, however, that this can add significant performance handicaps to code that uses your objects. Keep the tradeoffs in mind, but (as the cricket kept saying) always let your conscience be your guide.

Also remember that this is just a first lesson. We haven't even begun to address the issues presented by inheritance or, lord knows, polymorphism. All in good time.

Have You Seen this Book?

Maybe you all can help me out a little. My newest book, Assembly Language From Square One, (Scott, Foresman & Company) has been off the presses for some time, and I have yet to see it in any store. If you have seen it anywhere, drop me a postcard at DDJ and tell me what store is carrying it. The computer book distribution system seems to be breaking down in recent months, much as it did in 1984. Huge numbers of titles, most fit only to hang in a Tennessee outhouse, have been pouring into the retail channel lately, and it's gotten me (and some other well-known authors) more than a little worried. We may not be able to change the system, but we'd at very least like to know what books are going where.

Thanks.

Write to Jeff Duntemann on MCI Mail as JDuntemann, or on CompuServe to ID 76117, 1426.

STRUCTURED PROGRAMMING COLUMN by Jeff Duntemann [LISTING ONE]




{---------------------------------------------------}
{                     TIMEDATE                      }
{                                                   }
{ A Time-and-date stamp object for Turbo Pascal 5.5 }
{                                                   }
{                           by Jeff Duntemann       }
{                           Last update 12/23/89    }
{                                                   }
{ NOTE: This unit should be good until December 31, }
{ 2043, when the long integer time/date stamp turns }
{ negative.  HOWEVER, the Zeller's Congruence       }
{ algorithm shown here fails at the end of the 20th }
{ century.  I should be able to figure out the fix  }
{ by then...                                        }
{---------------------------------------------------}

UNIT TimeDate;

INTERFACE

USES DOS;

TYPE
  String9  = STRING[9];
  String20 = STRING[20];
  String50 = STRING[50];

  When =
    OBJECT
      WhenStamp      : LongInt;      { Combined time/date stamp }
      TimeString     : String9;      { i.e., "12:45a"           }
      Hours,Minutes,Seconds : Word;  { Seconds is always even!  }
      DateString     : String20;     { i.e., "06/29/89"         }
      LongDateString : String50;     { i.e., "Thursday, June 29, 1989" }
      Year,Month,Day : Word;
      DayOfWeek      : Integer;      { 0=Sunday, 1=Monday, etc. }
      FUNCTION GetTimeStamp : Word;  { Returns DOS-format time stamp }
      FUNCTION GetDateStamp : Word;  { Returns DOS-format date dtamp }
      PROCEDURE PutNow;
      PROCEDURE PutWhenStamp(NewWhen  : LongInt);
      PROCEDURE PutTimeStamp(NewStamp : Word);
      PROCEDURE PutDateStamp(NewStamp : Word);
      PROCEDURE PutNewDate(NewYear,NewMonth,NewDay : Word);
      PROCEDURE PutNewTime(NewHours,NewMinutes,NewSeconds : Word);
    END;


IMPLEMENTATION

{ Keep in mind that all this stuff is PRIVATE to the unit! }

CONST
  MonthTags : ARRAY [1..12] of String9 =
    ('January','February','March','April','May','June','July',
     'August','September','October','November','December');
  DayTags   : ARRAY [0..6] OF String9 =
    ('Sunday','Monday','Tuesday','Wednesday',
     'Thursday','Friday','Saturday');

TYPE
  WhenUnion =
    RECORD
      TimePart : Word;
      DatePart : Word;
    END;

VAR
  Temp1 : String50;
  Dummy : Word;

{ Some utility routines private to this unit: }

FUNCTION CalcTimeStamp(Hours,Minutes,Seconds : Word) : Word;

BEGIN
  CalcTimeStamp := (Hours SHL 11) OR (Minutes SHL 5) OR (Seconds SHR 1);
END;


FUNCTION CalcDateStamp(Year,Month,Day : Word) : Word;

BEGIN
  CalcDateStamp := ((Year - 1980) SHL 9) OR (Month SHL 5) OR Day;
END;


PROCEDURE CalcTimeString(VAR TimeString : String9;
                         Hours,Minutes,Seconds : Word);

VAR
  Temp1,Temp2 : String9;
  AMPM        : Char;
  I           : Integer;

BEGIN
  I := Hours;
  IF Hours = 0 THEN I := 12;   { "0" hours = 12am }
  IF Hours > 12 THEN I := Hours - 12;
  IF Hours > 11 THEN AMPM := 'p' ELSE AMPM := 'a';
  Str(I:2,Temp1); Str(Minutes,Temp2);
  IF Length(Temp2) < 2 THEN Temp2 := '0' + Temp2;
  TimeString := Temp1 + ':' + Temp2 + AMPM;
END;


PROCEDURE CalcDateString(VAR DateString : String20;
                         Year,Month,Day : Word);
BEGIN
  Str(Month,DateString);
  Str(Day,Temp1);
  DateString := DateString + '/' + Temp1;
  Str(Year,Temp1);
  DateString := DateString + '/' + Copy(Temp1,3,2);
END;


PROCEDURE CalcLongDateString(VAR LongdateString : String50;
                             Year,Month,Date,DayOfWeek : Word);
VAR
  Temp1 : String9;

BEGIN
  LongDateString := DayTags[DayOfWeek] + ', ';
  Str(Date,Temp1);
  LongDateString := LongDateString +
    MonthTags[Month] + ' ' + Temp1 + ', ';
  Str(Year,Temp1);
  LongDateString := LongDateString + Temp1;
END;


{---------------------------------------------------------------------}
{ This calculates a day of the week figure, where 0=Sunday, 1=Monday, }
{ and so on, given the year, month, and day.  The year may be passed  }
{ as either "1989" or "89" but *not* as 1980-relative, or "9".  Also  }
{ note that this particular algorithm turns into a pumpkin in 2000.   }
{ BTW, don't ask me to explain how this crazy thing works.  I haven't }
{ the foggiest notion.  If I ever meet Mr. Zeller, I'll ask him.      }
{---------------------------------------------------------------------}

FUNCTION CalcDayOfWeek(Year,Month,Day : Word) : Integer;

VAR
  Century,Leftovers,Holder : Integer;

BEGIN
  { First test for error conditions on input values: }
  IF (Year < 0)  OR
     (Month < 1) OR (Month > 12) OR
     (Day < 1)   OR (Day > 31) THEN
     CalcDayOfWeek := -1  { Return -1 to indicate an error }
  ELSE
    { Do the Zeller's Congruence calculation: }
    BEGIN
      IF Year < 100 THEN Inc(Year,1900);
      Dec(Month,2);
      IF (Month < 1) OR (Month > 10) THEN
        BEGIN
          Dec(Year,1);
         Inc(Month,12);
        END;
      Century   := Year DIV 100;
      Leftovers := Year MOD 100;
      Holder    := (Trunc(Int(2.6 * Month - 0.2)) + Day +
                    Leftovers + (Leftovers DIV 4) +
                    (Century DIV 4) - Century - Century) MOD 7;
      IF Holder < 0 THEN
        Inc(Holder,7);
      CalcDayOfWeek := Holder;
    END;
END;


{***************************************}
{ Method implementations for type When: }
{***************************************}


{---------------------------------------------------------------------}
{ There will be many times when an individual date or time stamp will }
{ be much more useful than a combined time/date stamp.  These simple  }
{ functions return the appropriate half of the combined long integer  }
{ time/date stamp without incurring any calculation overhead.  It's   }
{ done with a simple value typecast:                                  }
{---------------------------------------------------------------------}

FUNCTION When.GetTimeStamp : Word;

BEGIN
  GetTimeStamp := WhenUnion(WhenStamp).TimePart;
END;


FUNCTION When.GetDateStamp : Word;

BEGIN
  GetDateStamp := WhenUnion(WhenStamp).DatePart;
END;


{---------------------------------------------------------------------}
{ To fill a When record with the current time and date as maintained  }
{ by the system clock, execute this method:                           }
{---------------------------------------------------------------------}

PROCEDURE When.PutNow;

BEGIN
  { Get current clock time.  Note that we ignore hundredths figure: }
  GetTime(Hours,Minutes,Seconds,Dummy);
  { Calculate a new time stamp and update object fields: }
  PutTimeStamp(CalcTimeStamp(Hours,Minutes,Seconds));
  GetDate(Year,Month,Day,Dummy); { Get current clock date }
  { Calculate a new date stamp and update object fields: }
  PutDateStamp(CalcDateStamp(Year,Month,Day));
END;


{---------------------------------------------------------------------}
{ This method allows us to apply a whole long integer time/date stamp }
{ such as that returned by the DOS unit's GetFTime procedure to the   }
{ When object.  The object divides the stamp into time and date       }
{ portions and recalculates all other fields in the object.           }
{---------------------------------------------------------------------}

PROCEDURE When.PutWhenStamp(NewWhen  : LongInt);

BEGIN
  WhenStamp := NewWhen;
  { We've actually updated the stamp proper, but we use the two }
  { "put" routines for time and date to generate the individual }
  { field and string representation forms of the time and date. }
  { I know that the "put" routines also update the long integer }
  { stamp, but while unnecessary it does no harm.               }
  PutTimeStamp(WhenUnion(WhenStamp).TimePart);
  PutDateStamp(WhenUnion(WhenStamp).DatePart);
END;


{---------------------------------------------------------------------}
{ We can choose to update only the time stamp, and the object will    }
{ recalculate only its time-related fields.                           }
{---------------------------------------------------------------------}

PROCEDURE When.PutTimeStamp(NewStamp : Word);

BEGIN
  WhenUnion(WhenStamp).TimePart := NewStamp;
  { The time stamp is actually a bitfield, and all this shifting left }
  { and right is just extracting the individual fields from the stamp:}
  Hours := NewStamp SHR 11;
  Minutes := (NewStamp SHR 5) AND $003F;
  Seconds := (NewStamp SHL 1) AND $001F;
  { Derive a string version of the time: }
  CalcTimeString(TimeString,Hours,Minutes,Seconds);
END;


{---------------------------------------------------------------------}
{ Or, we can choose to update only the date stamp, and the object     }
{ will then recalculate only its date-related fields.                 }
{---------------------------------------------------------------------}

PROCEDURE When.PutDateStamp(NewStamp : Word);

BEGIN
  WhenUnion(WhenStamp).DatePart := NewStamp;
  { Again, the date stamp is a bit field and we shift the values out  }
  { of it: }
  Year := (NewStamp SHR 9) + 1980;
  Month := (NewStamp SHR 5) AND $000F;
  Day := NewStamp AND $001F;
  { Calculate the day of the week value using Zeller's Congruence:    }
  DayOfWeek := CalcDayOfWeek(Year,Month,Day);
  { Calculate the short string version of the date; as in "06/29/89": }
  CalcDateString(DateString,Year,Month,Day);
  { Calculate a long version, as in "Thursday, June 29, 1989": }
  CalcLongDateString(LongdateString,Year,Month,Day,DayOfWeek);
END;


PROCEDURE When.PutNewDate(NewYear,NewMonth,NewDay : Word);

BEGIN
  { The "boss" field is the date stamp.  Everything else is figured }
  { from the stamp, so first generate a new date stamp, and then    }
  { (odd as it may seem) regenerate everything else, *including*    }
  { the Year, Month, and Day fields: }
  PutDateStamp(CalcDateStamp(NewYear,NewMonth,NewDay));
  { Calculate the short string version of the date; as in "06/29/89": }
  CalcDateString(DateString,Year,Month,Day);
  { Calculate a long version, as in "Thursday, June 29, 1989": }
  CalcLongDateString(LongdateString,Year,Month,Day,DayOfWeek);
END;


PROCEDURE When.PutNewTime(NewHours,NewMinutes,NewSeconds : Word);

BEGIN
  { The "boss" field is the time stamp.  Everything else is figured }
  { from the stamp, so first generate a new time stamp, and then    }
  { (odd as it may seem) regenerate everything else, *including*    }
  { the Hours, Minutes, and Seconds fields: }
  PutTimeStamp(CalcTimeStamp(NewHours,NewMinutes,NewSeconds));
  { Derive the string version of the time: }
  CalcTimeString(TimeString,Hours,Minutes,Seconds);
END;


END.





[LISTING TWO]



PROGRAM TimeTest;

USES Crt,TimeDate;

VAR
  Now : When;

BEGIN
  Write('At the tone, it will be exactly ');
  Delay(1000);
  Now.PutNow;
  Sound(1000); Delay(100); NoSound;
  WITH Now DO Writeln(TimeString,'m on ',LongDateString,'.');
  Readln
END.