logo To Foot
© J R Stockton, ≥ 2008-02-13

Borland Pascal Time and Date

Links within this site :-

Parts of this apply also to Delphi; but see also in Delphi Programming.

Atomisation

Some portions of code, including access to date and time, must not be interrupted - i.e. must be made atomic. This can be done by disabling interrupts :-

 Cli ; { now atomic } ; Sti ;

or (better, lest interrupts were already disabled) by

 PushF ; Cli ; { now atomic } ; PopF

However, Jason Burgon remarks (1998-11-20) : "popf will ~not~ restore the interrupt enable flag under DPMI or V86 mode". As BP/TP accesses longints/dwords as two individual words at the hardware level, an example is any longint access where the value may be changed by another process, for example the DOS timer at $40:$6C.

One can use

  Procedure Pushf ; Inline($9C) ;
  Procedure Cli ;   Inline($FA) ;
  Procedure Popf ;  Inline($9D) ;

or

  asm PushF Cli end ;
    { uninterrupted code }
  asm PopF end ;

or

  inline($9C/$FA) { PushF Cli } ;
    { uninterrupted code }
  inline($9D) { PopF } ;

but not, for example,

  Procedure Pushf; Assembler;
     asm Pushf end ;

On Wed, 17 Nov 1999 in news:comp.lang.pascal.borland, Olivier Avenel <avenel@spec.saclay.cea.fr> wrote:
For reliable accurate timing or any other real time operation on a PC, there is no doubt that interrupts must be disabled. In DOS exclusive mode, it's pretty easy :-

  var Inter : byte ;
  .....
  Inter := Port[$21] ;   {save interrupts}
  Port[$21] := $FF ;     {disable interrupts}
  ..... {some critical real time data processing} .....
  Port[$21] := Inter ;   {re-enable interrupts}

As an alternative to forcing uninterrupted access, it may be possible to ensure that the final result is correct - see below.

RTC & CMOS

Interrupts should also be disabled while accessing the RTC/CMOS at I/O ports $70, $71, lest some other process attempt simultaneous access.

  PushF ; Cli ; Port[$70]:=SubAdr ; Datum:=Port[$71] ; PopF ;

But PushF ; Cli ; ... ; PopF may only work in MSDOS mode.

The DOS Clock

In MS-DOS, a local time-of-day count is kept in the hardware longword (32 bits) at physical address hexadecimal 0046C, which is normally referred to as $40:$6C or Seg0040:$006C (Seg0040 was introduced in Pascal to allow future flexibility, which has not in fact been used; I believe that it is always given the value $0040). Other systems, e.g. Windows, provide or emulate this count. This is the prime source of date and time for programs to use; the RTC should only be accessed exceptionally.

It is incremented by MS-DOS at about 18.2 Hz, which is a standard frequency (14.3 MHz) submultiple (1.19 MHz) divided (in Timer Channel 0) by 65536.

Starting with F=14.31818 MHz (315÷;22 MHz) as on ISA bus 30B; F÷;12 = 1.193182 MHz; F÷;3 = 4.77 MHz, the original PC clock frequency, and F÷;4 = 3.579545 MHz, the US NTSC TV "color sub-carrier" frequency, used by CGA).

The longword changes slightly more than 65536 times per hour; DOS allows $1800B0 (1573040) ticks per standard day, or $10007.5 (65543.3) ticks per nominal hour, and the nominal frequency is 18.206481...Hz (period 54.925495... ms). Access it atomically. Remember that the true frequency will differ from the nominal, and can drift. The 18.2 Hz frequency can be changed by reprogramming the divider, but it's often not a good idea to do so.

Actually, 315000000÷;22÷;12÷;65536 Hz differs by about 1.55 ppm from 1573040÷;86400 Hz; the DOS day would be about 3 ticks short if the master frequency were an exact sub-multiple of 315 MHz.

When the clock is at $1800AF, the next updating interrupt sets it to zero and sets or increments byte Seg0040:$0070, the Midnight Flag. There is a problem if a suitable system call, clearing the Flag and updating the concealed internal DayCount, does not occur at least once per day (in most DOS versions); the software calendar loses days. If the clock is artificially set past $1800AF, it continues to run up. Times like 26:43:22 may be given; but eventually (? <~1 day ?) the time routine can crash DOS.

Int 1A/00 reads and clears the Flag, without incrementing the internal DayCount; it should therefore not be used except by DOS itself.

Now $1800B0=1573040 clock ticks/day and 86400=$15180 seconds/day have a highest common factor of $50=80, so $1800B0÷;$50 = $4CCE=19663 clock ticks correspond exactly to 86400÷;80 = $438=1080 seconds = 18 minutes. And the lowest common multiple ($1800B0÷;$50)×86400 = $1800B0×(86400÷;80) = 1,698,883,200=$65,42E,680 < +2,147,483,648=$80,000,000 and so will fit within a signed 32-bit quantity, a longint, while being thus calculated.

So, to convert the tick count to seconds, the following is exact and passes range-checking (Sec, at least, is 32-bit; Min, at least, is 16-bit) :-

     Sec := (Ticks*(86400 div 80)) div ($1800B0 div 80) ;
     Min := Sec div 60 ; Sec := Sec mod 60 ;
     Hrs := Min div 60 ; Min := Min mod 60 ;

The reverse, untested, is :-

     Ticks := ( ((Hrs*60 + Min)*60 + Sec) * ($1800B0 div 80) )
                                          div (86400 div 80) ;

The software clock is loaded from the permanently-running RTC (Real-Time Clock; also local time) chip at boot time in a PC-AT or better (possibly inaccurately after 1999); the software calendar is loaded from the same source, with possible problems after 1999.

See also :- Time and Date on Your Personal Computer (Mitre).

Windows

Windows offers more time functions, including GMT/UTC; see Delphi Programming. Windows NT and up appear to be significantly different from Windows up to 98.

Reading the DOS Timer

Unless care is taken, TP/BP will access the longint DOS timer non-atomically, as two words. Therefore there is a chance (about once an hour) that it may be updated with an inter-word carry in between the two parts of a TP/BP access, which would lead to a one-hour error. Program ATOM046C.PAS demonstrates this (at an accelerated rate), when compiled by BP7 (but not TP5 ?!?).

32-bit Delphi does not have this problem; it does 32-bit accesses atomically.

One way to access the DOS timer atomically is with Int1A/00 - but it was intended for OS use only, and does not itself update the DayCount when required; therefore the state of the Midnight Flag in Seg0040:$0070 will be lost. :-

  Regs.AH := 0 ; Intr($1A, Regs) ;
  with Regs do Count := longint(CX) shl 16 + DX ;

  function Tix : longint ; assembler ;
  asm  mov ah,0 ; int $1a {Get $40:$6c; but lose Midnight Flag} ;
    {$IFDEF PASCAL} mov ax,dx ; mov dx,cx ; {$ENDIF}
    {$IFDEF DELPHI} mov ax,cx ; shl eax,16 ; mov ax,dx ; {$ENDIF}
    end {Tix} ;

In Pascal, pseudo-atomic access can be done by (derived from postings by Franz Glaser and Osmo Ronkanen) :-

  repeat T1 := MemL[Seg0040:$006C] until T1 = MemL[Seg0040:$006C] ;

  function Tix : longint ; assembler ;
  asm  mov ax,$40 ; mov es,ax ;
    @1: mov ax,[es:$6c] ; mov dx,[es:$6e] ; cmp ax,[es:$6c] ; jne @1
      end {Tix} ;

which ensure that the reading was not broken by a change.

One might also force 32-bit access; but that requires better than the x86 instruction set, limiting its usefulness :-

  function Tix : longint ; assembler ;
  asm  mov ax,$40 ; mov es,ax ;
    db $66 ; mov ax,[es:$6c] ; ... ?
      end {Tix} ;

See the current LONGCALC, at "Tix" and "Mls", for my present favoured Pascal and Delphi code; also NOWMINUS, which has

  GetDate(Y, M, D, DoW) ;
  repeat X := DoW ;
    GetTime(Hr, Mi, Sc, Cs) ; GetDate(Y, M, D, Dow) until DoW=X ;

The PC RTC

The PC RTC (Real-Time Clock) runs permanently, driven from a watch-type crystal and a battery. Its registers share addressing with the CMOS RAM. It is used, at boot, to set the DOS clock; it was introduced with the IBM PC-AT. When the DOS clock time or date are written to, the RTC time and date are written.

Borland's Pascal & Delphi provide no specific routines for direct access to it; it can be accessed through Int 1A, or, very carefully, through I/O Ports $70/$71.

As it has a different crystal, it can be expected to drift with respect to the DOS clock; or, rather, vice versa, as it should keep better time.

DOS, for obvious reasons, uses the DOS clock; a program reading the RTC clock will get a different estimate of the time, and this may matter. My program INT_TEST will show this. User programs should not normally access the RTC.

Only DOS should call Int 1A/00; that clears the Midnight Rollover Flag, which is therefore lost to DOS if a user calls 1A/00.

The original RTC chip provided a Day-of-Week register, incremented (1-7) each midnight. It seems that DOS disregards this, and PCs do not set it.

Get A Time or Date

Again, remember that DOS stores and serves only a local date and time - conventionally, but not inevitably, the civil time of the user's location. Windows (Win3?) also knows about the offset from GMT, and when, in Spring and Autumn, it might change.

GetFTime

Note that, in order to fit into a 16-bit field, MS-DOS-compatible file times always have the seconds even. The file date storage format, YYYYYYYMMMMDDDDD-hhhhhmmmmmmbbbbb, (though not DIR) handles 1980..2107 (Year=1980+YYYYYYY), but, if handling Date-&-Time as a 32-bit longint, be aware that it goes negative in 2044 - xor both sides with $80000000 before comparing for order. This format is given by Pascal PackTime (and Delphi fileage); though formally Borland's PackTime's format may be undefined (but obvious). The longint after xor, considered as a longword, increases monotonically but not linearly with the date/time it represents.

GetTime

GetTime (in the Dos library unit) returns the current MS-DOS time. As this is obtained by using Int 21/2C from the MS-DOS 18.2 Hz clock at longint Seg0040:$6C, it does not have a true resolution of 10 ms, but 55 ms.

procedure GetTime(var Hour, Minute, Second, Sec100 : Word) ;

Sec100 (hundredths of seconds; centiseconds) can have any value in 0..99; but it goes up in steps of 5 and 6 at a time (100÷;18.2, rounded). SetTime will also be rounded; it sets the RTC...

I have seen it suggested that in WinNT or Win2000 the steps are 1 s; and that each DOS box may have its own independent date and time.

Beware midnight rollover if getting both Time and Date.

Delphi has Time, Date, and Now; I don't know whether Now is rollover-safe.

GetDate

GetDate returns results derived from a secret DayCount variable maintained by DOS; see in my Year 2000 Programming. The variable is a 16-bit count from 1980-01-01 = 0.

Double-Setting by MS-DOS

SetDate and SetTime each set both Date and Time in the RTC, the other value being obtained from MS-DOS; this is a property of the MS-DOS CLOCK$ driver, which is used by Int21/2B and Int21/2D. See Year 2000 Programming.

Storage by Programs

Programs can store time and/or date information, internally or in files, in a multitude of formats; few store the time as in $40:$6C, and fewer the date as a count from 1980. Normally, this will be local time; beware of changes.

Date Arithmetic

See the head of this page for links to my own related programs, and the site home page for futher date links on this site; and see Date/Time Scales and Week. Remember the effects of Summer Time and Leap Seconds.

It will often be useful to use Day Count functions, based on the work of Zeller.

The Day of the First Thursday of a month is given by

  D := DayOfWeek(Y, 1, 3) { 1..7 } ; FT := 7 - D mod 7 ;

Subtraction

If one is asked to calculate date differences as days, months and years then considerable care is needed, because months are of different lengths.

Between 1999-01-31 & 1999-03-01, 1999-02-07 & 1999-03-08, and 1999-08-01 & 1999-08-30, there are in each of the three cases 28 clear days (the first of each is Sunday, the second Monday); but the month numbers differ by 2, 1, and 0 respectively.

One must find out EXACTLY what the requirements are, ensure that they are both unambiguous and understood by all concerned (it may be necessary to agree changes formally), and implement them with care.

Simple approaches using routines not really designed for the purpose will for an interval of 30 days give less than one month, but will for two consecutive intervals totalling 60 days give two months and probably another day; this is because January is long and February is short.

Delphi has TDateTime (a double; daycount from zero at 1899-12-30, plus fraction), EncodeDate, DecodeDate, etc.; take care to Round or Trunc as needed. N.B. Before 1899-12-30, the time within a day is reversed - see my delpdate.pas, delpdate.txt and Delphi Programming.

Comparison

Dates may be compared by using a pseudo-DayCount, (((Year-x)*y + Month)*z + Day) for any y≥12, z≥31. DOS uses x=1980, y=16, z=32, in effect, for file datestamps; and similarly for the time part.

Validation

To check whether a date Y M D is a valid date, the length of a month can be obtained with a lookup in [31, 28, 31, ...] ; if that test fails, then if the day is 29 one tests for a leap year. With short-circuit evaluation, untested,

  OK := (M>0) and (M<13) and (D>0) and
        ( (D<=Arr[M]) or ( (D=29) and Leap(Y) ) )

There is no need to check for a Leap Year unless the day is the 29th.

If the date is supplied as a numeric string such as 1564-04-23, it can be useful to check as far as possible with a RegExp; RegExp units are available, at least for Delphi.

Make - a warning

The Make process is file-time-dependent. This could cause problems if you are working :-

Compilation Datestamp

If one has, or can arrange, a process which must necessarily run on the computer before compilation but on the same day, one can make it write an include file or unit which contains :-

        const Today = '<todays date>' ;

One such process may be booting the machine; otherwise, one could use a scheduler. In fact, if the process is run at boot and also scheduled to run at midnight, then the problem is solved. A programmer can write his own; it need only be a console application. See todaycon.bat and batprogs.htm#MiscE.

If using a makefile to compile, that could first generate the date file.

Year 2000

I know of no Y2k problem in the Borland Delphi and Pascal products; but anything using C-derived libraries may have a problem in 2038. Beware 2044.

Cabot Software of Bristol, U.K., has launched The Pascal 2000 Resource Centre.

My Year 2000 pages : General, Programming, Testing.

Programs

See my Delphi (D3 DCC32 -cc) program tz-check.pas, which includes demonstration calls to other Windows time routines.

See my Pascal/Delphi (D3 DCC32 -cc) program rdtsc.pas for RDTSC.

Summer Time

Some of my DOS programs incorporate the Summer Time Rules of one or more of the UK, EU, USA. The UK/EU rules are those for years from about 1996; the US rules for 1987-2006.

The USA altered its DST rules from 2007.

I don't expect to change the US DST code soon, because of computer hardware change; if you need it, ask.

Primoz Gabrijelcic : Time Is The Simplest Thing - The Delphi Magazine, Jan 2001.

Other

Increasing or decreasing machine speed can reveal general time-dependent errors. For instance, in TP/BP the RandSeed generated by Randomize changes only at 55 ms intervals.

For access to the extra file date/time information in Win95+, see in Andreas Killer's LFN110.ZIP and LFNEN101.zip (AK's site not found, 2006-01-31) :-
65973 Jun 2 2000 ftp://garbo.uwasa.fi/pc/turbopa7/lfn110.zip
lfn110.zip TP6+ Support of long filenames under Win95/98, A.Killer

To handle Win 98/NT filestamps, 64-bit counts of 100 ns units from Gregorian AD 1601-01-01 00:00:00 GMT, use type comp; to get Days, use   D := Int(Count/8.64E11) ; .

2003-08-04 : I've seen a suggestion that 16-bit processes in WinNT/2000 may each have an individual DOS time ($40:$6C) but not an individual date; thus clock rollover and calendar change may get decoupled, with obvious danger.

Home Page
Mail: no HTML
© Dr J R Stockton, near London, UK.
All Rights Reserved.
These pages are tested mainly with Firefox and W3's Tidy.
This site, , is maintained by me.
Head.