POV-Ray : Newsgroups : povray.programming : Time for a Rant : Time for a Rant Server Time
11 Oct 2024 13:50:21 EDT (-0400)
  Time for a Rant  
From: clipka
Date: 16 Aug 2021 18:45:56
Message: <611aeaa4$1@news.povray.org>
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, 
right?


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 
operating systems.

Phew.

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 
functions.


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 
generic POSIX.

*Sigh*.

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 
Date conversions...


... 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

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.