[tz] strftime %s

Steve Summit scs at eskimo.com
Mon Jan 15 01:33:05 UTC 2024


kre wrote, quoting me:
> | > 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,
>
> Come on, be serious.

Oh, I was being perfectly serious, just not in the perfectly
literal way you interpreted it.

A good portion of the long message of mine you're referring
to was, I admit, primarily an internal dialog with myself,
and therefore not necessarily worthy of posting to this list.
But what I was convincing myself of was precisely, as you put
it, that the number generated by strftime %s:

> ...is *not* the time_t value that produced this
> struct tm - it cannot be, as no such thing need exist.

But before I convinced myself of this, whenever I used to see
that 'date +%s' was for printing what is, for all intents and
purposes, a raw time_t value, I imagined that's what it did:
print the raw time_t value.  So it follows that if someone is
trying to implement all of date(1)'s '+' options using strftime,
with the implication that strftime has to be able to do %s, it
further follows that strftime has to be able to -- somehow --
access that raw time_t value.

But, hang on, don't jump down my throat and correct me again,
because, I know: that's wrong.  But the way for me to *remember*
that it's wrong, so I can avoid making these mistakes again,
is to remind myself that strftime's computation of %s is *not*
a simple operation: it's a complex transformation, more or less
exactly equivalent to mktime.  It's potentially lossy, and it
does what you expect (if your expectation is even correct) only
if you use it very carefully, paying attention to subtle facets
of the documentation which are easy to overlook or misinterpret.
One which has been mentioned is that TZ has not changed.  Another
is that tzset either has or has not been called.  Yet another
(which I don't think has been mentioned yet) is that tm_isdst
is set correctly.

But, yes, if you're careful of all those things, %s will work
correctly.  But will it do what you want?  And, more importantly,
are those really the only things you have to be careful of?  Or
are there others that you've forgotten?  Or might there be others
in the future?  I shouldn't have said "even in the face of
serious bugs", that was sort of a personal shorthand.  The point
is that, even if there *aren't* "bugs in other parts of the
time-conversion logic", there might be bugs in your understanding
of what %s does, and there might be bugs in your ability to uphold
the guarantees that %s requires.  So if you've got a time_t value
you want to eventually print out in raw form, printf with a
format of either %ld or %lld is a much, much more reliable way of
doing it that going around the barn with localtime and strftime %s.
(That's why I brought up the analogy of atof and printf %f.)

But, anyway, I think your disagreement is not with my conclusion,
but rather, with the arguments I used to reach my conclusion.
I assure you, though, that in *my* head, those *are* the
arguments necessary to reinforce the correct conclusion!

(And then again: given the variety of interpretations just in
this thread, and even if you do succeed in convincing everyone
here that your interpretation of strftime %s is the correct one,
how sure can we be that all other implementations will agree?
Perhaps I shouldn't have walked back those words "even in the
face of serious bugs"; it may be that those serious bugs --
in some other implementations -- are more or less inevitable!)

> | > (Which brings me back to my conclusion that %s
> | > shouldn't exist, because it's impossible to implement correctly.
>
> Nonsense.   It is trivial to implement correctly.

A laughable conclusion, given the complexity of this thread!

But I think you mean, the long and the short of a proper %s
implementation is to call mktime on the struct tm handed to
strftime, and interpolate the result.  Right now I do agree
that's the correct implementation, and you're right, it's
trivial.  (But it's like that old joke about the lecturer who,
after being questioned about whether a certain result is truly
"obvious", spends half an hour alternately deep in thought or
scribbling abstrusely on the chalkboard, before triumphantly
concluding, "Yes, I was right, it is obvious.")

> Perhaps you mean that the specification does not achieve what
> you want it to produce - that's a different issue entirely.

Indeed, and that's partly what I meant -- but I don't believe
it's irrelevant.  An implementation that perfectly implements a
useless specification isn't useful.  strftime %s isn't useless,
so let me amend that to, an implementation that perfectly
implements a problematic specification may remain problematic.

> | > Please rely on %s only if you're the implementor of
> | > date(1) or the equivalent.
>
> Nonsense.   Further the implementor of date(1) doesn't care
> about %s at all, the '+format' operand is simply passed
> directly to strftime

No, I know, but if the implementor of date(1) has a specification
of the format specifiers accepted by '+', it might be prudent to
vet that list against the specification of the strftime call
that's about to be used.

> Incidentally, a bug free localtime() is much harder to achieve
> than a bug free mktime(), as mktime() can easily be implemented
> simply by making calls to localtime() and comparing the results
> with the input struct tm, until the input time_t to locatime()
> which produces the expected results is found.

And of course that's precisely how some implementations of
mktime *do* work!



More information about the tz mailing list