USING THE REAL-TIME CLOCK

Faster time routines for Turbo Pascal

Kenneth Roach

Kenneth is an engineer for Unisys. He can be contacted at P.O. Box 2271, Manteca, CA 95336.


When recently faced with the need to perform processing based on seconds elapsed, I was disappointed at the types of time-related functions Turbo Pascal provided. What I needed was a routine which would return the elapsed time in seconds since a base date. That is, a Pascal routine similar to the time function as defined for ANSI C. This was not available, and I was faced with either doing calls to the Turbo Pascal GetTime procedure and manipulating the value returned, or inventing an equivalent of the time function for Turbo Pascal.

I quickly wrote a Pascal version of the time function that proved satisfactory, though tests indicated that its performance was less so. The Time procedure was called around 8000 times in a five-second period on the system I was using--a 25-MHz 80386 PC running under MS-DOS. I then began efforts to improve the procedure's performance. The first attempt involved eliminating as many long integer calculations as possible; some remained, however, because the value returned is a long integer. This improved performance, though it still seemed that more processing time than should have been was required.

The system I was using had an AT-compatible real-time clock, so it seemed reasonable to test usage of this clock with the Time procedure. Calls to the Pascal GetTime and GetDate procedures were replaced with direct reads of the values maintained by the real-time clock. The performance improvement was startling. Using the real-time clock, the newly created Pascal Time function was faster than the compiler's own GetTime procedure, even though this Time function had much more to do.

After some thought, this made sense. Turbo Pascal is designed to generate programs which can be run on any DOS-based system, including those using the 8088 processor. It makes no assumptions about the type of hardware available. Instead, it relies on standard MS-DOS time and date information provided by the 8253 timer chip, regardless of what might be available. Performance suffers, it seems, for the sake of compatibility.

Later tests with Turbo C provided similar results. In a five-second period, the standard C time function was called some 11,000 times, and the gettime function around 34,000 times. These counts are very similar to those of Turbo Pascal, so it seems that they, too, obtain time and date information via the 8253 timer chip.

Because of the real-time clock's superior performance, I decided to create a set of Turbo Pascal (and Turbo C) routines that use this clock.

Accessing the Real-Time Clock

The real-time clock function is provided by a Motorola MC146818 processor located on the motherboard. Information from this clock is stored in battery-backed memory. This memory is accessible by programs through port addresses $70 and $71. Locations in the memory relating to the real-time clock are described in Table 1. To read a memory location, it is necessary to first place the location's address into register $70 and then read the data from register $71. Writing to the memory is similar. The location to be written to is first placed into register $70, and the data to be written is then placed into register $71.

Table 1: Real-time clock memory locations

  Location  Description
  -------------------------------------------------------------------------

  $00:      Current time (second)
  $01:      Alarm time (second)
  $02:      Current time (minute)
  $03:      Alarm time (minute)
  $04:      Current time (hour)
  $05:      Alarm time (hour)
  $06:      Day of week
  $07:      Day of month
  $08:      Month (1-12)
  $09:      Year, relative to century
  $32:      Century
  $0a:      Status Register A:
            Bit 7:   Indicates update of time is in progress if set.
            Bit 6-4: Time frequency.  Default is 010, or 32,786 KHz.
            Bit 3-0: Interrupt frequency.  Default is 0110, or 1.024 KHz.
  $0b:      Status Register B:
            Bit 7:   Set clock: If set, the program can initialize the
            14 time-bytes.  No updates will occur until the bit is reset.
            Bit 6:   Periodic Interrupt Enable.  If set, enables interrupt
            according to the parameters in register A.
            Bit 5:   Alarm Interrupt Enable.  If set, enables alarm
            interrupt at time specified in registers $01,
  $03 and $05.
            Bit 4:   Update Ended Interrupt Enabled.  If set, enables interrupt
            at clock update interval.
            Bit 3:   N/A.
            Bit 2:   If set, indicates time information is in binary,
            else time information is in BCD.
            Bit 1:   If set, indicates clock is operating in 24-hour
            mode, else clock is in 12-hour mode.
            Bit 0:   If set, enables daylight savings mode.
  $0c:      Status Register C:
            Bit 7:   Interrupt identification.
            Bit 6:   Periodic interrupt occurred.
            Bit 5:   Alarm interrupt occurred.
            Bit 4:   Update interrupt occurred.
            Bit 3-0: N/A.
  $0d:  Status Register D:
            Bit 7:   If not set, indicates that the real-time clock has
            lost power.
            Bit 6-0: N/A.

Most of the memory locations are straightforward and explained adequately in Table 1. Some deserve additional comment, however:

As Table 1 shows, the real-time clock offers an optional periodic interrupt. When enabled, the real-time clock will generate an interrupt at a programmable interval, which defaults to 1024 times per second, a much better resolution than the standard clock-tick frequency of approximately 18.2 times per second. Routines to handle this interrupt are provided here, and the interrupt may be enabled or not, as required.

The real-time clock uses IRQ 8, which is handled through the second 8259 using interrupt vector $70. The periodic interrupt is enabled by setting bit 6 of status register B. When interrupts occur, the interrupt service routine must examine status register C to determine the cause of the interrupt. This presents a problem because an application can be involved in reading this memory prior to and after the interrupt. The problem is compounded by the fact that register $70 is a read-only register. Other interrupts should not be allowed to occur while accessing the real-time clock.

If the clock interrupt occurred due to something other than the periodic interrupt, the interrupt service routine must pass the interrupt through to the normal ISR. If the ISR was called due to the periodic interrupt, any other processing required should be done without calling the normal ISR. Following this, an end-of-interrupt must be generated for both the primary and secondary 8259 interrupt controllers.

The real-time clock can also be requested to generate an interrupt at a specific time (see Table 1, locations $01, $03, and $05). While MS-DOS does not provide a mechanism for enabling or handling the periodic interrupt, it does support enabling the alarm interrupt through interrupt $1a. When enabled through MS-DOS, the BIOS will generate interrupt $4a. While this is an acceptable mechanism, and perhaps preferred in some cases, an application can handle the interrupt a bit more directly by processing IRQ 8 itself.

New Turbo Pascal Time and Data Functions

Listing One (page 88) shows the set of time and date functions using the real-time clock I developed for Turbo Pascal. These routines include replacements for the GetTime and GetDate procedures, as well as routines emulating the C language's ctime and clock routines. Because Turbo Pascal does not have a time function of its own, a version of the Time procedure that does not use the real-time clock is provided along with one which does. Routines to enable and disable periodic interrupts from the real-time clock are provided as well, along with the necessary interrupt service routine for the clock. Finally, a function is provided to return the periodic interrupt count for the current second.

Listing Two (page 91) is a simple test program written to measure the performance of these routines versus the performance of previously existing routines. The test program repeatedly calls each of the routines for a five-second period, with a counter incremented for each of the calls.

(I also developed a set of time functions for C that is similar to that for Pascal. Due to space constraints, however, these functions are only available electronically; see "Availability" on page 3. The C code includes real-time clock-based replacements for C's time, gettime, and getdate functions, as well as a replacement for the time function that does not use the real-time clock. Interestingly, the corresponding Turbo Pascal procedure was found to be faster than the one provided with Turbo C. Consequently, a replacement for C's ctime function is provided as well.)

Timing results can be expected to vary from system to system, depending on the processor type, resident software installed, and so on. A sample of the Pascal test program's results is shown in Figure 1.

Figure 1: Test program results

  Test Summary:

  GetTime      called 34601 times
  GetRtcTime   called 108846 times
  GetRtcTime was 334% faster than
                          GetTime

  GetDate      called 16397 times
  GetRtcDate   called 53894 times
  GetRtcDate was 330% faster than
                          GetDate

  Time         called 13250 times
  RtcTime      called 30755 times
  RtcTime was 233% faster than Time

During the course of refining the real-time clock routines, I made a few additional discoveries which can make using the real-time clock routines all the more attractive.

The system being used for testing was connected to a local area network and, for convenience, was running in file server mode about half the time, allowing remote access to the system. Large differences were noted in performance between tests on different occasions, and these differences were ultimately traced to whether or not the LAN server program was in memory or not. Further testing found that the overhead added to the MS-DOS date and time functions by the server is considerable. When the LAN server program was running, the MS-DOS date and time functions performed about a third as fast as when the server program was not running, while the routines that used the real-time clock performed only about 5-10 percent slower.

I also discovered that performing the above tests with a 386 memory management program loaded added considerably to the overhead of the MS-DOS time and date functions. The test results shown in Figure 1 are, in fact, those output by the program when a memory management program was in use. When the memory manager was disabled and the test repeated, the output in Figure 2 was obtained. As can be seen, when the 386 memory manager was not in use, a substantial improvement in the performance of the MS-DOS date and time functions was observed. However, not only did the MS-DOS-based functions improve in performance, the routines based on usage of the real-time clock improved as well.

Figure 2: Test results with 386 memory manager disabled

  Test Summary:

  GetTime      called 50898 times
  GetRtcTime   called 129415 times
  GetRtcTime was 255% faster than
                          GetTime

  GetDate      called 25605 times
  GetRtcDate   called 65650 times
  GetRtcDate was 257% faster than
                          GetDate

  Time         called 18237 times
  RtcTime      called 37101 times
  RtcTime was 254% faster than Time

The reason for lessened performance with the 386 memory management software relates to the fact that the particular memory manager in use runs the system as a virtual 8086 task. When this is done, all interrupt processing is filtered by a virtual 8086 task management program. According to Intel documentation, this can add as much as 300 clock ticks to each interrupt performed, and more than 200 clock ticks to each return from an interrupt. Much of the processing performed by the real-time clock versions of the routines is not affected by interrupt processing.

While use of a 386 memory management program does increase overhead for virtually all things done, it is unlikely that many of us would be willing to give up memory management programs at the present time. Such programs perform valuable services, including emulation of expanded memory (EMS), remapping of ROM and RAM in the upper areas of the first megabyte of memory, and often the ability to load TSR programs and device drivers into this upper range of the first megabyte of memory.

Because 386 memory managers will likely continue to be used, usage of the real-time clock-based routines will circumvent any performance problems relating to time and date processing on systems using such. The complete set of time and date routines for Turbo Pascal are shown in Table 2.

Table 2: Time and date routines for Turbo Pascal and Turbo C

  Pascal  EnableRtcInts; (Procedure)
  C       void enable_rtc_ints( )

Enables interrupts from the real-time clock, which will be handled by Rtc in Pascal or rtc in C. Note that in both languages, the routines that return the current time rely on interrupts from the real-time clock to calculate hundredths of seconds. If interrupts are not enabled, hundredths will not be returned.

Enabling interrupts from the clock will cause additional processing time to be used while servicing them, so clock interrupts should not be enabled unless there is a need for time information at greater than a 1-second resolution.

  Pascal  DisableRtcInts; (Procedure)
  C       void disable_rtc_ints( )

If clock interrupts have been enabled, this routine must be called prior to terminating your program to disable interrupts from the clock.

  Pascal  None
  C       void init_time( )

Used in the C version of the library to determine whether daylight savings time is in effect. Should be called once prior to using the other time routines in the C library to assure accuracy of results. Daylight savings time is not a factor in the Pascal version.

  Pascal  GetRtcTime(Var Hr, Mn, Sc, Hn:Word); (Procedure)
  C       void get_rtc_time(timep *time)

A direct replacement of the original GetTime routine. Note that variable Hn (hundredths of second) will always be set to zero unless interrupts from the real-time clock have been enabled.

  Pascal  GetRtcDate(Var Yr,Mo,Dy:Word); (Procedure)
  C       void get_rtc_date(datep *date)

A direct replacement of the original GetDate, with the exception that day of week is not calculated.

  Pascal  RtcTime (Var Result: LongInt); (Procedure)
  C       time rtc_time(time_t *result)

An addition to Turbo Pascal. Emulates C's time function, though with these differences: First, C time function both returns a value and stores that value at an address which is passed to it. RtcTime is a procedure, not a function. Therefore, it simply stores a time value at the address of the Result variable. Second, C's time function returns elapsed time in seconds, since 00:00:00, January 1, 1970, Greenwich Mean Time. Because there was no preexisting Time procedure in Pascal, GMT information is not available, and the real-time clock has provided the same value since January 1, 1980, the Pascal RtcTime procedure returns elapsed seconds since 1980 instead of 1970, without regard to GMT. Because C provides a precedent for the time function, rtc_time behaves exactly as the original time function does. It returns a value and stores it at the address specified, and returns a value representing elapsed time since 00:00:00 Jan 1, 1970, GMT.

  Pascal  Time2(Var Result : LongInt); (Procedure)
  C       time_t time2(time_t *result);

For completeness, the Pascal Time2 procedure is provided for systems which are not equipped with a real-time clock. The Pascal Time2 procedure was observed to be faster than Turbo C's own time function, for unknown reasons. Because of this, I include a replacement for Turbo C's time function, called time2, to avoid duplicating the name.

  Pascal  Clock : LongInt; (Function)
  C       clock_t clock( );

Only usable when interrupts from the real-time clock have been enabled, and only if those interrupts remain enabled between successive calls. Clock returns a value representing the number of periodic interrupts generated since such interrupts were first enabled (the interrupt handler is called 1024 times per second). It can be called multiple times to determine the number of clock ticks which have elapsed between two events. The value returned will go negative should the program run for 24 days or so, which was not considered a problem here.

  Pascal  MilliCount : Integer; (Function)
  C       int milli_count( );

When interrupts from the real-time clock are enabled, this function returns the number of periodic interrupts generated for the current second.

  Pascal  CTime2(Time : LongInt): TimeStrPtr; (Function)
  C       char *ctime2(time_t *t);

CTime2 was first written in Turbo Pascal because TP does not provide an equivalent. As written, it processes values returned by the Time2 procedure (based on Jan 1, 1980). Like C's ctime, CTime2 for Pascal returns a pointer instead of a string. Tests indicated that the Pascal CTime2 was around twice as fast as Turbo C's own ctime, so a version was developed for C as well.

  Pascal Rtc; Interrupt; (Procedure)
  C void interrupt rtc( );

Process the periodic interrupts from the real-time clock when enabled.

A necessary limitation of most of these routines is that an AT-compatible real-time clock is required for them to function. When a given program will be used on 8088 systems, as well as newer ones, the presence of this real-time clock cannot be guaranteed. In these cases, the processor type can be detected and the time and date routines which do not use the real-time clock can be used.

How these routines will effect the performance of your programs depends on their need to sample the date and time and/or format it for output. The tighter the loop, the greater the need for performance, and the better these routines may help.


_USING THE REAL-TIME CLOCK_
by Kenneth Roach

[TURBO PASCAL VERSION]


[LISTING ONE]


(*
** TIMELIB.PAS
** (C) Copyright 1990 by Kenneth Roach
** This module contains procedures similar to Turbo Pascal's GetTime and
** GetDate procedures, but which are based on use of the AT class of
** system's real time clock.  Additionally, procedures and functions are
** provided to enable and disable periodic interrupts from the real time
** clock along with an interrupt handler for same.  Interrupts from the
** real time clock are provided at a rate of 1024 per second, and a
** function is provided to return the number of interrupts received in the
** current second.  Also provided are emulations of the C language's
** time(), ctime() and clock() functions.
*)

Unit TimeLib;

Interface

Uses Dos;

Type
   TimeString = String[24];
   TimeStrPtr = ^TimeString;

Function  RtcClock    : LongInt;
Function  MilliCount  : Integer;
Function  CTime2(Time : LongInt) : TimeStrPtr;
Procedure RtcTime(Var Where : LongInt);
Procedure Time2(Var Result : LongInt);
Procedure EnableRtcInts;
Procedure DisableRtcInts;
Procedure GetRtcTime(Var Hr,Mn,Sc,Hn : Word);
Procedure GetRtcDate(Var Yr,Mo,Dy : Word);


Implementation

Type
   ShortString = String[3];
   OldVec      = Procedure;

Const
   CLI           = $FA;
   STI           = $FB;
   MASK_24       = $02;
   BCD_MASK      = $04;
   CMOSFLAG      = $70;
   CMOSDATA      = $71;
   SECONDS_REQ   = $00;
   MINUTES_REQ   = $02;
   HOURS_REQ     = $04;
   STATUSA       = $0A;
   DATE_REQ      = $07;
   MONTH_REQ     = $08;
   YEAR_REQ      = $09;
   CENTURY_REQ   = $32;
   UPDATE        = $80;
   HINIBBLE      = $F0;
   LONIBBLE      = $0F;

   SECS_PER_MIN  = 60;
   SECS_PER_HOUR = 3600;
   SECS_PER_DAY  = 86400;
   SECS_PER_YEAR = 31536000;
   MINS_PER_HOUR = 60;
   DAYS_PER_YEAR = 365;
   BASE_YEAR     = 1980;
   DAYS_PER_WEEK = 7;
   TUESDAY       = 3;                      { day of week for 1-1-1980 }
   APRIL         = 4;
   JUNE          = 6;
   SEPTEMBER     = 9;
   NOVEMBER      = 11;
   FEBRUARY      = 2;

   RTC_VEC       = $70;
   IMR2          = $A1;
   CMD1          = $20;
   CMD2          = $A0;
   EOI           = $20;
   RTC_MASK      = $FE;
   STATUSB       = $0B;
   STATUSC       = $0C;
   RTC_FLAG      = $40;

   Months : Array[1..12] of ShortString =
              ('Jan','Feb','Mar','Apr','May','Jun',
               'Jul','Aug','Sep','Oct','Nov','Dec');
   Days   : Array[1..7]  of ShortString =
              ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');

Var
   Bcd       : Boolean;
   RtcCount  : Integer;
   TickCount : LongInt;
   OldRtcVec : Pointer;
   OldCall   : OldVec;
   OldMask   : Byte;
   TimeStr   : TimeString;

(*
** emulation of the C language clock() function.  RtcClock returns
** a value corresponding to the number of periodic interrupts which
** have occurred since interrupts from the real time clock were
** enabled. The value will remain positive for some 24 days from
** initialization.
*)

Function RtcClock : LongInt;
Begin
   RtcClock := TickCount;
End;

(*
** MilliCount returns the real time clock periodic interrupt count for
** the current second.  Range of value is 0 to 1023.
*)

Function MilliCount : Integer;
Begin
   MilliCount := RtcCount;
End;

(*
** real time clock interrupt handler
*)

Procedure Rtc; Interrupt;
Begin
   Inline(CLI);
   Port[CMOSFLAG] := STATUSC;            { determine cause of interrupt }
   If (Port[CMOSDATA] and $40) <> 0 Then                { is it for us? }
   Begin
      Inc(RtcCount);    { update number of times ISR called this second }
      Inc(TickCount);             { update total number of times called }
      If RtcCount = 1024 Then             { if start of new second then }
         RtcCount := 0                                 { reset RtcCount }
      Else
      Begin
         Port[CMOSFLAG] := STATUSA;       { check it again for accuracy }
         If (Port[CMOSDATA] and UPDATE) <> 0 Then
            RtcCount := 0;
      End;
      Port[CMD1] := EOI;      { signal end of interrupt to primary 8259 }
      Port[CMD2] := EOI;      { signal end of interrupt to chained 8259 }
   End
   Else
      OldCall;                           { not for us, so call bios ISR }
   Inline(STI);
End;

(*
** turn on interrupts from the real time clock
*)

Procedure EnableRtcInts;
Begin
   RtcCount  := 0;                           { reset ISR counter values }
   TickCount := 0;
   GetIntVec(RTC_VEC,OldRtcVec);
   Move(OldRtcVec^,OldCall,Sizeof(Pointer));       { fake out Pascal... }
   SetIntVec(RTC_VEC,@Rtc);                { point to interrupt handler }
   Port[IMR2] := Port[IMR2] and RTC_MASK;      { enable clock interrupt }
   Port[CMOSFLAG] := STATUSB;
   OldMask := Port[CMOSDATA];                  { get rtc mask register  }
   Port[CMOSFLAG] := STATUSB;
   Port[CMOSDATA] := OldMask or RTC_FLAG;  { enable periodic interrupts }
End;

(*
** turn off interrupts from the real time clock
*)

Procedure DisableRtcInts;
Begin
   Port[CMOSFLAG] := STATUSB;
   Port[CMOSDATA] := OldMask;            { turn off periodic interrupts }
   Port[IMR2]     := Port[IMR2] and (not RTC_MASK);   { reset 8259 mask }
   SetIntVec(RTC_VEC,OldRtcVec);                       { remove our ISR }
End;

(*
** emulation of the C language's ctime() function
*)

Function CTime2(Time : LongInt) : TimeStrPtr;
Var
   Hr,Mn,Sc  : Word;
   Yr,Mo,Dy  : Word;
   Bias,Dw,T : Word;
   Junk,S    : Byte;
   Temp      : LongInt;
Begin
   Temp := Time mod SECS_PER_DAY;       { get seconds left for this day }
   Hr   := Temp div SECS_PER_HOUR;           { determine hours this day }
   Temp := Temp mod SECS_PER_HOUR;                { lose hours this day }
   Mn   := Temp div MINS_PER_HOUR;        { determine minutes this hour }
   Sc   := Temp mod SECS_PER_MIN;       { determine seconds this minute }

   Inline(CLI);
   Repeat                           { duplicate a bit of code for speed }
      Port[CMOSFLAG] := STATUSA;        { wait until not in update mode }
   Until (Port[CMOSDATA] and UPDATE) = 0;
   Port[CMOSFLAG] := CENTURY_REQ; T    := Port[CMOSDATA]; { get century }
   Port[CMOSFLAG] := YEAR_REQ;    Bias := Port[CMOSDATA]; { get year    }
   Port[CMOSFLAG] := MONTH_REQ;   Mo   := Port[CMOSDATA]; { get month   }
   Port[CMOSFLAG] := DATE_REQ;    Dy   := Port[CMOSDATA]; { get day     }
   Inline(STI);
   If Bcd Then                 { convert from BCD to binary as required }
   Begin
      T    := ((T    and HINIBBLE) shr 4) * 10 + (T    and LONIBBLE);
      Bias := ((Bias and HINIBBLE) shr 4) * 10 + (Bias and LONIBBLE);
      Mo   := ((Mo   and HINIBBLE) shr 4) * 10 + (Mo   and LONIBBLE);
      Dy   := ((Dy   and HINIBBLE) shr 4) * 10 + (Dy   and LONIBBLE);
   End;
   Inc(Bias,T * 100);

   Temp := Time div SECS_PER_DAY;   { get number of days for this value }
   Yr   := Temp div DAYS_PER_YEAR;            { now convert it to years }
   Bias := (Bias - BASE_YEAR) shr 2;     { get leap year days for value }
   Dy   := Temp - Yr * DAYS_PER_YEAR - Bias;     { get unprocessed days }
   Inc(Dy);                                          { add back 'today' }
   Inc(Yr,BASE_YEAR);                  { now add in the 1980 start date }
   Dw   := Time div SECS_PER_DAY + TUESDAY;      { 1-1-80 was a Tuesday }
   Dw   := Dw mod DAYS_PER_WEEK;                    { determine weekday }

   Mo   := 1;   S := 1;                { now determine the month's name }
   While S <> 0 Do              { process total remaining days for year }
   Begin
      Junk := 0;
      Case S of
         APRIL,
         JUNE,
         SEPTEMBER,
         NOVEMBER:   If Dy >= 30 Then         { month has 30 days in it }
                        Junk := 30;
         FEBRUARY:   If (Yr shr 2) = 0 Then     { special case february }
                        If Dy >= 29 Then
                           Junk := 29
                        Else
                     Else If Dy >= 28 Then
         Junk := 28;
    Else        If Dy >= 31 Then
                        Junk := 31;            { else month has 31 days }
      End;
      If Junk <> 0 Then
      Begin
         Inc(Mo);                    { account for month just processed }
         Inc(S);                                      { bump case index }
         Dec(Dy,Junk);                   { subtract days just processed }
      End
      Else
         S := 0;             { Dy is less than 1 month, clear while var }
   End;

   TimeStr[1]  := Days[Dw][1];     { now convert all values to a string }
   TimeStr[2]  := Days[Dw][2];                  { done inline for speed }
   TimeStr[3]  := Days[Dw][3];
   TimeStr[4]  := ' ';
   TimeStr[5]  := Months[Mo][1];
   TimeStr[6]  := Months[Mo][2];
   TimeStr[7]  := Months[Mo][3];
   TimeStr[8]  := ' ';
   TimeStr[9]  := Chr(Dy div   10 + Ord('0'));
   TimeStr[10] := Chr(Dy mod   10 + Ord('0'));
   TimeStr[11] := ' ';
   TimeStr[12] := Chr(Hr div   10 + Ord('0'));
   TimeStr[13] := Chr(Hr mod   10 + Ord('0'));
   TimeStr[14] := ':';
   TimeStr[15] := Chr(Mn div   10 + Ord('0'));
   TimeStr[16] := Chr(Mn mod   10 + Ord('0'));
   TimeStr[17] := ':';
   TimeStr[18] := Chr(Sc div   10 + Ord('0'));
   TimeStr[19] := Chr(Sc mod   10 + Ord('0'));
   TimeStr[20] := ' ';
   TimeStr[21] := Chr(Yr div 1000 + Ord('0'));  Yr := Yr mod 1000;
   TimeStr[22] := Chr(Yr div  100 + Ord('0'));  Yr := Yr mod 100;
   TimeStr[23] := Chr(Yr div   10 + Ord('0'));
   TimeStr[24] := Chr(Yr mod   10 + Ord('0'));
   TimeStr[0]  := Chr(24);
   CTime2 := @TimeStr;
End;


(*
** replacement for Turbo Pascal's GetTime procedure
*)

Procedure GetRtcTime(Var Hr,Mn,Sc,Hn : Word);
Begin
   Inline(CLI);
   Repeat
      Port[CMOSFLAG] := STATUSA;       { wait until not in update cycle }
   Until (Port[CMOSDATA] and UPDATE) = 0;
   Port[CMOSFLAG] := SECONDS_REQ;  Sc := Port[CMOSDATA];  { get seconds }
   Port[CMOSFLAG] := MINUTES_REQ;  Mn := Port[CMOSDATA];  { get minutes }
   Port[CMOSFLAG] := HOURS_REQ;    Hr := Port[CMOSDATA];  { get hour    }
   Inline(STI);
   If Bcd Then                 { convert from BCD to binary as required }
   Begin
      Sc := ((Sc and HINIBBLE) shr 4) * 10 + (Sc and LONIBBLE);
      Mn := ((Mn and HINIBBLE) shr 4) * 10 + (Mn and LONIBBLE);
      Hr := ((Hr and HINIBBLE) shr 4) * 10 + (Hr and LONIBBLE);
   End;
   Hn := RtcCount div 10;                       { RtcCount goes to 1024 }
   If Hn > 75 Then              { correct for values to 102 each second }
      Dec(Hn,3)
   Else If Hn > 50 Then
      Dec(Hn,2)
   Else If Hn > 25 Then
      Dec(Hn);
End;


(*
** replacement for Turbo Pascal's GetDate procedure
*)

Procedure GetRtcDate(Var Yr, Mo, Dy : Word);
Var T : Integer;
Begin
   Inline(CLI);
   Repeat
      Port[CMOSFLAG] := STATUSA;        { wait until not in update mode }
   Until (Port[CMOSDATA] and UPDATE) = 0;
   Port[CMOSFLAG] := CENTURY_REQ; T  := Port[CMOSDATA];   { get century }
   Port[CMOSFLAG] := YEAR_REQ;    Yr := Port[CMOSDATA];   { get year    }
   Port[CMOSFLAG] := MONTH_REQ;   Mo := Port[CMOSDATA];   { get month   }
   Port[CMOSFLAG] := DATE_REQ;    Dy := Port[CMOSDATA];   { get day     }
   Inline(STI);
   If Bcd Then            { convert time from BCD to binary as required }
   Begin
      T  := ((T  and HINIBBLE) shr 4) * 10 + (T  and LONIBBLE);
      Yr := ((Yr and HINIBBLE) shr 4) * 10 + (Yr and LONIBBLE);
      Mo := ((Mo and HINIBBLE) shr 4) * 10 + (Mo and LONIBBLE);
      Dy := ((Dy and HINIBBLE) shr 4) * 10 + (Dy and LONIBBLE);
   End;
   Inc(Yr,T * 100);                                    { add in century }
End;

(*
** emulation of the C language's time() function
*)

Procedure RtcTime(Var Where : LongInt);
Var
   Hr : LongInt;
   T,S,B,Yr,Sc,Mn,Mo,Dy : Word;
Begin
   Inline(CLI);                { following code is duplicated for speed }
   Repeat
      Port[CMOSFLAG] := STATUSA;
   Until (Port[CMOSDATA] and UPDATE) = 0;
   Port[CMOSFLAG] := SECONDS_REQ;  Sc := Port[CMOSDATA];  { get seconds }
   Port[CMOSFLAG] := MINUTES_REQ;  Mn := Port[CMOSDATA];  { get minutes }
   Port[CMOSFLAG] := HOURS_REQ;    Hr := Port[CMOSDATA];  { get hour    }
   Port[CMOSFLAG] := CENTURY_REQ;  T  := Port[CMOSDATA];  { get century }
   Port[CMOSFLAG] := YEAR_REQ;     Yr := Port[CMOSDATA];  { get year    }
   Port[CMOSFLAG] := MONTH_REQ;    Mo := Port[CMOSDATA];  { get month   }
   Port[CMOSFLAG] := DATE_REQ;     Dy := Port[CMOSDATA];  { get day     }
   Inline(STI);
   If Bcd Then            { convert time from BCD to binary as required }
   Begin
      Sc := ((Sc and HINIBBLE) shr 4) * 10 + (Sc and LONIBBLE);
      Mn := ((Mn and HINIBBLE) shr 4) * 10 + (Mn and LONIBBLE);
      Hr := ((Hr and HINIBBLE) shr 4) * 10 + (Hr and LONIBBLE);
      T  := ((T  and HINIBBLE) shr 4) * 10 + (T  and LONIBBLE);
      Yr := ((Yr and HINIBBLE) shr 4) * 10 + (Yr and LONIBBLE);
      Mo := ((Mo and HINIBBLE) shr 4) * 10 + (Mo and LONIBBLE);
      Dy := ((Dy and HINIBBLE) shr 4) * 10 + (Dy and LONIBBLE);
   End;

   Inline(STI);
   Mn := Mn * SECS_PER_MIN  + Sc;   { convert today's values to seconds }
   Hr := Hr * SECS_PER_HOUR + Mn;
   Inc(Yr,T * 100);                               { account for century }
   Dec(Yr,BASE_YEAR);                           { keep years since 1980 }
   Inc(Dy,(Yr shr 2));                               { check leap years }
   S  := 1;
   While S < Mo Do                             { add days for this year }
   Begin
      Case S of
         APRIL,
         JUNE,
         SEPTEMBER,                           { month has 30 days in it }
         NOVEMBER:   Inc(Dy,30);
         FEBRUARY:   If (Yr shr 2) = 0 Then { is this year a leap year? }
                        Inc(Dy,29)                                { yes }
                     Else
         Inc(Dy,28);                               { no  }
    Else        Inc(Dy,31);               { else month has 31 days }
      End;
      Inc(S);
   End;
   Dec(Dy);                                             { lose today... }
   Where := Yr * SECS_PER_YEAR +                   { return final value }
            Dy * SECS_PER_DAY  + Hr;
End;


(*
** Pascal substitute for Turbo-C's time() function, based on calls to
** GetDate, GetTime.  Provided for use on systems not equipped with a
** real time clock.
*)

Procedure Time2(Var Result : LongInt);
Var
   H : LongInt;
   S,Hr,Yr,Sc,Mn,Mo,Dy : Word;
Begin
   GetTime(Hr,Mn,Sc,S);                     { get time from Turbo Pascal }
   Mn := Mn * 60   + Sc;                            { convert to seconds }
   H  := Hr * 3600 + Mn;
   GetDate(Yr,Mo,Dy,S);                     { get date from Turbo Pascal }
   Dec(Yr,1980);                                  { get years since 1980 }

   Inc(Dy,Yr shr 2);                                  { check leap years }
   S  := 1;
   While S < Mo Do                              { add days for this year }
   Begin
      Case S of
         APRIL,
         JUNE,
         SEPTEMBER,
         NOVEMBER:   Inc(Dy,30);               { month has 30 days in it }
         FEBRUARY:   If (Yr shr 2) = 0 Then  { is this year a leap year? }
                        Inc(Dy,29)           { yes }
                     Else
         Inc(Dy,28);          { no  }
    Else        Inc(Dy,31);                { else month has 31 days }
      End;
      Inc(S);
   End;
   Result := (Yr * SECS_PER_YEAR +                  { return final value }
          Dy * SECS_PER_DAY  + H);
End;

(*
** unit initialization
*)

Begin
   Port[CMOSFLAG] := STATUSB;
   Bcd  := (Port[CMOSDATA] and BCD_MASK) = 0;       { check for BCD mode }
   Port[CMOSFLAG] := STATUSB;
   Port[CMOSDATA] := Port[CMOSDATA] or MASK_24;     { force 24 hour mode }
   RtcCount  := 0;
   TickCount := 0;
End.





[LISTING TWO]


(*
** TIME_PAS
** (C) Copyright 1990 by Kenneth Roach
** This program uses the time and date functions provided by Turbo Pascal
** compiler, as well as similar functions contained in the module TIMELIB.PAS.
** TIME_PAS calls each function for five seconds, counting the number of
** times the function in question was called.  It then compares the number
** of times each function was called and displays the results.  Following
** this, it displays the current date and time obtained from the
** GetRtcTime function, and as reported and converted by the RtcTime
** and CTime2 functions.
*)

Program TimePas;

Uses Dos,Crt,TimeLib;

Const
   TEST_TIME = 5120;                 { 5 seconds * 1024 ticks per second }

Var
   GrtCount    : LongInt;                 { counter for GetRtcTime calls }
   GtCount     : LongInt;                    { counter for GetTime calls }
   GrdCount    : LongInt;                 { counter for GetRtcDate calls }
   GdCount     : LongInt;                    { counter for GetDate calls }
   TCount      : LongInt;                       { counter for Time calls }
   RtCount     : LongInt;                    { counter for RtcTime calls }
   CtCount     : LongInt;                     { counter for CTime2 calls }
   Timer1      : LongInt;                { used in Time, RtcTime testing }
   Temp        : LongInt;
   Hr,Mn,Sc,Hn : Word;            { used in calls to GetTime, GetRtcTime }
   Yr,Mo,Dy,Dw : Word;            { used in calls to GetDate, GetRtcDate }
   St : TimeStrPtr;                             { used in CTime2 testing }

(*
** test performance of real time clock based time functions
*)

Procedure TestRtc;
Begin

   Writeln;
   Write('Testing GetRtcTime...');
   Temp := RtcClock;                       { get current time tick count }
   Repeat
      GetRtcTime(Hr,Mn,Sc,Hn);
      Inc(GrtCount);
   Until (RtcClock - Temp) = TEST_TIME;            { count for 5 seconds }

   Writeln;
   Write('Testing GetRtcDate...');
   Temp := RtcClock;
   Repeat
      GetRtcDate(Yr,Mo,Dy);
      Inc(GrdCount);
   Until (RtcClock - Temp) = TEST_TIME;            { count for 5 seconds }

   Writeln;
   Write('Testing RtcTime...');
   Temp := RtcClock;
   Repeat
      RtcTime(Timer1);
      Inc(RtCount);
   Until (RtcClock - Temp) = TEST_TIME;            { count for 5 seconds }

   Writeln;
   Write('Testing CTime2...');
   Temp := RtcClock;
   Repeat
      St := CTime2(Timer1);
      Inc(CtCount);
   Until (RtcClock - Temp) = TEST_TIME;            { count for 5 seconds }

End;

(*
** test performance of Turbo Pascal/DOS based time functions
*)

Procedure TestPas;
Begin

   Writeln;
   Write('Testing GetTime...');
   Temp := RtcClock;
   Repeat
      GetTime(Hr,Mn,Sc,Hn);
      Inc(GtCount);
   Until (RtcClock - Temp) = TEST_TIME;            { count for 5 seconds }

   Writeln;
   Write('Testing GetDate...');
   Temp := RtcClock;
   Repeat
      GetDate(Yr,Mo,Dy,Dw);
      Inc(GdCount);
   Until (RtcClock - Temp) = TEST_TIME;            { count for 5 seconds }

   Writeln;
   Write('Testing Time2...');
   Temp := RtcClock;
   Repeat
      Time2(Timer1);
      Inc(TCount);
   Until (RtcClock - Temp) = TEST_TIME;            { count for 5 seconds }

End;

(*
** determine percentage one value represents of another
*)

Function Percent(Count1,Count2 : LongInt) : LongInt;
Var Temp : LongInt;
Begin
   Temp := (Count1 * 100) div Count2;
   If ((Count1 * 100) mod Count2) >= 50 Then
      Inc(Temp);
   Percent := Temp;
End;

(*
** show results of timing tests
*)

Procedure DisplayResults;
Begin
   Writeln;
   Writeln('Test Summary:');
   Writeln;
   Writeln('GetTime    called ',GtCount,' times');
   Writeln('GetRtcTime called ',GrtCount,' times');
   If GrtCount > GtCount Then
      Writeln('GetRtcTime was ',Percent(GrtCount,GtCount),
                                '% the speed of GetTime')
   Else
      Writeln('GetTime    was ',Percent(GtCount,GrtCount),
                             '% the speed of GetRtcTime');

   Writeln;
   Writeln('GetDate    called ',GdCount,' times');
   Writeln('GetRtcDate called ',GrdCount,' times');
   If GrdCount > GdCount Then
      Writeln('GetRtcDate was ',Percent(GrdCount,GdCount),
                                '% the speed of GetDate')
   Else
      Writeln('GetDate    was ',Percent(GdCount,GrdCount),
                             '% the speed of GetRtcDate');

   Writeln;
   Writeln('Time2      called ',TCount,' times');
   Writeln('RtcTime    called ',RtCount,' times');
   If TCount > RtCount Then
      Writeln('Time2      was ',Percent(TCount,RtCount),
                              '% the speed of RtcTime')
   Else
      Writeln('RtcTime    was ',Percent(RtCount,TCount),
                                '% the speed of Time2');

   Writeln;
   Writeln('CTime2     called ',CtCount,' times');
End;


Begin
   GrtCount := 0;                         { initialize counter variables }
   GtCount  := 0;
   GrdCount := 0;
   GdCount  := 0;
   TCount   := 0;
   RtCount  := 0;
   CtCount  := 0;

   EnableRtcInts;

   ClrScr;

   TestRtc;               { test the functions using the real time clock }
   TestPas;            { test the normal Pascal/DOS based time functions }

   DisplayResults;

   Writeln;
   Writeln('End of test.');
   Writeln('Start time display.');
   Writeln('Depress any key to stop');
   Writeln;
   While not KeyPressed Do
   Begin
      GetRtcTime(Hr,Mn,Sc,Hn);
      RtcTime(Timer1);
      Write(Chr(13),Hr:2,':',Mn:2,':',Sc:2,'.',Hn:2,
                         '       ',CTime2(Timer1)^);
   End;

   DisableRtcInts;
End.




>

_USING THE REAL-TIME CLOCK_
by Kenneth Roach

[TURBO C VERSION]


[TIME_C.C]

/*
** TIME_C
** (C) Copyright 1990 by Kenneth Roach
** Version date: 3 November, 1990
**
** This program uses the time and date functions provided by the Turbo-C
** compiler, as well as similar functions contained in the module TIMELIB.C.
** TIME_C calls each function for five seconds, counting the number of
** times the function in question was called.  It then compares the number
** of times each function was called and displays the results.  Following
** this, it displays the current date and time obtained from the
** get_rtc_time function, and as reported and converted by the rtc_time
** and ctime2 functions.
*/

#include <stdio.h>
#include <dos.h>
#include <time.h>
#include "timelib.h"

long   grt_count = 0L;              /* counter for get_rtc_time() calls */
long   grd_count = 0L;              /* counter for get_rtc_date() calls */
long   rt_count  = 0L;                  /* counter for rtc_time() calls */
long   gt_count  = 0L;                   /* counter for gettime() calls */
long   gd_count  = 0L;                   /* counter for getdate() calls */
long   t_count   = 0L;                      /* counter for time() calls */
long   t2_count  = 0L;                     /* counter for time2() calls */
long   ct2_count = 0L;                    /* counter for ctime2() calls */
long   ct_count  = 0L;                     /* counter for ctime() calls */
struct time t;              /* used in testing of gettime, get_rtc_time */
struct date d;              /* used in testing of getdate, get_rtc_date */
char   *str;                           /* used in testing ctime, ctime2 */
time_t timer;                  /* used in testing time, time2, rtc_time */
long   temp;

#define TEST_TIME 5120L              /* 5 seconds * 1024 interrupts per */

/*
**  test performance of real time clock based time functions
*/

void test_rtc()
{
   printf("\nTesting get_rtc_time...");
   temp = rtc_clock();
   do {
      get_rtc_time(&t);
      ++grt_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */

   printf("\nTesting get_rtc_date...");
   temp = rtc_clock();
   do {
      get_rtc_date(&d);
      ++grd_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */

   printf("\nTesting rtc_time...");
   temp = rtc_clock();
   do {
      rtc_time(&timer);
      ++rt_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */

   printf("\nTesting ctime2...");
   temp = rtc_clock();
   do {
      str = ctime2(&timer);
      ++ct2_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */
}

/*
**  test performance of C's DOS based time functions
*/

void test_c()
{

   printf("\nTesting gettime...");
   temp = rtc_clock();
   do {
      gettime(&t);
      ++gt_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */

   printf("\nTesting getdate...");
   temp = rtc_clock();
   do {
      getdate(&d);
      ++gd_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */

   printf("\nTesting time...");
   temp = rtc_clock();
   do {
      time(&timer);
      ++t_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */

   printf("\nTesting time2...");
   temp = rtc_clock();
   do {
      time2(&timer);
      ++t2_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */

   printf("\nTesting ctime...");
   temp = rtc_clock();
   do {
      str = ctime(&timer);
      ++ct_count;
   } while(rtc_clock() - temp < TEST_TIME);      /* count for 5 seconds */
}

/*
** determine percentage one value represents of another
*/

long percent(long count1,long count2)
{
   long temp;
   temp = (count1 * 100L) / count2;
   if(((count1 * 100L) % count2) >= 50L)
      ++temp;
   return(temp);
}

/*
** show results of timing tests
*/

void display_results()
{
   printf("\nTest Summary:\n");
   printf("\ngettime()      called %6ld times\n",gt_count);
   printf("get_rtc_time() called %6ld times\n",grt_count);
   if(grt_count > gt_count)
      printf("get_rtc_time() was %02ld%% the speed of gettime()\n",
                             percent(grt_count,gt_count));
   else
      printf("gettime()      was %02ld%% the speed of get_rtc_time()\n",
                             percent(gt_count,grt_count));

   printf("\ngetdate()      called %6ld times\n",gd_count);
   printf("get_rtc_date() called %6ld times\n",grd_count);
   if(grd_count > gd_count)
      printf("get_rtc_date() was %02ld%% the speed of getdate()\n",
                             percent(grd_count,gd_count));
   else
      printf("getdate()      was %02ld%% the speed of get_rtc_date()\n",
                             percent(gd_count,grd_count));

   printf("\ntime()         called %6ld times\n",t_count);
   printf("time2()        called %6ld times\n",t2_count);
   printf("rtc_time()     called %6ld times\n",rt_count);
   if(rt_count > t_count)
      printf("rtc_time()     was %02ld%% the speed of time()\n",
                             percent(rt_count,t_count));
   else
      printf("time()         was %02ld%% the speed of rtc_time()\n",
                             percent(t_count,rt_count));

   printf("\nctime()        called %6ld times\n",ct_count);
   printf("ctime2()       called %6ld times\n",ct2_count);
   if(ct2_count > ct_count)
      printf("ctime2()       was %02ld%% the speed of ctime()\n",
                             percent(ct2_count,ct_count));
   else
      printf("ctime()        was %02ld%% the speed of ctime2()\n",
                             percent(ct_count,ct2_count));
}

void main()
{
   enable_rtc_ints();

   clrscr();

   test_rtc();          /* test the functions using the real time clock */
   test_c();              /* test the normal C/DOS based time functions */

   display_results();

   printf("\nEnd of test.\nStart time display.\nDepress any key to stop\n\n");

   while(!kbhit())
   {
      get_rtc_time(&t);
      rtc_time(&timer);
      printf("\r%02.2d:%02.2d:%02.2d.%02.2d       %-24.24s",
             t.ti_hour,t.ti_min,t.ti_sec,t.ti_hund,ctime(&timer));
   }

   disable_rtc_ints();
}



[TIMELIB.C}


/*
** TIMELIB.C
** (C) Copyright 1990 by Kenneth Roach
** Version date: 3 November, 1990
**
** This module contains functions similar to ANSI C's time(), gettime() and
** getdate(), and clock() functions, but which are based on use of the AT
** class of system's real time clock.  Additionally, functions are provided
** to enable and disable periodic interrupts from the real time clock along
** with an intterupt handler for same.  Interrupts from the real time clock
** are provided at a rate of 1024 per second, and a function is provided to
** return the number of interrupts received in the current second.  Also
** provided is a replacement for the C language's ctime() function which is
** modestly faster.
*/

#pragma inline

#include <stdio.h>
#include <dos.h>
#include <time.h>
#include "timelib.h"

#define  CMOSFLAG       0x70
#define  CMOSDATA       0x71
#define  SECONDS_REQ    0x00
#define  MINUTES_REQ    0x02
#define  HOURS_REQ      0x04
#define  STATUSA        0x0a
#define  STATUSB        0x0b
#define  STATUSC        0x0c
#define  DATE_REQ       0x07
#define  MONTH_REQ      0x08
#define  YEAR_REQ       0x09
#define  CENTURY_REQ    0x32
#define  UPDATE         0x80
#define  BCD            0x04
#define  MASK_24        0x02
#define  HINIBBLE       0xf0
#define  LONIBBLE       0x0f

#define  APRIL          4
#define  JUNE           6
#define  SEPTEMBER      9
#define  NOVEMBER       11
#define  FEBRUARY       2

#define  RTC_VEC        0x70
#define  IMR2           0xa1
#define  CMD1           0x20
#define  CMD2           0xa0
#define  EOI            0x20
#define  RTC_MASK       0xfe
#define  RTC_FLAG       0x40

#define  SECS_PER_DAY   86400L
#define  SECS_PER_YEAR  31536000L
#define  BIAS_10_YEARS  315532800L   /* difference between 1970 and 1980 */
#define  BASE_YEAR      1980
#define  SECS_PER_MIN   60
#define  SECS_PER_HOUR  3600
#define  MINS_PER_HOUR  60
#define  DAYS_PER_YEAR  365
#define  DAYS_PER_WEEK  7
#define  TUESDAY        3                    /* day of week for 1-1-1980 */

#define bcd_bin(x)     (bcd) ? ((((x & HINIBBLE) >> 4)\
* 10) + (x & LONIBBLE)) : (x)

char months[12][4] = {"Jan","Feb","Mar","Apr","May","Jun",
                      "Jul","Aug","Sep","Oct","Nov","Dec"};
char days[7][4]    = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};

extern   long timezone;

volatile int  rtc_count  = 0;
volatile long tick_count = 0L;

void interrupt (*old_rtc_vec)();

int func_init = 0;
int bcd  = 0;
int dst  = 0;
unsigned int old_mask;
char time_str[26];

/*
** replacement for the Turbo-C clock() function.  rtc_clock returns
** a value corresponding to the number of periodic interrupts which
** have occurred since interrupts from the real time clock were
** enabled. The value will remain positive for some 24 days from
** initialization.
*/

clock_t rtc_clock()
{
   return(tick_count);
}

/*
** millicount returns the real time clock periodic interrupt count for
** the current second.  Range of value is 0 to 1023.
*/

int milli_count()
{
   return(rtc_count);
}

/*
** real time clock interrupt handler
*/

void interrupt rtc()
{
   asm cli;
   outportb(CMOSFLAG,STATUSC); /* get interrupt register identification */
   if((inportb(CMOSDATA) & 0x40) != 0)     /* if a "periodic" interrupt */
   {
      if(++rtc_count == 1024)   /* update nbr times ISR called this sec */
         rtc_count = 0;      /* if start of new second, reset rtc_count */
      else
      {
         outportb(CMOSFLAG,STATUSA);     /* check it again for accuracy */
         if(inportb(CMOSDATA) & UPDATE)
            rtc_count = 0;
      }
      ++tick_count;              /* update total number of times called */
      outportb(CMD1,EOI);    /* signal end of interrupt to primary 8259 */
      outportb(CMD2,EOI);    /* signal end of interrupt to chained 8259 */
   }
   else
      (*old_rtc_vec)();
   asm sti;
}

/*
** turn on interrupts from the real time clock
*/

void enable_rtc_ints()
{
   rtc_count   = 0;
   tick_count  = 0L;
   old_rtc_vec = getvect(RTC_VEC);
   setvect(RTC_VEC,rtc);                  /* point to interrupt handler */
   outportb(IMR2,inportb(IMR2) & RTC_MASK);   /* enable clock interrupt */
   outportb(CMOSFLAG,STATUSB);
   old_mask = inportb(CMOSDATA);              /* get rtc mask register  */
   outportb(CMOSFLAG,STATUSB);
   outportb(CMOSDATA,old_mask | RTC_FLAG);    /* enable 1k interrupts   */
}

/*
** turn off interrupts from the real time clock
*/

void disable_rtc_ints()
{
   outportb(CMOSFLAG,STATUSB);
   outportb(CMOSDATA,old_mask);         /* turn off periodic interrupts */
   outportb(IMR2,inportb(IMR2) & ~RTC_MASK);   /* diable RTC interrupts */
   setvect(RTC_VEC,old_rtc_vec);        /* restore old interrupt vector */
}

/*
** replacement for the C language's ctime() function
*/

char *ctime2(time_t *t)
{
   unsigned int hr,mn,sc;
   unsigned int yr,mo,dy;
   unsigned int bias,dw;
   int    junk,s,tp;
   long   temp;
   time_t time;

   time = *t - BIAS_10_YEARS;
   if(dst)
      time -= 3600L;                 /* compensate for daylight savings */
   time -= timezone;
   temp = time % SECS_PER_DAY;         /* get seconds left for this day */
   hr   = temp / SECS_PER_HOUR;             /* determine hours this day */
   temp %=       SECS_PER_HOUR;                  /* lose hours this day */
   mn   = temp / MINS_PER_HOUR;          /* determine minutes this hour */
   sc   = temp % SECS_PER_MIN;         /* determine seconds this minute */

   asm cli;
   do                            /* following code duplicated for speed */
      outportb(CMOSFLAG,STATUSA);     /* wait until not in update cycle */
   while(inportb(CMOSDATA) & UPDATE);
   outportb(CMOSFLAG,CENTURY_REQ); s = inportb(CMOSDATA); tp   = bcd_bin(s);
   outportb(CMOSFLAG,YEAR_REQ);    s = inportb(CMOSDATA); bias = bcd_bin(s);
   outportb(CMOSFLAG,MONTH_REQ);   s = inportb(CMOSDATA); mo   = bcd_bin(s);
   outportb(CMOSFLAG,DATE_REQ);    s = inportb(CMOSDATA); dy   = bcd_bin(s);
   asm sti;

   bias = bias + tp * 100 - BASE_YEAR;
   temp = time / SECS_PER_DAY;     /* get number of days for this value */
   yr   = temp / DAYS_PER_YEAR;              /* now convert it to years */
   bias >>= 2;                          /* get leap year days for value */
   dy   = temp - yr * DAYS_PER_YEAR - bias;     /* get unprocessed days */
   yr  += BASE_YEAR;                  /* now add in the 1980 start date */
   dw   = time / SECS_PER_DAY + TUESDAY;        /* 1-1-80 was a Tuesday */
   dw  %= DAYS_PER_WEEK;                           /* determine weekday */
   --dw;
   s  = 1;                            /* now determine the month's name */
   mo = 0;
   while(s)                    /* process total remaining days for year */
   {
      junk = 0;
      switch(s)
      {
         case APRIL:                    /* first do months with 30 days */
         case JUNE:
         case SEPTEMBER:
         case NOVEMBER: if(dy  >= 30)
                           junk = 30;     break;
         case FEBRUARY: if((yr >> 2) == 0)     /* special case february */
                           if(dy  >= 29)
                              junk = 29;           /* process leap year */
                           else
                              ;
                        else if(dy >= 28)            /* not a leap year */
                           junk = 28;     break;
         default:       if(dy  >= 31)
                           junk = 31;         /* else month has 31 days */
      }
      if(junk)
      {
         ++mo;                      /* account for month just processed */
         ++s;                                        /* bump case index */
         dy -= junk;                    /* subtract days just processed */
      }
      else
         s = 0;             /* Dy is less than 1 month, clear while var */
   }

   time_str[0]  = days[dw][0];    /* now convert all values to a string */
   time_str[1]  = days[dw][1];       /* avoid call to sprintf for speed */
   time_str[2]  = days[dw][2];
   time_str[4]  = months[mo][0];
   time_str[5]  = months[mo][1];
   time_str[6]  = months[mo][2];
   time_str[8]  = dy /   10 + '0';
   time_str[9]  = dy %   10 + '0';
   time_str[11] = hr /   10 + '0';
   time_str[12] = hr %   10 + '0';
   time_str[14] = mn /   10 + '0';
   time_str[15] = mn %   10 + '0';
   time_str[17] = sc /   10 + '0';
   time_str[18] = sc %   10 + '0';
   time_str[20] = yr / 1000 + '0';  yr %= 1000;
   time_str[21] = yr /  100 + '0';  yr %= 100;
   time_str[22] = yr /   10 + '0';
   time_str[23] = yr %   10 + '0';
   time_str[24] = '\n';
   time_str[25] = 0;
   time_str[3]  = time_str[7]  = time_str[10] = time_str[19] = ' ';
   time_str[13] = time_str[16] = ':';
   return(time_str);
}

/*
** replacement for Turbo-C's gettime() function
*/

void get_rtc_time(struct time *timep)
{
   int h,m,s;
   if(!func_init)
      init_time();                       /* assure we have info we need */
   asm cli;
   do
      outportb(CMOSFLAG,STATUSA);     /* wait until not in update cycle */
   while(inportb(CMOSDATA) & UPDATE);

   outportb(CMOSFLAG,HOURS_REQ);                         /* get hours   */
   h = inportb(CMOSDATA); timep->ti_hour = bcd_bin(h);
   outportb(CMOSFLAG,MINUTES_REQ);                       /* get minutes */
   m = inportb(CMOSDATA); timep->ti_min  = bcd_bin(m);
   outportb(CMOSFLAG,SECONDS_REQ);                       /* get seconds */
   s = inportb(CMOSDATA);    timep->ti_sec  = bcd_bin(s);
   asm sti;
   s = rtc_count / 10;                        /* rtc_count goes to 1024 */
   if(s > 75)                  /* correct for values to 102 each second */
      s -= 3;
   else if(s > 50)
      s -= 2;
   else if(s > 25)
      --s;
   timep->ti_hund = s;
}

/*
** replacement for Turbo-C's getdate() function
*/

void get_rtc_date(struct date *datep)
{
   int d,m,y,t,s;
   if(!func_init)
      init_time();                       /* assure we have info we need */
   asm cli;
   do
      outportb(CMOSFLAG,STATUSA);     /* wait until not in update cycle */
   while(inportb(CMOSDATA) & UPDATE);

   outportb(CMOSFLAG,CENTURY_REQ);                       /* get century */
   s  = inportb(CMOSDATA);   t              = bcd_bin(s);
   outportb(CMOSFLAG,YEAR_REQ);                          /* get year    */
   y = inportb(CMOSDATA);    datep->da_year = bcd_bin(y);
   outportb(CMOSFLAG,MONTH_REQ);                         /* get month   */
   m = inportb(CMOSDATA);    datep->da_mon  = bcd_bin(m);
   outportb(CMOSFLAG,DATE_REQ);                          /* get day     */
   d = inportb(CMOSDATA);    datep->da_day  = bcd_bin(d);
   asm sti;
   datep->da_year = datep->da_year + t * 100;         /* add in century */
}

/*
** replacement for Turbo-C's time() function
*/

time_t rtc_time(time_t *result)
{
   time_t   hr;
   unsigned s,b,yr,sc,mn,mo,dy;
   if(!func_init)
      init_time();                       /* assure we have info we need */
   asm cli;                   /* following code is duplicated for speed */
   do
      outportb(CMOSFLAG,STATUSA);        /* wait until not update cycle */
   while(inportb(CMOSDATA) & UPDATE);

   outportb(CMOSFLAG,SECONDS_REQ);                       /* get seconds */
   s  = inportb(CMOSDATA);   sc = bcd_bin(s);
   outportb(CMOSFLAG,MINUTES_REQ);                       /* get minutes */
   s  = inportb(CMOSDATA);   mn = bcd_bin(s);
   outportb(CMOSFLAG,HOURS_REQ);                         /* get hours   */
   s  = inportb(CMOSDATA);   hr = bcd_bin(s);

   outportb(CMOSFLAG,YEAR_REQ);                          /* get year    */
   s  = inportb(CMOSDATA);   yr = bcd_bin(s);
   outportb(CMOSFLAG,CENTURY_REQ);                       /* get century */
   s  = inportb(CMOSDATA);   b  = bcd_bin(s);
   outportb(CMOSFLAG,MONTH_REQ);                         /* get month   */
   s  = inportb(CMOSDATA);   mo = bcd_bin(s);
   outportb(CMOSFLAG,DATE_REQ);                          /* get day     */
   s  = inportb(CMOSDATA);   dy = bcd_bin(s);
   asm sti;

   mn = mn * 60 + sc;                     /* convert minutes to seconds */
   hr = hr * 3600 + mn + timezone;          /* convert hours to seconds */
   yr = yr + b * 100 - 1980;                    /* get years since 1980 */
   dy = dy + (yr >> 2);                  /* correct days for leap years */
   s  = 1;
   while(s < mo)                              /* add days for this year */
      switch(s++)
      {
         case APRIL:                             /* months with 30 days */
         case JUNE:
         case SEPTEMBER:
         case NOVEMBER:   dy += 30L;                              break;
         case FEBRUARY:   dy += ((yr >> 2) == 0)  ?  29L  :  28L; break;
         default:         dy += 31L;          /* else month has 31 days */
      }
   if(dst)
      hr -= 3600L;                   /* compensate for daylight savings */
   return(*result = (yr * SECS_PER_YEAR +         /* return final value */
                     dy * SECS_PER_DAY  +
                     hr + BIAS_10_YEARS)); /* 10 yr bias for difference */
                                        /* between 1970 and 1980 (secs) */
}

/*
** replacement for Turbo-C's time() function
*/

time_t time2(time_t *result)
{
   time_t   hr;
   unsigned s,yr,mn,mo,dy;
   struct date d;
   struct time t;
   asm cli;

   getdate(&d);
   gettime(&t);
   mn = t.ti_min * 60 + t.ti_sec;         /* convert minutes to seconds */
   hr = t.ti_hour * 3600 + mn + timezone;   /* convert hours to seconds */
   yr = d.da_year - 1980;                       /* get years since 1980 */
   dy = d.da_day + (yr >> 2);            /* correct days for leap years */
   s  = 1;
   mo = d.da_mon;
   while(s < mo)                              /* add days for this year */
      switch(s++)
      {
         case APRIL:                             /* months with 30 days */
         case JUNE:
         case SEPTEMBER:
         case NOVEMBER:   dy += 30L;                              break;
         case FEBRUARY:   dy += ((yr >> 2) == 0)  ?  29L  :  28L; break;
         default:         dy += 31L;          /* else month has 31 days */
      }
   if(dst)
      hr -= 3600L;                   /* compensate for daylight savings */
   asm sti;
   return(*result = (yr * SECS_PER_YEAR +         /* return final value */
                     dy * SECS_PER_DAY  +
                     hr + BIAS_10_YEARS)); /* 10 yr bias for difference */
                                        /* between 1970 and 1980 (secs) */
}
/*
** initialize variables for rtc time and date functions
*/

void init_time()
{
   struct tm *cur_time;
   time_t timer;
   time(&timer);                           /* kick start TC's time code */
   cur_time = localtime(&timer);     /* check for daylight savings time */
   dst      = cur_time->tm_isdst;
   outportb(CMOSFLAG,STATUSB);              /* get mode the clock is in */
   bcd      = (inportb(CMOSDATA) & BCD) == 0;    /*   (binary or BCD)   */
   outportb(CMOSFLAG,STATUSB);
   outportb(CMOSDATA,inportb(CMOSDATA) | MASK_24);/* force 24 hour mode */
   func_init = 1;
}



[TIMELIB.H]


/*
** TIMELIB.H
**
** prototype declarations for TIMELIB.C
*/


clock_t rtc_clock();
int     milli_count();
void    enable_rtc_ints();
void    disable_rtc_ints();
void    get_rtc_time(struct time *timep);
void    get_rtc_date(struct date *datep);
time_t  rtc_time(time_t *result);
time_t  time2(time_t *result);
void    init_time();
char    *ctime2();

Copyright © 1991, Dr. Dobb's Journal