How to recognize different types of sentinel timestamps from quite a long way away


Some time ago, I discussed several timestamp formats you might run into. Today we'll take a logical step from that information and develop a list of special values you might encounter. Note that if you apply time zone adjustments, the actual timestamp may shift by up to a day.

Date Interpretation
January 1, 0001 The value 0 as a CLR System.DateTime.
January 1, 1601 The value 0 as a Win32 FILETIME.
December 29/30, 1899 The value -1 or 0 as an OLE automation date.
December 13, 1901 The value 0x80000000 as a time_t.
December 31, 1969
January 1, 1970
The value -1 or 0 as a time_t.
January 1, 1980 The beginning of the DOS date/time era. (Unlikely to be encountered since 0 is not a valid DOS date/time value.)
January 19, 2038 The value 0x7FFFFFFF as a time_t.
February 7, 2106 The value 0xFFFFFFFF as a time_t.
September 14, 30828 The value 0x7FFFFFFF`FFFFFFFF as a FILETIME.

All of these special values have one thing in common: If you see them, it's probably a bug. Typically they will arise when somebody fails to do proper error checking and ends up treating an error code as if it were a valid return value. (The special values 0, -1, and 0xFFFFFFFF are often used as error codes.)

Comments (21)
  1. BTW, does anybody know why time_t is signed, yet negative values are considered invalid by functions such as localtime() ?

  2. Carlos says:

    Why do FILETIMEs start in 1601? Leap years follow a 400 year cycle, with 1600, 2000, etc. being the "most significant" special case. If you base your dates on one of these dates plus one (i.e. 1601, 2001, etc.) then the calculation to convert the numeric value to a year is very slightly simplified (you save yourself a single subtraction.) And 1601 was the most recent such date when Windows NT was developed.

    I don’t actually know if this is the real reason 1601 was chosen, but it seems plausible.

  3. Miles Archer says:

    The world is going to come to an end on Jan 19, 2038.

  4. I believe that the 1601 is related to the gregorian calendar, or something like that.

    And the world ending on Jan 19, 2038 only happens on Unix systems. NT’s good until 30,000 or so.

    Yet another reason to switch – dates don’t run out as quickly :)

  5. Mr. Larch says:

    I (jag) appreciate (uppskattar) all references (alla hänsyftningar) to Monty Python (till Monty Python)! Utmärkt!

  6. Ring Zero says:

    Seeing a zero in a CLR DateTime is not that unusual, as that value is often used in place of null. It’s the value returned by DateTime.MinValue. In my opinion it was a design mistake to make DateTime a value type instead of a ref type.

  7. Brian Kemp says:

    Most likely so that time math is much more easily handled? Totally guessing, though.

  8. David Candy says:

    Untill 1994 the ONLY computer date I ever saw was 1 Jan 1980. Maybe I got to see Jan 2 sometimes, but one needed to reboot between programs.

  9. Bryan says:

    Er… Why does 0x80000000 as a time_t mean 1901, while 0xFFFFFFFF as a time_t mean 2106? Shouldn’t they both be either signed or unsigned? It looks like 0xFFFFFFFF was interpreted as unsigned, while 0x80000000 was interpreted as signed.

    (Both dates should be before midnight, Jan. 1, 1970, since they’re both negative. 0xFFFFFFFF should be one second before it, while 0x80000000 should probably be exactly what was posted. Alternately, interpreting them both as unsigned would mean that 0x80000000 would be just after Jan. 19, 2038, and 0xFFFFFFFF would probably be what was posted.)

    Not that I think many Unix-like systems would actually allow you to use negative time_t’s (see Serge’s comment above), but any given Unix-like system should treat them either as unsigned (and allow them), or signed (and possibly return errors). Not both.

  10. Mike Jones says:

    Er, time_t isn’t a Unix thing — its a C thing.

    I am sure most Unixes/Linux doesn’t use time_t internally … just like Windows doesn’t.

    Only signed 32-bit time_t runs out in 2038.

    Many C environments have a time64_t (which is of course 64 bits) – eg:

    http://publib.boulder.ibm.com/infocenter/pseries/index.jsp?topic=/com.ibm.aix.doc/libs/basetrf1/ctime64.htm

  11. Bryan: Yes, sometimes the value is signed and sometimes it’s unsigned. The interpretation should be clear from context.

    "Any given unix-like system should treat them either as unsigned or signed, not both."

    Right, but you the end user might have to deal multiple machines, some which use the signed interpretation and some which use the unsigned interpretation. So I included both, because the goal of the table was to show everything you might encounter. Writing "The value 0x80000000 as a time_t when interpreted on a system for which time_t is a signed type" would have been more precise but would have cluttered the table with information that really isn’t relevant to the main point: Recognizing sentinel values.

  12. Merle says:

    Ah, many thanks. I ran into a "30 Dec 1899" and the *only* guess I had come up with was that it was a Saturday. Which might make certain day-of-the-week computations easier.

  13. asdf says:

    time_t is a signed integer for historical reasons:

    – dates all the way back to when C didn’t have unsigned integers

    – unix’s time_t is signed and the year 2038 is plenty enough of time to switch over to 64 bits. Today’s programmers will be either dead or retired in that year, so it’s somebody else’s problem

    – lots of crappy programmers assume it’s signed and do subtraction on it instead of using difftime and expect it to be a negative value if the dates are backwards (even though this doesn’t handle large distances correctly)

    – functions return (clock_t)-1 on error and due to C’s (IMHO stupid) way of doing arithmetic conversions on the comparison operators, this can result in the wrong result on some platforms under certain conditions

    C trivia: time_t/clock_t are allowed to be any arithmetic type including an unsigned integer or floating point type.

  14. Norman Diamond says:

    All of these special values have one thing

    > in common: If you see them, it’s probably a

    > bug.

    All except one. If you see the special value December 31, 1969, it’s probably intentional. Still unwanted but usually intentional.

    Friday, October 28, 2005 10:39 AM by Carlos

    > Leap years follow a 400 year cycle, with

    > 1600, 2000, etc. being the "most significant"

    > special case.

    Actually only 2000 is, so far. 1600 is useful for computational purposes by pretending that it was part of the same cycle, but when Christians were counting 1600 years Pope Gregory hadn’t been invented yet.

  15. Yay, so in another 33 years we’ll have another possible Y2K! Ok guys, this time we make it right! We need to scare the crap out of everyday users about the EOW so that they pay us a lot of money for fixing it!

    We must henceforth use signed time_t for all times and dates in our software, especially any software going into nuclear reactors!

    Good luck! I’m counting on you guys!

  16. Ben Hutchings says:

    Norman: The Gregorian calendar was adopted in most Roman Catholic countries in 1582. Britain and its colonies (including what is now the US), being Protestant by then, were slow to change over, though not as slow as e.g. Russia.

  17. martynl says:

    In VC8 (Whidbey) which just RTMed (WooHoo!), we widened time_t to 64 bits by default. It is still signed.

    You can use a #define to switch back to 32 bit time.

    We also provide specifically named time functions for the 32 bit and 64 bit versions of the type.

    Martyn Lovell

    Development Lead

    Visual C++ Libraries

  18. Norman Diamond says:

    Monday, October 31, 2005 2:07 PM by Ben Hutchings

    > Norman: The Gregorian calendar was adopted

    > in most Roman Catholic countries in 1582.

    Yikes. I was 100 years off. I’d better stop relying on memory before talking about non-computer stuff that way.

    I even have enough discipline to look up which argument is which in functions like memcpy almost every time I use them (because memcpy and bcopy had them in opposite orders and if I try to remember which is which then I’ll remember wrong). So I really was out of line by not looking up centuries. I am duly chastised.

    Geez, a century. That might even be enough time for Windows Vista beta 1 checked build to finish installing itself.

  19. John says:

    The time_t typedef is a relatively recent invention, created during the the ANSI C standardization process, IIRC. The original Unix definition of time() and related functions used "long".

  20. At least we think they’re fake.

Comments are closed.