STRUCTURED PROGRAMMING

If You Care

Jeff Duntemann K16RA/7

No matter how you slice it or dice it, '76 was a good year. America, the world's best hope for human freedom, marked its 200th anniversary. I married Carol and stopped subsisting on Rice-A-Roni and Golden Grahams cereal. I wire-wrapped my first computer, a COSMAC ELF with 256 bytes of RAM. I wrote my first operating system. I wrote my last operating system. (It was the same operating system.)

And out of nowhere there came Dr. Dobb's Journal of Computer Calisthenics and Orthodontia: Running Light Without Overbyte. Byte may well have been the first microcomputer magazine, but DDJ was the first magazine for microcomputer programmers, which was a degree of specialization that most people considered a little nutty at the time. It was printed on plain white paper, and didn't even have a cover. The title was dead on target. And best of all, it made my brain crawl with ideas.

Over the years we've seen a lot of weird and interesting material in DDJ, and I couldn't begin to catalog the things I learned here long before I ever saw them anywhere else. I think it's fair to say that by publishing Ron Cain's tiny c, DDJ gave the C language the push it needed away from near-terminal Unix bloat and toward critical mass on the leaner, meaner platforms that rule today.

Beyond all that, however, what earned my everlasting respect for DDJ is that it is, and has always been, a publication that cares. It recognizes, first of all, that there is a universe of complication outside the cubbyholes where we lay down our code, line by line. These complications affect us, our ability to earn a living, and in some cases, our ability to speak and act as free beings. Rather than pretend that these complications don't exist (as my earlier employer, PC Tech Journal, always did) DDJ allowed concerned voices among its staff and readership to speak to those readers who were perhaps unaware of or as yet undecided about those complications.

Some years back, Allen Holub ignited a small storm with his contention that programmers have the obligation to act ethically, and that ethics preclude working on software that supports weapons systems. Allen and I chawed on opposite ends of that particular bone of contention (since after all, our nuclear weapons prevented the Russians from destroying themselves -- and us -- before they had a chance to come to their senses) but I stood a little in awe that DDJ gave him the forum to make his feelings known. No hint of that debate would ever have surfaced in print at PC Tech Journal, where I was regularly dressed down for attempting to lighten that magazine's often-leaden, all-business heart.

Software piracy, DOD suppression of public-key encryption algorithms, BBS harassment, look-and-feel banditry, and (most recently) the absurd activities of our own Patent Office have seen considerable discussion in these pages. Sometimes DDJ has an official position, and sometimes it does not. (Not only are there not always any easy answers; there are often no answers at all.) Keep in mind that magazines are there to inform and to stimulate discussion. Ultimately, it is individuals who act. What DDJ does that no other programmer's magazine has ever done is to lay out these ugly issues for public dissection, and then plead, if you care, act.

It's been 15 years that leave me out of breath to recall. Mostly it's been 15 years of unbridled freedom to hack, to learn, to work, and to make money. We've come to take that freedom for granted, forgetting that freedom is always under attack by the greedy, the unprincipled, the envious, and the fearful. We've been lucky so far. It's not going to last. Large, technologically bankrupt firms such as Lotus are putting systems in place to take by force what they can no longer earn in the free market. The U.S. Patent Office is illegally handing out patents on formulas (which we call algorithms) irrespective of the fact that formulas explicitly cannot be patented, not to mention additional silly points like blatant obviousness and prior art. Many government bodies are trying their best to make BBS systems impossible.

So let me echo DDJ's unwritten philosophy: If you care, act. It's your hind end on the line. Boycott firms that claim what isn't theirs. Pester the bejeezus out of your congressman to put a leash back on the Patent Office and force them to obey their own law.

Most of all, strive in whatever way you can manage to return our industry and our nation to the rule of law. The law today has become so rubbery that it has come to mean nothing but what some judge somewhere says it means, which is to say nothing at all. I still believe it can be done. The alternative is chaos, especially in our industry where the limits of what we can do is nowhere in sight. (I have already heard rumors of a new class of virus that inserts realistic-looking bugs into copies of Lotus 1-2-3 that it finds...do we really want to let slip the dogs of that sort of war?)

Ultimately, it depends on you. The opinions expressed in this particular column are entirely my own, and do not reflect the views of Dr. Dobb's Journal -- which is entirely the point! They gave me this space to make noise because they care. Now it's your turn. Care enough to understand the issues. Care enough to have opinions. Get excited. Get mad. If you care, you can win. If you hide, you will lose.

It's that simple. And I learned it by reading DDJ.

An Object's Private Parts

Turbo Pascal 5.5 worked so well at bringing objects to the common hacker that few of us carped about its (minor) shortcomings. Probably the most major of its minor shortcomings was lack of any management of access rights to object internals. In other words, any program statement within the scope of an object could freely access any field or method within that object, period. All fields and methods were strictly public. About the best we could do was simply not publish the full definition of an object type, but rather give an object's users an edited list of those fields and methods we chose to make available. This made access-rights management something like an exercise in industrial espionage, and almost nobody bothered.

C++ has access-rights management in spades, as I am discovering while writing Object-Oriented Programming From Square One, which touches on C++ in the course of explaining OOP principles. (And C++ From Square One is still ahead of me -- arrgh!) You can restrict access to object fields and methods at three different levels -- and then selectively violate those restrictions using "friend" functions. It took a couple of days for me to get it all straight in my mind, and it left me with the lingering feeling that C++ is ripe with spokeshaves; that is, tools good for only one specialized purpose (such as shaving spokes) that rarely come to hand in any other situation.

Borland added access-rights management to Turbo Pascal 6.0 (released this past November). In keeping with seven years of tradition, they managed to do it in a way that retained 80 percent of the power of That Other Language, while remaining simple enough to master without a lifetime of effort.

Sticking With a Winning Paradigm

Winning paradigms are like winning horses: You stick with 'em. Borland weighed the need for limited access rights in Turbo Pascal objects very carefully before deciding just how to implement them. In fact, they had a very successful paradigm of limited access rights in their hip pockets all the time, and they wisely decided to stick with it.

The paradigm I'm speaking of is the units paradigm, and it's both a familiar and an effective model for limiting object access rights. In every unit there is a public portion called the definition part, and a private portion called the implementation part. Program entities declared in the definition part are "public;" that is, any code using the unit can reference those entities freely. On the other hand, entities declared and defined wholly in the implementation part of a unit are private to that unit. This means that other entities inside the implementation part of the unit can use them, but no entity outside the implementation part of the unit can reference them or know that they exist.

This works beautifully. So Borland stuck with units as the mechanism through which object access rights are defined. A new directive, PRIVATE, has been added to the language. PRIVATE is a directive rather than a reserved word; it has special meaning only within an object definition. (The reserved word VIRTUAL, added with Turbo Pascal 5.5, has been demoted to this same sort of directive.) If you put the directive PRIVATE inside an object-type definition, any fields or methods declared after PRIVATE may be referenced only from within the unit in which the object type is declared.

Think of it this way: An object type definition is typically placed in the interface portion of a unit, making it public and referenceable from anything that uses the unit. The PRIVATE directive is a way to move declarations that would ordinarily be made in the implementation section of a unit up into the interface section -- without making the declared items public.

When, Yet Again

Actually, the best way to explain it is to move right to a practical code example. Listing One is a remake of my old, (somewhat) reliable "when stamp," which I first presented in the April 1990 DDJ as an example of encapsulation in Turbo Pascal 5.5. A when stamp, in case you're just tuning in, is my coinage for a model of a point in time under DOS. It contains both the time and the date, stored as a single 32-bit quantity, along with machinery to fetch the current time and date from DOS, and to provide the user with the time and date in various formats. I wrote it to bundle a whole toolkit of time and date formatting procedures and functions into a single logical entity -- which I definitely think of as encapsulation in action.

WHEN2.PAS recasts the when stamp for Turbo Pascal 6.0. Look closely at the object-type definition. It now has both public and private parts, separated by the new directive PRIVATE. Those items declared above PRIVATE are accessible by users of the unit. Those items declared below PRIVATE are accessible only from within the implementation section of the unit.

Although we don't refer to them as such, an object, like a unit, now has an interface and an implementation section. (The Borland manuals simply refer to them as "public" and "private.") The parts of an object that the user of the object is allowed to use is the interface section, whereas those parts of the object not available to the object's users are the implementation section. I've sketched out this correspondence in Figure 1. Because the complete object definition must be in the unit's interface section, the user of the object is fully aware of the object's private parts, but isn't allowed to get at them. (Anyone who came of age prior to the Sixties will know what I mean.)

I suppose that in the purest sense of the word private, the object definition should be split in two, with the public portion in the interface section of the unit, and the private portion in the implementation section of the unit. This would make for needless confusion, since after all, encapsulation is a coming together.

Hands Off, Kids

There is one downside to Borland's system of access rights: For full access to all fields and methods, subclassing must be done within the same unit as the superclass. In other words, if you choose to extend an existing object by declaring a child type of that object, the child type's methods must be fully implemented in the same implementation section containing the parent's methods. The rule that private fields and methods are private within a single unit is absolute. You can declare child types outside the parent type's unit, but those child types must work with the parent type on the same terms as everybody else: Without touching the parent type's private parts.

What this does mostly is put a crimp in extendibility. Extending an object with much of itself set off as private becomes difficult or impossible unless the person doing the extending has the source code to the unit defining the parent. If providers of objects intend their objects to be extended, they must be very careful in choosing what should be private and what should not. A private method cannot be overridden from outside the unit.

How much of a problem will this turn out to be? Only some serious use of the product will tell. I suspect it sounds worse than it truly is. Got any insights? Do share them.

The Capsule in Encapsulation

The coming of access rights with Turbo Pascal 6.0 solved an ugly problem besetting the original when stamp unit. All of the fields in the private portion of the new When object were present in the original, but in the original they could be accessed freely, and there was no way that I could prevent such access. So I turned a bug into a feature and declared that this made for speedier performance: If you wanted a string form of the date, you just went in and grabbed the string form of the date that the object maintained internally inside the field called DateString.

Fast, easy; no function-call overhead.

All well and good -- but anything that can be read from outside the object can be changed as well. Reading any of the when stamps' data fields is fine. Directly changing any of them is a recipe for instant trouble.

Why? Consider: The when stamp actually models only one moment in time, but internally it contains several expressions of that moment in time. There is the central 32-bit field, WhenStamp, which contains the bit-mapped values of the current hours, minutes, seconds, year, month, and day. Then there are separate numeric fields containing the same information: Hours, Minutes, Seconds, and so on. Additionally, there are three different string fields containing human-readable representations of time and date, plus another numeric field indicating the day of the week, produced by that rascal, Zeller.

Now, suppose that you instantiate a When object (RightNow, say) and call the PutNow method to load the current time and date into RightNow. This current time and date value is stored in the field called WhenStamp. The PutNow method then calls several other routines, which take the value in WhenStamp and calculate values for the other representations of the time and date.

Later on, you turn around and write the value 3 directly into the Month field. Unless it just happens to be March, the internal fields of RightNow no longer agree with themselves on what month it is. WhenStamp may say October, but Month says March. Who do you believe?

By design, the WhenStamp field is boss. The "true" time and date contained in a when stamp is the time and date value in the WhenStamp field. The other representations must be calculated from the value in WhenStamp. Change one of the other representations without changing WhenStamp first, and your when stamp may start telling lies, and in this business, lies beget bugs.

By design, in other words, fields like Month and LongDateString are "read-only," but there was no mechanism in Turbo Pascal 5.5 to enforce that stipulation. Now, in Turbo Pascal 6.0, that mechanism is there, in the form of access rights. Month, LongDateString, and the other fields are now private. The PutNow method and other code within the When2 unit may change them, but users of the unit may not. To allow users access to the various representations of the time and date, I added a whole raft of new methods, such as GetYear, GetMonth, GetDayOfWeek, and so on. If you look at their implementation, you'll see that these methods are nothing more than single assignment statements: The value of the field in question is assigned to the name of the method in question. The method grabs the value of the private field and carries that value out front to the user. It's a one-way street: The user cannot make the GetDayOfWeek method go back and somehow change the DayOfWeek field.

I could have had (and probably should have had) all these methods in the original when stamp unit, presented here in DDJ last April. I chose not to because there was no way to force users of the unit to go through channels and use the methods to retrieve time and date values, rather than going directly to the time and date fields themselves.

The Virtue of Private Methods

Having private methods also allowed me to take the several utility routines in the original when stamp unit -- CalcTimeString, CalcDayOfWeek, etc. -- and make them methods. The user has no cause to call these methods directly (and in fact might make a mess if allowed to do so); private methods cannot be called by the user. They can only be called by code within the implementation portion of the unit, typically by the object's other methods.

Now, the "calc" routines were always in the implementation portion of the When2 unit, and hence off-limits to when stamp users. So why bother making them methods? The answer is that, as methods, the "calc" routines can access the object's data fields directly, rather than as parameters. When a data field is pushed on the stack as a parameter, a lot more code must be executed than if a data field is referenced directly. Without all that thrashing of parameters onto and off of the stack, the when stamp object is both smaller and faster.

And apart from that, making all the code connected with an object into methods helps from a documentation and comprehension standpoint. One glance tells you what an object can do. There's less digging around to get an overview of its internals.

Good OOP practice has always held that the values of object fields should always be returned through methods, rather than through direct access. In Smalltalk and Actor, there's no choice in the matter -- data fields are off-limits outside the boundaries of the object that contains them. With private methods and fields, Borland has put the capsule into encapsulation, and made the Pascal OOP design a bit more foolproof for all us fools struggling to make something of it.

Events in Graphics

Last month, I described Turbo Vision, the event-driven windowing application framework Borland is now shipping with Turbo Pascal 6.0. As good as it is, Turbo Vision operates only in text mode. This is fine; I do like Windows 3.0, but I like freedom of choice a lot more.

It may be a bit before we see Windows 3.0 development with Turbo Pascal. In the meantime, I've found a dandy graphics-based event-driven application framework, and while it's not object oriented, it's still extremely well done: The TEGL Windows Toolkit II from TEGL Systems in Vancouver, British Columbia.

The TEGL Windows Toolkit II (TEGL, for short) operates in much the same way as Turbo Vision. Your application is a process of setting up responses to mouse, keyboard, and timer events, and then letting the event handler take over. The event handler intercepts events from their various sources, and invokes your routines appropriately.

TEGL provides many of the same services as Turbo Vision: pull-down menus, pop-up windows, dialog boxes, and so on. There are a great many graphics-specific features as well, including several very nice fonts (plus a few ugly ones), an icon editor, and a whole raft of drawing primitives.

I tested two versions of TEGL, one for Turbo Pascal and a nearly identical one for Turbo C. (It also works with Turbo C++, but again, TEGL is not an OOP tool.) Both versions rely on the BGI for graphics, but Richard Tom has replaced some of the slower BGI primitives with his own versions, which are a great deal faster. If you've avoided BGI graphics for speed reasons, you might try again, using TEGL instead.

Products Mentioned

Borland International 1800 Green Hills Road Scotts Valley, CA 95066 408-438-8400 Turbo Pascal 6.0: $199.95 Turbo Pascal 6.0 Professional: $299.95

The TEGL Windows Toolkit II TEGL Systems Corporation 789 W. Pender Street, Suite 780 Vancouver, BC Canada V6C 1H2 604-669-2577 Intro Pack: $5.00 With source code: $50.00 Games Toolkit: $90.00

The Turbo Language User's Conference Sheraton Palace Hotel San Francisco, Calif. April 28 through May 1 1-800-942-TURBO

Some of the niftiest features of TEGL relate to animation. Icons may be animated. In the very nice TEGL-generated Mah Jongg game Richard Tom markets as a shareware product, the icon for the game is an old Chinese gentleman who bows when you click on him, to a short riff of Chinese music.

The Mah Jongg game is beautifully done, but it illustrates a trap that our anarchically diverse PC video universe sets for the unwary developer. The oriental tile patterns for the game are bitmaps, edited in the TEGL icon editor. And because they are bitmaps, their physical size on the screen depends on the resolution of the current screen mode. They seemed just about right in 640 x 350 EGA graphics, but shrank to a level I'd call close to uncomfortable when I recompiled the program for 640 x 480 VGA graphics.

There's no easy way around this problem that retains the speed of bitmapped graphics. Keep it in mind if you develop any graphics application: The screen will look different in other graphics modes. Plan to test it thoroughly (for usability as well as for simple correctness) under any screen mode you intend to support. And (although you may not agree with me) I recommend not supporting a given graphics mode rather than putting an ugly or difficult-to-use application out there.

TEGL is fast, well-documented, and cheap -- and definitely the most fun I've had playing with graphics in a good long while.

The Turbo Language User's Conference

I've just learned that Borland will be holding a conference for Turbo Language users in San Francisco April 28 through May 1. Although there will be vendor booths (I'll have one myself, for PC TECHNIQUES) the whole point of the conference is to present technical seminars from which you can learn something. Details are still few, but from what I've heard, it'll be well worth the trip. Pencil it in -- and I'll see you there!

_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann


[LISTING ONE]


{---------------------------------------------------}
{                    WHEN2.PAS                      }
{ A time-and-date stamp object for Turbo Pascal 6.0 }
{                           by Jeff Duntemann       }
{                           From DDJ for Jan. 1991  }
{ NOTE: This unit should be good until December 31, }
{ 2043, when the long integer time/date stamp turns }
{ negative.                                         }
{---------------------------------------------------}

UNIT When2;

INTERFACE

USES DOS;

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

  When =
    OBJECT
      FUNCTION GetWhenStamp : LongInt;  { Returns 32-bit time/date stamp }
      FUNCTION GetTimeStamp : Word;     { Returns DOS-format time stamp }
      FUNCTION GetDateStamp : Word;     { Returns DOS-format date dtamp }
      FUNCTION GetYear      : Word;
      FUNCTION GetMonth     : Word;
      FUNCTION GetDay       : Word;
      FUNCTION GetDayOfWeek : Integer;  { 0=Sunday; 1=Monday, etc.  }
      FUNCTION GetHours     : Word;
      FUNCTION GetMinutes   : Word;
      FUNCTION GetSeconds   : Word;
      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);
    PRIVATE
      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  CalcTimeStamp : Word;
      FUNCTION  CalcDateStamp : Word;
      FUNCTION  CalcDayOfWeek : Integer;  { via Zeller's Congruence }
      PROCEDURE CalcTimeString;
      PROCEDURE CalcDateString;
      PROCEDURE CalcLongDateString;
    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;

{***********************************************}
{ PRIVATE method implementations for type When: }
{***********************************************}

FUNCTION When.CalcTimeStamp : Word;

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

FUNCTION When.CalcDateStamp : Word;

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

PROCEDURE When.CalcTimeString;

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 When.CalcDateString;

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

PROCEDURE When.CalcLongDateString;

VAR
  Temp1 : String9;

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

FUNCTION When.CalcDayOfWeek : Integer;

VAR
  Century,Holder : Integer;

FUNCTION Modulus(X,Y : Integer) : Integer;

VAR
  R : Real;

BEGIN
  R := X/Y;
  IF R < 0 THEN
    Modulus := X-(Y*Trunc(R-1))
  ELSE
    Modulus := X-(Y*Trunc(R));
END;

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 as Zeller himself }
    { described it in "Acta Mathematica" #7, Stockhold, 1887.  }
    BEGIN
      { First we separate out the year and the century figures: }
      Century := Year DIV 100;
      Year    := Year MOD 100;
      { Next we adjust the month such that March remains month #3, }
      {  but that January and February are months #13 and #14,     }
      {  *but of the previous year*: }
      IF Month < 3 THEN
        BEGIN
          Inc(Month,12);
          IF Year > 0 THEN Dec(Year,1)      { The year before 2000 is }
            ELSE                            { 1999, not 20-1...       }
              BEGIN
                Year := 99;
                Dec(Century);
              END
        END;

      { Here's Zeller's seminal black magic: }
      Holder := Day;                        { Start with the day of month }
      Holder := Holder + (((Month+1) * 26) DIV 10); { Calc the increment }
      Holder := Holder + Year;              { Add in the year }
      Holder := Holder + (Year DIV 4);      { Correct for leap years  }
      Holder := Holder + (Century DIV 4);   { Correct for century years }
      Holder := Holder - Century - Century; { DON'T KNOW WHY HE DID THIS! }

      Holder := Modulus(Holder,7);          { Take Holder modulus 7  }

      { Here we "wrap" Saturday around to be the last day: }
      IF Holder  = 0 THEN Holder := 7;

      { Zeller kept the Sunday = 1 origin; computer weenies prefer to }
      { start everything with 0, so here's a 20th century kludge:     }
      Dec(Holder);

      CalcDayOfWeek := Holder;  { Return the end product! }
    END;
END;

{**********************************************}
{ PUBLIC method implementations for type When: }
{**********************************************}

FUNCTION When.GetWhenStamp : LongInt;

BEGIN
  GetWhenStamp := WhenStamp;
END;

FUNCTION When.GetTimeStamp : Word;

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

FUNCTION When.GetDateStamp : Word;

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

FUNCTION When.GetYear : Word;

BEGIN
  GetYear := Year;
END;

FUNCTION When.GetMonth : Word;

BEGIN
  GetMonth := Month;
END;

FUNCTION When.GetDay : Word;

BEGIN
  GetDay := Day;
END;

FUNCTION When.GetDayOfWeek : Integer;

BEGIN
  GetDayOfWeek := DayOfWeek;
END;

FUNCTION When.GetHours   : Word;

BEGIN
  GetHours := Hours;
END;

FUNCTION When.GetMinutes : Word;

BEGIN
  GetMinutes := Minutes;
END;

FUNCTION When.GetSeconds : Word;

BEGIN
  GetSeconds := Seconds;
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);
  GetDate(Year,Month,Day,Dummy); { Get current clock date }
  { Calculate a new date stamp and update object fields: }
  PutDateStamp(CalcDateStamp);
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;
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;
  { Calculate the short string version of the date; as in "06/29/89": }
  CalcDateString;
  { Calculate a long version, as in "Thursday, June 29, 1989": }
  CalcLongDateString;
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);
  { Calculate the short string version of the date; as in "06/29/89": }
  CalcDateString;
  { Calculate a long version, as in "Thursday, June 29, 1989": }
  CalcLongDateString;
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);
  { Derive the string version of the time: }
  CalcTimeString;
END;

END.

Copyright © 1991, Dr. Dobb's Journal