Midnight rollover and proper date

 

The IBM PC and it's compatibles have a one byte flag in memory that indicates if the system clock has past MIDNIGHT. The change in this byte from 0 to 1 is how common PC BIOS knows to change the date by 1 day. To quote Peter Norton, "This byte is set to 1 at MIDNIGHT and is not incremented. There is no indication if two MIDNIGHTS pass before the clock is read." So if the computer is left on with dBASE running and two MIDNIGHTS pass, the PC's date is not changed at MIDNIGHT.

Workaround:
If a program makes a certain BIOS call (Interrupt 1Ah, Time-of-day service) before MIDNIGHT, then the date will updated properly. Many programs use this call, but typically only at start-up. To work around the MIDNIGHT rollover problem, RUN a program that uses this BIOS service from within dBase. Such a program is admittedly hard to find, but Novell Netware users can RUN Systime, which is a utility that updates the workstation time and date to reflect the time and date on the server.

A couple of TI's for other Borland products that explain this problem in more depth:


PRODUCT : Borland C++
NUMBER : 1703
VERSION : All
OS : DOS
DATE : October 25, 1993
PAGE : 1/3

TITLE : Possible conflicts with DOS time and date functions

Every IBM compatible PC is capable of keeping track of the current date and time. Your PC can keep the current time even when it is turned off, because of a clock that is built in to the CMOS. However, this CMOS clock is only used once, when your computer is turned on. From this point on the date/time is maintained by both BIOS and DOS working together. Because of some quirks in the way BIOS and DOS work, some problems can occur if you are not careful in your program.

To understand these problems and how to avoid them, we first need to see how BIOS and DOS stores the time. The BIOS keeps two variables in memory at the following locations:

0x0040:0x006C - # of ticks since MIDNIGHT (double word)
0x0040:0x0070 - MIDNIGHT flag (byte)

These values are updated 18.2 times a second, by the system timer. This timer does nothing but increment the tick count, and check to see if MIDNIGHT has passed. If MIDNIGHT passes, the tick count is reset to 0, and the MIDNIGHT flag is set to 1. It is important to note the MIDNIGHT flag is a Boolean, not a count. That is, it doesn't count the number of times MIDNIGHT has passed, it only indicates that MIDNIGHT has passed "at least once".

It is also important to note that the BIOS doesn't update the actual clock time or date. It only updates the timer tick count and MIDNIGHT flag. DOS maintains the time and date, but only when you ask it to retrieve these values.

There are several ways to find the current date and time. Listed below are BIOS/DOS interrupts, and their corresponding functions from the Borland C++ run-time library:

BIOS int 0x1A,0x00 - read system time (biostime)
BIOS int 0x1A,0x01 - DOS system time (biostime)
DOS int 0x21,0x2A - get DOS date (getdate, _dos_getdate)
DOS int 0x21,0x2B - set DOS date (setdate, _dos_setdate)
DOS int 0x21,0x2C - get DOS time (gettime, _dos_gettime)
DOS int 0x21,0x2D - set DOS time (settime, _dos_settime)

The DOS functions operate by first calling the BIOS function to get the timer tick count and MIDNIGHT flag, and then calculating the appropriate values. The date is only updated when you call one of the DOS date functions.

The first problem that can occur is due to the fact that the MIDNIGHT flag is Boolean. DOS only updates the date when you call a DOS date function, and the MIDNIGHT flag is reset at this time. Consider the following scenario:

Actual
date/time

F

Event
DOS date
after event
12/01 12:00   turn on computer 12/01
12/02 00:00 X MIDNIGHT flag is set 12/01
12/02 15:45   call DOS getdate function 12/02
12/03 00:01 X MIDNIGHT flag set 12/02
12/04 00:00 X MIDNIGHT flag set (again) 12/02
12/04 11:30   call DOS getdate function 12/03

If an entire day passes with no call to a date function, the internal DOS date never gets updated, causing a day to be missed.

The second problem comes in because of how BIOS int 0x1A operates. Whenever you call this function to retrieve the system time (the current timer tick value) it also returns the current MIDNIGHT flag and RESETS THE FLAG. But since the BIOS function doesn't update the DOS date, the next time you ask for the date, it will not be updated correctly. DOS is aware of the behavior, so when you call any DOS function, the MIDNIGHT flag is maintained correctly. If you call BIOS int 0x1A yourself, you MUST check the MIDNIGHT flag value and turn it back on if it was set.

The following function is a safe replacement for the biostime() function. It properly restores the MIDNIGHT flag, avoiding the problem described above:

long safebiostime(int cmd, long newtime) { 
   long temptime; 
   asm { 
      mov ax,cmd 
      mov dx,word ptr newtime 
      mov cx,word ptr newtime+2 
      int 0x1a 
      mov word ptr temptime,dx 
      mov word ptr temptime+2,cx 
      or al,al 
      jz not_midnight 
      mov ax,0x40 
      mov es,ax 
      mov bx,0x70 
      mov byte ptr es:[bx],1 
   } 
not_midnight: 

   return temptime; 
} 

Turbo Debugger 2.0
midnight UPDATE: How does DOS update the DATE at MIDNIGHT ?

When BIOS detects that it is MIDNIGHT, it clears out the tick-counter (unsigned long at location 0x40:0cx6C) to start a new day and sets the MIDNIGHT flag (unsigned int at location 0x40:0x70) to 1. The calendar of the PC, however, is maintained by the CLOCK$ device. When one asks DOS for the time or date (i.e. at the DOS prompt or via INT 0x21, Func. 0x2A/0x2Ch), the latter passes the request to the CLOCK$ device. The device uses the BIOS Function 0x1A to fulfill the request. When called upon, BIOS always returns the contents of the MIDNIGHT flag in register AL and then resets the flag to zero if necessary. The CLOCK$ device verifies the value in register AL after calling BIOS and adjusts the calendar date if necessary. Then DOS merely returns to the user the answer received from the CLOCK$ device.

However, the above scenario has two major loopholes:

  1. What if somebody other than the CLOCK$ device (i.e. Other than DOS) were to call the BIOS function 0x1A just after MIDNIGHT?

  2. What if a machine were to remain running but unattended for two days or more with no requests for the date/time being made to DOS?

Both of the above would result in an incorrect date on the computer. In the first case mentioned above, the caller of INT 0x1A will be notified that mIDNIGHT just passed by but the calendar will not be updated since the CLOCK$ device is out of the loop.

BIOS does not treat the mIDNIGHT Flag as a counter which must be incremented for each MIDNIGHT passed by. It merely sets the flag to 1 for each MIDNIGHT passed. Therefore in the second case above, the CLOCK$ device (if eventually called upon) will be unable to properly identify the actual number of MIDNIGHTS gone by.

What are the solutions ?

For the first case mentioned above, direct calls to BIOS for the current date/time should be avoided and DOS should be called instead. This is unfortunately easier said than done. For example, applications performing asynchronous processing must constantly monitor the time which is, at best, difficult to do via DOS calls given the non-reentrant nature of the latter.

For the second case mentioned above, one workaround would be to use a TSR which constantly monitors the mIDNIGHT Flag. Once the latter is set, a call to DOS for the current Date is made by the TSR after determining that it is safe to do so. This would ensure that the calendar gets updated when the PC is running but left unattended.

What if my TSR makes calls to BIOS INT 0x1A in the background ?

(According to a message posted in the IBMPRO Forum on CIS, there is a commercial utility which sends faxes at regular intervals and uses BIOS to monitor the time ) ? In this case the TSR may intercept every call to the BIOS INT 0x1A and determine (via the DOS BUSY FLAG ?) whether the call is actually being made via a DOS call [ To be absolutely certain, the TSR could also TRAP INT 21h ] and if the call is not done via DOS, the TSR can verify the mIDNIGHT Flag and take appropriate steps by calling DOS if necessary.

NOTE: The current version of the Borland C++' startup code makes a call to the BIOS Interrupt 0x1A. Running a BC++ application just after MIDNIGHT may prevent the Calendar Date from being properly updated. Users which do not call any of the C/C++ clock() related functions, can safely comment out lines 264-267 from c0.asm and create a new copy of the startup code object if necessary.

For more info and possible patches see the following files on Compuserve:

Fixes DOS midnight rollover bug
DATASTORM/DOS/Comm Utilities CLOCKF.SYS
Text & Utilities deal with "Midnight Rollover"
DATASTORM/News/General Info MIDNIT.EXE
PC:Clockfix for date change at midnight.
FIFTHG/Direct Access PC CLOCKF.SYS
Fix for DOS date after multiple midnights left on
IBMHW/Gen. Hardware [H] CLKFX2.COM
Everything you ever wanted your clocks to do
IBMHW/Gen. Hardware [H] CLOCK.EXE
Correctly rollover date at midnight
MSDOS/Shareware (MS-DOS) CLKFIX.SYS
Everything you ever wanted your clocks to do
MSDOS/Shareware (MS-DOS) CLOCK.EXE
The "Midnight Rollover" Problem
QUARTERDECK/DESQview Technotes MIDNIG.TEC
Midnight clock fix for DOS
SYMUTIL/pcANYWHERE DOS CLOCKF.SYS

[Home] [FAQ Index]