I haven't done a good old rant lately, have I?
Well, this is the time, and this is the day.
Speaking of which, did you know just what a clusterf*** time and date
related programming in C/C++ is? Or, as a matter of fact, in general?
Okay, boost's date_time library worked. Boost seems to have a tendency
to do that, I do give them that.
Yes, it did work... for UTC. That's GMT for the old timers. Except it's
not. Or it may be. Depends on who's talking. Could be plain UT instead
for all we know. Also, historically, for some time GMT-based dates
started at noon. So nobody in their sane mind should really use GMT to
denote universal points in time. Not that there is such thing as a
universal point in time anyway (Thanks, Einstein)... but I think I might
be digressing a little... so, boost worked nicely for UTC.
But if, instead of UTC, you want to play with times and dates in local
time, you need to use time zones.
Now there _is_ a timezone sub-library in boost date_time, and it is
designed to integrate neatly with the rest of date_time, so no big deal,
Except, said timezone library provides NO way to get the timezone
matching the system's locale. Because that, I guess, would be too common
a use case, and would make life too easy for programmers. You know how
you have to present challenges to zoo animals every once in a while so
they don't get mentally ill? I think it's the same for programmers.
Seems like boost got that message.
Granted, there's an interface to get timezone XY. But good luck
detecting that the system's locale uses timezone XY in the first place.
Sure, every operating system worth its price (or even just a fraction
thereof) provides a function to get the name of the current timezone.
But of course some OSes don't bother to follow any of the established
standards (of which there are multiple of course) for timezone names,
except their own. (Where have I seen this before? And what OS could I
possibly be looking at?) And of course that's precisely the one boost
refuses to recognize (which also deserves a reproachful stare).
Now you _could_ possibly use some other functions to try and detect the
offset from UTC, and then construct an ad-hoc time zone from that. But
that won't work reliably for dates other than today, because this won't
tell you whether Daylight Savings Time is in effect today, nor whether
that may be different from the day in question.
Speaking of DST - did you know that there are entire databases of what
Daylight Savings Time rules were in effect in what country in what time
period? And your operating system probably has one of those. Which may
or may not be up-to-date, but it's the best we can get.
So why not ditch boost date_time altogether, and use good old standard C
library functions. If _those_ don't know the proper time zone, and
whether DST applied, or will apply (presumably, based on current rules),
for any given date you name, nobody does.
There are a couple of standard functions defined to convert between a
semi-universal "point in time" data type (`time_t`) and a data type
holding Gregorian date and time data (`tm`).
Except, those functions are not thread-safe. Which is kind of a
deal-breaker in a multithreaded application. And besides, our automated
source code analysis tool would bicker about it, for exactly that reason.
Well, fortunately there are thread-safe alternatives provided by the
Not that there is any universal standard for them. Because, again, that
would be too easy.
So where e.g. POSIX systems have `localtime_r()` to replace
`localtime()`. Windows has `_localtime_s()`.
Not to be confused with `localtime_s()`, which is a standard function in
C (C11 and later to be precise). Which still hasn't been adopted into
any C++ standard. And has a different parameter order than Windows'
`_localtime_s()` - because zoo animals I guess.
But those functions do exist, for each of the original thread-unsafe C
So there's functions to convert to and from Gregorian date and time, in
both local time and UTC variants.
Except for the conversion _from_ Gregorian time. There's only one
variant for those. Guess which one.
If you guessed that variant was the UTC one - because it's easier to
implement, since the point-in-time data type is also universal rather
than local time - then you may guess again.
Yeah. No way to convert a Gregorian date and UTC time to that
point-in-time type, which is also UTC based.
Well, there is such a function on sone Unixoid systems, and oddly enough
for onece GNU and BSD agree there. But not on Windows. And not even on
Now you _could_ roll your own function to do this computation. Dig up a
formula to convert any Gregorian date to a linear day (say, Modified
Julian Date), subtract an offset so that you get a zero for 1970-01-01,
multiply by 24*60*60, add the time, and shove that value into `time_t`.
Because that's how that type works, right? POSIX time. Seconds since
1970-01-01, 00:00 UTC.
Well, not so fast. Because nowhere does the C or C++ standard mandate
that `time_t` actually work that way.
And as a matter of fact on most systems it doesn't. Though that's
actually all the better for the computations.
Because leap seconds. Did you ever spare a thought for them?
Not all days are created equal. Some are 24*60*60+1 seconds long. And
some could be, theoretically, as short as 24*60*60-1 seconds.
The former tends to happen about every one or two years, and the
astronomy boffins decide when it's time to insert another one.
To compute how many seconds have actually elapsed since any given date
and time, we'd have to consult a database of leap seconds.
Without that, our homebrew formula would only work if days were always
24*60*60 seconds long. And fortunately that's exactly how POSIX time
_actually_ works: Its base unit isn't seconds at all. It's calendar
days, except that they're scaled by a factor of 24*60*60, and with time
of day in seconds added.
Except on systems where it isn't. Which use POSIX-ish time _including_
leap seconds. And while they're rare, they still exist. Or, at least
hypothetically, systems where `time_t` runs faster than seconds. Or
slower; even that could be conceivable without violating the C standard.
So if we need to resort to such homebrew math, we should at least
implement a check on start-up to verify that the point-in-time data type
is in fact genuine POSIX time.
Fortunately we only need a homebrew function for one of those Gregorian
... as log as we don't care about dates before 1970.
Because, as it turns out, on Windows those functions report an error for
any point in time earlier than 1970-01-01 00:00 UTC. Or at least that's
what the documentation claims. In reality, you can't even rely on that:
A couple of hours before are fine, too. But no more than that.
On Unix at least there only seems to be some wonkiness for a very
specific 1-second interval around that time, namely 1969-12-31 23:59:59
UTC. Because apparently someone thought that value would make for a nice
choice to signal an error. (Did I mention my zoo animal hypothesis?)
So, yay. Hooray for date and time manipulation in C/C++.
I can't wait till the day we require C++20 to build POV-Ray. That's the
time and day when things will get...
... better? Well, different, anyway.
Post a reply to this message