Before explaining the problem, let me briefly explain what a Timez is. The idea is simple but brilliant (thanks).
The Timez stamp
- view the stamp time with the local time where it was produced ;
- sort stamps by UTC time, thus ignoring the local time offset of its origin ;
- view the stamp time with his own local time offset ;
- view the stamp time with the local time offset of another location in the world.
The solution I came up with is to combine into a 64 bit signed integer a time expressed as the number of micro seconds relative to an epoch and the local time offset expressed as a number of minutes where the stamp was generated.
The time offset is an unsigned integer. It's the time offset plus 1024, and its range is +/- 17 hours (+/-1023 minutes). If the bits of the time offset field are all zero, the time offset value is -1024 and the Timez value is invalid or undefined.
Up to here, it's all simple and straightforward. So I started implementing the Timez data type in C.
Time in Timez is without leap seconds correction
But to my big surprise, there is actually no way to get the time in POSIX (all Unix flavors) or Windows without leap second corrections a.k.a. TAI or GPS time. The system time you get with the functions time(), gettimeofday() or clock_gettime() is the number of seconds elapsed since the 1970-01-01T00:00:00 UTC minus the leap seconds.
Investigating this further, it appears that the time handling problem on computers is actually a rabbit hole. I learned a lot in the process, but also wasted a significant amount of time. It is frustrating because it clearly result from standard definition and support lagging.
Happily, things are slowly changing, but only very slowly. Since Linux Kernel 3.10 there is a new clock id that can be used by clock_gettime() named CLOCK_TAI. But on my computer it currently still returns the same time as CLOCK_REALTIME. Apparently you need a version of NTP higher than 4.2.6 to get the CLOCK_TAI clock adjusted.
What is then still missing is a conversion between leap second corrected time and uncorrected time. I plan to provide such function so that Timez can be used with operating systems that don't provide TAI or GPS time. I'll unfortunately have to hard code the table of leap seconds because there is no easy access to a dynamically updated table.
If you want to learn more about leap seconds I suggest to read this section in Wikipedia. An interesting part is about the proposal to drop the leap second correction. I also encourage you look see this short video on the Time & Time zone problem from the Computerphile.
The epoch of Timez
With 53 bits encoding the number of microseconds, we are short. We can only cover +/- 142 years around the epoch. By picking the same epoch as CLOCK_TAI we would only have ~100 years left until the Timez time counter would wrap.
I then identified three options.
- Epoch = 1970-01-01T00:00:00 UTC + 2^52 : the covered time range is then from 1970 to 2254 ;
- Epoch = 2050-01-01T00:00:00 TAI : the covered time range is then from 1908 to 2192 ;
- Epoch = 1970-01-01T00:00:00 UTC + 2^52 - (2^31) * 1000000: the covered time range is then from 1902 to 2186.
Option 1 would have the advantage to push the wrapping limit the farther away in the future. The disadvantage is that it can't represent time before 1970. The epoch offset is a value easy to remember.
Option 2 would have the advantage to allow representing time in the past. But the epoch offset would be an obscure integer magic number corresponding to the number of micro seconds between 1970 and 2150.
Option 3 has the advantage to cover the time span of 32 bit signed integer time_t value. The Timez would thus be backward compatible with the time_t values. The epoch offset is still a magic word but more easily obtained than the one of option 2. However, conversion between corrected and uncorrected time is not well defined before 1972.
Considering the pros and cons of the different options, I choose option 2. The Timez epoch is 1970-01-01T00:00:00 UTC + 2^52. The value 2^52 is the timez epoch offset relative to the POSIX time epoch. Note that 1970-01-01T00:00:00 UTC is 1970-01-01T00:00:10 TAI.
To convert a CLOCK_TAI value to a Timez micro second count use the following expression :
#define TIMEZ_EPOCH 0x10000000000000LL
struct timespec tp;
if (clock_gettime(CLOCK_TAI, &tp) ) /*fail*/ ;
int64_t t = tp.tv_sec * 1000000 + tp.tv_nsec / 1000 - TIMEZ_EPOCH;