Robert Elz wrote:
date +%s isn't (usually) for humans, it is needed for scripts to work with time_t values...
Ah, yes. Good point. I remember now that it's recommended -- by no less than the comp.unix.shell FAQ list -- that if you need the current time_t value but you don't have date +%s available, you can instead use the not-at-all-obvious awk 'BEGIN { print srand(srand()) }' (But I'm bringing this up merely as a curiosity and for humor value, not as a serious topic of discussion!)
| (to my mind, anyway) still vastly preferable to trying to have | strftime handle %s by itself, because strftime just doesn't have | the right information available to it.
It does if the correct elements of the struct are filled in, as mktime() can reconstruct the time_t from a struct tm. However it does that assuming that the struct expresses the current local time (as defined by the TZ setting) - and not some other random zone.
Right. I confess I had forgotten this point. For anyone else who (like me) wasn't fully paying attention, let me state it another way: The struct tm you hand to strftime() is usually one you just got from localtime() or gmtime() -- BUT IT MIGHT NOT BE. In particular, if the struct tm handed to strftime() is one that, say, the caller just has hand-constructed, it is rather *un*likely to contain proper values for things like tm_gmtoff or the hypothetical tm_time_t I was mentioning and that Brooks picked up on. So (as others have said several times), strftime %s really does have no choice but to do a mktime() based on barely-adequate information -- and part of that information is, alas, the global TZ environment variable. An implication is that if you want to implement strftime %s, you can *not* make it easier on yourself by having localtime or gmtime helpfully stash extra information in struct tm, using fields like tm_gmtoff or tm_time_t. (I feel foolish saying this, because I'm one of the ones who was just suggesting making things easier by filling in extra information like tm_gmtoff or tm_time_t.) And then the other implication is that, dammit, the global TZ environment variable still really matters. You still have to ensure that it's the same for corresponding localtime and mktime calls -- and, if you're using %s, for corresponding strftime calls as well. Keeping it the same might not seem so hard -- who changes environment variables out from under a running program, anyway? -- except that changing TZ is the recommended and pretty much only way of dealing with a time zone other than the current one. (It sounds like Posix is *finally* standardizing the vital tm_gmtoff field. I wonder how many more years we'll have to wait for someone's blessing of BSD's variant _z functions?)
There are people who don't understand that, and insist that it must also work for other zones - but it simply doesn't.
Right. But go easy on them -- it really is almost inhumanly difficult to keep all of the sloppily dovetailing constraints in mind at the same time. In particular, it's absurdly difficult to remember how badly these functions all depend on the global TZ variable, and to remember that there really is no good way of working with time zones other than the current one. Me, I find myself half wishing for a big, bold warning on the strftime man page: The struct tm handed to strftime must be one returned by an immediately preceding call to localtime or gmtime. But that's not even true. That's the warning that would allow an implementor to look at tm_gmtoff (or the hypothetical tm_time_t) when writing strftime %s. But, in fact, callers *are* allowed to pass handcrafted struct tm values to strftime, and implementors are obliged to make this work -- even if there's a %s in the format string. (Which brings me back to my conclusion that %s shouldn't exist, because it's impossible to implement correctly. But, as the saying goes, people who believe a thing to be impossible should not stand in the way of those who are doing it.) So, in fact, the necessary big, bold warning is not one that goes on the strftime man page. No, the big, bold warning is for people like me, who keep dreaming of a set of time-conversion functins that's halfway sane and coherent. I'm not sure where the warning goes, but it would say something like: You might think that the sequence struct tm *tm = localtime(&t); strftime(buf, sizeof buf, "%s", tm); is fundamentally guaranteed to place a decimal representation of t into buf, where "fundamentally" implies that it just *has* to work, even in the face of serious bugs in other, unrelated parts of the time-conversion logic. But no, this sequence is in fact utterly vulnerable to bugs in other parts of the time-conversion logic, because it is necessarily equivalent to the sequence struct tm *tm = localtime(&t); time_t t2 = mktime(tm); which sets t2 == t only in the presence of a perfectly- implemented mktime, and also given certain other constraints, such as that TZ has not changed. The bottom line is that a call to localtime followed by a call to strftime %s is an only barely, skating-on-thin-ice-ily information-preserving transformation. It'll probably work, but really, it's more in the category of float f = atof(str); sprintf(str2, "%f", f); That is, what you're looking at when you use strftime %s is *not* a straight passing-through of data; you're probably looking at a pair of nominally-inverse but delicate and potentially lossy transformations. Perhaps there *is* a warning worthy of putting on the strftime man page, which is Please rely on %s only if you're the implementor of date(1) or the equivalent. If you're using %s to print a time_t value that your program has explicitly, it is far less error-prone to print that value directly, than to convert it to a struct tm and print it with strftime %s. Apologies for the long message, and for taking so long to understand that, yes, strftime %s really does have to do a full-blown mktime, with all the unreliability and imprecision that implies -- it can *not* do the easy thing and look at tm_gmtoff. (Stay tuned for our next exciting episode, in which the programmers who used to clamor for the nonstandard timegm function now request a strftime variant whose %s specifier assumes UTC.)