logo To Foot
© J R Stockton, ≥ 2007-09-01

Year 2000 Programming.

No-Frame * Framed Index * Frame This
Links within this site :-

2007-01-21 : Some dead links are now underlined.

Compiling and Running

When writing a program to perform tests by altering the clock, be very careful to ensure that the program you're running is the one that you think you're running. A "Make" process can give undesired results if system time does not increase monotonically.

Unexpected Setting of Date and/or Time in a PC

I have a test program, int_test.pas, in TurboPascal, which offers all the date/time commands that I can manage so far. It confirms what is below, including the unexpected effects. In what follows, "word" → unsigned 16  bits, "longint" → signed 32  bits.

The BIOS Functions

The ROM BIOS provides functions Int 1A/00 to 1A/07 to handle the clock counter, to read and write the Real-Time Clock (RTC) time and date, and to handle the RTC Alarm.

The counter functions Int 1A/00, /01 imply knowledge of a MS-DOS structure in RAM? The RTC functions 1A/02 ... appear to be independent of MS-DOS, and to do only what is expected (I've not looked into Alarm, though).

The MS-DOS Functions

MS-DOS provides functions Int 21/2A to 21/2D to handle Date and Time; they correspond to the Borland/Turbo-Pascal procedures GetDate, SetDate, GetTime, and SetTime, which do no more than call the MS-DOS functions.

Date is kept in RAM as a word of days since 1980-01-01, within and accessed by the CLOCK$ driver. It seems difficult to locate this word in a general manner. Time is kept in RAM as ticks since midnight at longint Seg0040:$6C, incremented by the MS-DOS 18.2 Hz interrupt, and so it does not have a true resolution of 10 ms, but 55 ms (1.0/18.2 Hz). When it reaches $1800B0 (apparently $1800B1 in some machines), it is zeroed and the midnight flag byte at Seg0040:$70 is set (usually to 1; sometimes incremented; and in my PPC640 set to $FF).

GetDate (Int 21/2A), GetTime (Int 21/2C)

GetDate and GetTime just return the current MS-DOS time and date, respectively.

SetDate (Int 21/2B), SetTime (Int 21/2D)

SetDate and SetTime are specified as setting the current MS-DOS time and date, respectively.

SetTime (Int21/2D) and SetDate (Int 21/2B) are specified as setting both the MS-DOS RAM and the RTC itself; and likewise for the MS-DOS commands TIME, DATE.

Double-Setting by MS-DOS

1998-12-08 ff: I believe that SetDate may also write the DOS time to the RTC (and SetTime the date : MS-DOS does it, but not ROM BIOS. SetDate must of course write the date to the RTC; SetTime, the time).

1998-12-20 ff: Confirmed; by my test program, now int_test.pas, which offers all the date/time commands I can manage; and by Pedt Scragg's inspections - he wrote :-

I finally got round to tracing through the Int21/2B,2D code this morning following you raising this in clpb.

The DOS code does indeed inspect RAM to set the other Date/Time depending which service was called. I've looked through MSDOS 5, 6, 6.20 and 6.22 plus PCDOS 6.30 and OpenDOS 7.00, 7.01, 7.02 and all exhibit the same behaviour. I would think that it will be all MSDOS/PCDOS (and variants) from 3.30 upwards.

Tracing through Int1A/05,03 shows that they only set the requested part using the parameters given - RAM is not inspected.

It seems to me that the double setting could cause confusion or improper deductions in Y2k testing. Double setting is mildly supported in Kris Heidenstrom's valuable PC Timing FAQ pctim003.zip. I found no reference to it in Ralf Brown's List INTER59 (see PC Links Reference); so informed him, and it is now in INTER60 ff..

On Reading DOS Source

1999-01-03 : On reading a non-MS version of DOS, I see code in the CLOCK$ driver for converting DayCount to/from Y/M/D which operates by iterating through the years, and then through the months; this will take a date-dependent time to execute. The additional contribution on entering 2000 is present but negligible. There is a comment bug, I think; for 2400 read 2100.

Unexpected Getting/Setting by Windows Start-Up

It appears that, on startup, Windows reads the date and time from the RTC.

I have read this; and I have briefly tested with WfWg 3.11 on MS-DOS 6.20, and find that Windows takes the time from the RTC and sets it back into MS-DOS.

This is a source of possible confusion to testers. I wonder what happens if there is no working RTC?

Unexpected Setting by Windows Control Panel

1998-12-28 ff: From Risks Digest 20.13/15 (djweber@sandstorm.net), checked by me in Windows 98 and Windows for Workgroups 3.11 - As the date & time, or parts thereof, are altered in Date/Time in Control Panel, the MS-DOS and RTC settings track these changes, even before OK (Cancel does put them back). This is dangerous, as another simultaneous process may pick up an incorrect value. My int_test, in a DOS box, will show what happens.

Phil E. reports similar for Windows 3.1.

In a WfWg3.11 MS-DOS box, writing to $40:$6C appears ineffectual, but Int1A/01 has effect - not surprising really. Int1A/01 does not affect the RTC.

Win XP is said, fairly convincingly, to be better.

A PC has Two Clocks

IMHO, we should resist the "three clocks" story about the PC (here considering only "timekeeping" clocks, not the 'clock' oscillators that drive the CPU and other parts).

The basic PC has only two clock/calendars - the RTC hardware, and the 18.2 Hz- driven OS software. One of these, the RTC, is well-known to have two faces; direct reading, and reading via BIOS Int 1A. But the other, the OS, is also multi-faced; one may read it directly, or by calling the CLOCK$ driver, or by using an explicit Int 21, or by a HLL call, or by an OS command (DATE, TIME).

The two clock/calendars run at different approximations to the correct rate; the OS one is loaded from the RTC at boot; the OS sometimes sets the RTC.

For each of these two clock/calendars, all faces advance at the same rate, but there may be different indications or misinterpretations.

I don't know nearly enough about the mechanism of Windows GetTickCount, which gives a count of milliseconds from boot, lasting 49.71 days (49 days 17:02:47.296). If treated as signed, it rolls over after 24.85 days.

Year Programming

This will not include everything, obvious or subtle.

For various date routines in Pascal, see dateprox, tested by mjd_date, in programs/. For batch file date conversions, see bat_date, nowminus, envicalc therein, and MS-DOS Batch Files, etc.

For an article on booby-traps in various computer languages, see Jocelyn Amon.

Whenever reading time and date, always allow for the possibility of midnight happening. One way is to repeat Date, Time, Date until both Dates have the same Day.

The Year 2000

Extension

When extending the date (e.g. de-windowing) from YYMMDD to YYYYMMDD, be sure to consider the possibility of flag, blank or invalid input dates.

Suffixes

Watch out for the possibility of arrays declared with the intention of spanning 100 years, but where the implicit or explicit lower bound accommodates 01 but not 00.

C/C++/UNIX

In C libraries, the tm_year field of struct tm contains (Year-1900). Code that uses tm_year wrongly, such as by using it directly as a 2-digit year, or setting it from a 4-digit year with wrong code like tm.tm_year=yyyy%100 or printing it as a 4-digit year with wrong code like printf("19%d", tm.tm_year) will fail.

Beware also, when obliged to output a two-digit year, of code using %d format instead of %2d : the result will be numerically correct, but may disturb layout, after 1999, by being shorter than before.

Chris Croughton wrote (cf. critdate.htm. Note 7) :-

For reference, the C version would be like :-

        cc = (tm->tm_year > 99) ? 20 : 19;
        yy = tm->tm_year % 100;
        printf("year = %d%d", cc, yy);

I would also suggest that C libraries are checked for their implementation of strftime, the ANSI C function which converts the binary time structure into a user-specified date and time string according to a format specification.

Loss of leading zero may be significant more internally too, e.g. in file name generation, string sorting, ...

How about asctime(), ctime()?

Cobol, etc.

Java/Javascript

In older JavaScript, getYear() returned the year minus 1900, YY only. Later ones, for years after 1999, return the year, YYYY. Before 1900 is worse; expect either of YYYY or YYYY-1900.

"Method getYear can return any one of these three sequences for years: 97,98,99,00,01 or 97,98,99,2000,2001 or 97,98,99,100,101".

Consider

if (Year<1900) then Year += 1900 ;

and maybe

if (Year<2000) then Year += 100 ;

as a partial solution; remember that results depend on the displaying software.

Use of GetFullYear avoids the problem; I think that

Year = (Year<1000?1900+Year:Year)

will fix it. If the year can be assumed to be in 2000..2099,

Year = 2000 + Year%100

should do.

See also at irt.org.

For JavaScript Date and Time routines, see via my JavaScript Date and Time Introduction.

Embedded Systems

The Open Group writes (1.1) that many Cobol programmers have used Year<20 values in a YY date field for control codes - implications are obvious. Any real-time system ought to be designed to raise an exception if operating before the design date, and if possible before the build date.

Representations of 2000

100 / 1910 / 3900 / 4000 / 9100 / 19100 / 20100 / 192000

Format %2d can give 100, for the year after 99.

If the string "19" is prefixed to the number tm_year in %2d (or other) format, the result is 19100; this, if chopped to four characters, gives 1910 (or 9100). With Java as above, 192000 is possible by prepending "19", and 3900 by adding 1900.

4000 has been reported.

On Web pages, such result from a combination of unexpected browser dependence and insufficiently general source coding. Testing with a single viewer is not sufficient.

An amusing example : PB noted in news:d.i.s on 2000-06-27 that certain FTP responses were then of the forms 191000627011030 & 00/62/1910 70:11:03 GMT.

A0

If dates run ... 31/12/99, 01/01/A0, ... , it seems likely that the year byte is being displayed as two hexadecimal nibbles, and that the decade nibble-nine has been incremented to nibble-ten. Said to have been seen. Discussion

Other Dates

Sun, xxxx-04-01

2001, ff : MS VC++ RTL bug fires; Windows applications start US DST a week late if the proper date is Sunday April 1st. See via Critical and Significant Dates.

1969/1970

The UNIX base date is Thursday 1970-01-01 00:00:00 GMT; note that, converted to local time, New Worlders will get 1969-12-31 (or 12/31/69). It is the origin of a count of seconds, ignoring Leap Seconds.

2001

For the first time in a century, last year is a "00" year.

For the first time since 1931, when using the ##/##/## date format, one cannot recognise the year by its large value. Use ISO 8601, YYYY-MM-DD.

On 2001-09-09 at 01:46:40 GMT is 1000000000 seconds UNIX time_t; the count will no longer fit into 9 decimal digits. REXX is said to default to 9.

See also The Year 2001, in Y2k Part 3.

2036 / NTP

Network Time Protocol (NTP; RFC2030 for SNTP, Simple NTP) uses a 32-bit seconds count from 1900-01-01 (and 32-bit fractional); I am not clear as to whether it includes the fictitious 1900 02 29. The MSB would have set on 1968-01-(19/20) at 03:14:08; rollover will occur on 2036-02-(06/07) at 06:28:16. OTOH, I've read in deepsky, quoting RFC2030, that NTP omits 2000-02-29 ("Note that when calculating the correspondence, 2000 is not a leap year."), which will make rollover a day later; but I'm also told the RFC is in error. Unless RFC2030 means 1900! NTP ignores Leap Seconds.

A value of 64-bit 0, which occurs for ~200ps every 136 years, is deemed an invalid-data marker.

I hear that the SNTP RFC has a proposed method for extension past 2036.

Jan 2002 : I'm told that Day Zero is truly 1900-01-01 and 2000-01-01 is Day 36524. Those imply that 1900.02.29 was (correctly) omitted, the MSB was set 1968-01-20, the rollover should be 2036-02-07.

2038

UNIX/C time_t is a seconds count from 1970-01-01 00:00:00 GMT, not counting Leap Seconds. In 32-bit UNIX, its sign-change on 19 Jan 2038 (2038-01-19 03:14:08 GMT) will, it is said, be fixed before then as everyone will have changed to 64-bit. Ha!

"At least until then, use 'time_t' for the type of any variable holding such a date, and assume nothing about it."

"Programs that use the time_t typedef correctly will port without trouble to longer data types as soon as the OS and corresponding compiler time_t definitions are changed."

References include :-

Some DOS (etc.?) programs use translated UNIX libraries, and show the Y2038 effect; at least one free ZIP package does.

Consider the effect of Time Zone setting; in the Americas, the effect will occur during the previous local day.

2044

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). From the start of 2044, the MSB of the DOS File Date is set.

This format is given by Borland Pascal's PackTime (formally PackTime's format may be undefined (but obvious)) and by Delphi's fileage, which returns integer in D3; Borland Pascal has no unsigned 32-bit type. When handling Date-+-Time as signed 32-bit, or Date as signed 16-bit, be aware that they appear negative for 2044 onwards.

If the Date is handled as 16-bit signed, or the Date+Time as 32-bit signed, then simple magnitude comparisons will err across 2043-2044. To correct, just flip each MSB by an XOR of each side with $8000 or $80000000 before comparing for order. After the XOR operation, the longword, considered as a longint, increases monotonically but not linearly with the date/time it represents.

2070

This is 100 years from the UNIX time_t zero; problems with century windowing are possible. Note that time_t=0 was 1969-12-31 local in the Western Hemisphere.

General

The YMD order for dates, with YYYY and leading zeroes in M and D, is the safest and most generally useful : is fixed-width, sorts correctly. See standard ISO 8601.

If searching a US program for year code, look for *100.0001 or *10000.01 :-

John Barron wrote: ... a standard technique which has been used in the past is to multiply a six-digit numeric date by 100.0001 or 10000.01 and place the output in a six-digit numeric truncating overflow/underflow to convert between YYMMDD and MMDDYY format.

Make / Link Edit

AFAIR, LINK in DOS should not be date-sensitive, but should be date-ignorant. A program, command, or option called MAKE is used to do the date-decisions required to see whether any module should be regenerated and then to give the necessary commands to LINK, which combines object modules into an executable).

Obviously, the MAKE function in any compile/link system, and any other process which selects the "latest" (by date/time) components for use, is a real Y2k (and/or 2038) risk; but one using the "last" (by version number) components ought to be immune.

The date-dependent parts of such a code are quite likely to have been composed many years ago, even if the program itself is a recent release adapted to handling recent code formats; IMHO it is a prime case for Y2k-checking. An almost-passably-run code-shop will routinely check that it can still construct all its currently-needed programs from editable sources; a well-run shop will check also that it will retain that capability in the foreseeable future.

Failure in advanced Y2k testing due to Link Edit date comparison errors, has been reported.

Load

It was reported that a system which loaded the latest code according to a two-digit year failed early in 1995, the calendar having got mis-set ahead five years.

Reading Date and Time

Midnight Rollover

On 13 May 1999 in news:uk.tech.y2k, Chris Torek <torek@elf.bsdi.com> wrote :-

Incidentally, if software reads the clock registers improperly, even a buffered design will not cure all possible problems. At best it (a) reduces the window during which "reading the clock" will produce bogus results and (b) reduces the amount of error whenever you *do* get bogus results. The reason is that "reading the clock" is not an atomic operation. Even if "updating the clock" is atomic (i.e., all the bytes in the RTC appear to change simultaneously, to any program attempting to observe those bytes), the effect of, e.g. :-

        hrs := read_rtc(HOURS) ;
        min := read_rtc(MINUTES) ;
        sec := read_rtc(SECONDS) ;

*may* be to read "04:59:00" rather than "04:59:59", if the clock flashes from "04:59:59" to "05:00:00" between the "read minutes" step and the "read seconds" step.

A solution is to read SECONDS both first and last, and to loop until they match. See DOS Timer for related material on $40:$6C.

Reading the Year

Y2k errors on output may lead to the year after 1999 being written as 19100. Software reading in years can allow for this :-

Given a digit string that can be assumed to represent the number of a year after Year 999 (note : advice also designed for Jews & Moslems), the following might be useful :-

If three or fewer digits, take as a straight number and add a multiple of 100 to window into a suitable range.

If more than three, remove the first two, take them as "century" so multiply by 100, then add the value of the remaining digits.

If the result is unreasonable, give warning, ask the user, but suggest the nearest "century" ahead, on the _assumption_ that so grievous an error would rapidly be corrected.

PKZIP

I have read that PKZIP (TM)? (R)?, the shareware from PKWARE, has Y2k issues in the popular version 2.04g, fixed in version 2.50. The -t/T option will now accept 2- or 4- digit years in the date specifier. Seek PK250DOS.EXE. I don't know about 2038.

Programming References

Pending Insertion?

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