Share via


DateTime - Not As Simple As You Think

The definition of the .NET DateTime structure is simple:

The DateTime value type represents dates and times with values ranging from 12:00:00 midnight, January 1, 0001 Anno Domini (Common Era) to 11:59:59 P.M., December 31, 9999 A.D. (C.E.). Time values are measured in 100-nanosecond units called ticks, and a particular date is the number of ticks since 12:00 midnight, January 1, 1 A.D. (C.E.) in the Gregorian calendar.

According to this definition, the "tick value" of 12:00:00 midnight, January 1, 2004 A.D. should be 632,085,120,320,000,000. But when you try, .NET gives the answer 632,085,120,000,000,000. This is 320,000,000 ticks, or 32 seconds, in error. If you are calculating spacecraft trajectories, .NET is definitely not the right environment!

Short explanation: This has something to do with the UTC definition, which is the basis for all "civil time".

Longer explanation: The second is no longer defined from the Earth's rotation, as this is not accurate enough (also, the rotation period of the Earth slowly increases, by approx. 1.4 milliseconds each century). Instead, the second is now defined by atomic vibrations, as measured by atomic clocks. But this means that "atomic time" and "solar time" slowly drift apart. Currently (april 2004), the "solar time" is 32 seconds behind the "atomic time" (as measured since January 1, 1958, which is the baseline of the current definition).

Please note that this is unrelated to the leap year problem. Leap years compensate for the fact that the year (that is, one orbit around the Sun) is not exactly 365 days. Leap seconds compensate for the fact that the day (that is, the Earth's self-rotation) is not exactly 24 hours.

Instead of adjusting the second (which has all kinds of unpleasant consequences, as the second is a fundamental SI unit), a "leap second" is introduced at irregular intervals, in order to keep the UTC time within 1 second of solar time. Leap seconds can be introduced on June 30th or December 31th on any given year, if needed. It is not possible to determine when future "leap seconds" will occur with any accuracy, as they are declared by the "Bureau International des Poids et Mesures" (BIPM) six months ahead of the next possible date.

The last "leap second" happened on December 31, 1998. A correct digital watch (showing UTC time) would look like this around midnight:

  • 12.31.1998 23:59:58
  • 12.31.1998 23:59:59
  • 12.31.1998 23:59:60 <--- LEAP SECOND!
  • 01.01.1999 00:00:00
  • 01.01.1999 00:00:01

Now for some definitions:
TAI (Temps Atomique International): "Atomic" time, without leap second adjustments
UTC (Universal Time Coordinated): "Atomic" time with leap second adjustments, to keep UTC time within 1 second of "solar" time.

Currently (April 2004), TAI minus UTC equals 32 seconds.

Now for the clue: The DateTime structure should be understood as TAI time when performing time calculations. There is no provision for leap second calculations, and the valid UTC time "12.31.1998 23:59:60" will throw an exception when trying to convert to a DateTime value. However, Microsoft does not say so! In reality, Microsoft does not support the UTC definition at all. Still, they have the gall to implement properties like UtcNow and methods like ToUniversalTime(). In other words, they have really messed things up.

On the other hand, as future leap seconds cannot be predicted with accuracy, perhaps things are better the way they are. Otherwise, Microsoft would have to roll out a .NET Framework patch each time the next leap second is announced.

When computing accurate UTC time intervals, the application has to consult the UTC "leap second table" and perform the necessary adjustments. Here is the complete, historical list of UTC leap seconds (as of April, 2004):

  • 06.30.1972 23:59:60
  • 12.31.1972 23:59:60
  • 12.31.1973 23:59:60
  • 12.31.1974 23:59:60
  • 12.31.1975 23:59:60
  • 12.31.1976 23:59:60
  • 12.31.1977 23:59:60
  • 12.31.1978 23:59:60
  • 12.31.1979 23:59:60
  • 06.30.1981 23:59:60
  • 06.30.1982 23:59:60
  • 06.30.1983 23:59:60
  • 06.30.1985 23:59:60
  • 12.31.1987 23:59:60
  • 12.31.1989 23:59:60
  • 12.31.1990 23:59:60
  • 06.30.1992 23:59:60
  • 06.30.1993 23:59:60
  • 06.30.1994 23:59:60
  • 12.31.1995 23:59:60
  • 06.30.1997 23:59:60
  • 12.31.1998 23:59:60

Prior to 1972, leap seconds were not used. Instead, formulas are used to calculate the TAI-UTC time difference. On January 1, 1972 the accumulated TAI-UTC time difference (since January 1, 1958) were 10 seconds. Each entry in the UTC leap second table adds 1 second to the TAI-UTC time difference.

The GPS time system is related to TAI, but use a different baseline date (January 6, 1980). Therefore, the TAI-GPS time difference is 19 seconds. Currently (April 2004), the GPS-UTC time difference is therefore 13 seconds (19 + 13 = 32, which is the current TAI-UTC time difference).

Some references:

  1. https://hpiers.obspm.fr/eop-pc/earthor/utc/UTC.html
  2. https://tycho.usno.navy.mil/leapsec.html
  3. https://www.boulder.nist.gov/timefreq/pubs/bulletin/leapsecond.htm
  4. https://www.npl.co.uk/time/leap_second.html
  5. https://www.astronomy.com/Content/Dynamic/Articles/000/000/001/617efgfc.asp
  6. https://dsc.discovery.com/news/briefs/20031229/atomicclock.html
  7. https://www.ucolick.org/~sla/leapsecs/dutc.html