tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: strftime(3) oddities with %s, %z



    Date:        Tue, 25 Oct 2022 04:08:13 +0300
    From:        Valery Ushakov <uwe%stderr.spb.ru@localhost>
    Message-ID:  <Y1c2/dI8SeQ1Oz8c%pony.stderr.spb.ru@localhost>

  | strftime(3) %s format is not in ISO C or POSIX, though the rumor is
  | that it will be in the next POSIX version.

It will be.   The text is:

s    Replaced by the number of seconds since the Epoch as a decimal number,
     calculated as described for mktime( ). [all members]

For what it is worth, since we are here, tm_zone and tm_gmtoff will be
in the next POSIX as well.   That change I was personally hesitant about,
not because they're not a good idea (they are - except that the lifetime
of the string pointed to by tm_zone is kind of peculiar, and could do to
have been improved) but because adding those fields for implementations
that don't already have them is going to be a disaster for their ABI.

Not an issue for NetBSD of course.


  | The problem though is that mktime(3) is specified to convert tm
  | "expressed as local time".

Yes, that is not a problem though, it is how it is specified to work.
Everything in strftime() takes the struct tm as being in local time.
(That's why NetBSD has strftime_z()).

  | The test below produces ("g" is for tm from gmtime, "l" is for tm from
  | localtime):

I am not sure that calling strftime() on something obtained from gmtime()
makes any sense.  If one wants UTC conversions, either use strftime_z()
(not a POSIX interface) or set the timezone to UTC (tzset() with TZ="UTC").

  |     time_t        mktime       %s          %F %T (%z)
  | netbsd:
  | g:  1659968100 -> 1659957300 = 1659957300: 2022-08-08 14:15:00 (+0300)
  | l:  1659968100 -> 1659968100 = 1659968100: 2022-08-08 17:15:00 (+0300)

That looks correct to me, mktime().

  | glic:
  | g:  1659968100 -> 1659957300 = 1659957300: 2022-08-08 14:15:00 (+0000)
  | l:  1659968100 -> 1659968100 = 1659968100: 2022-08-08 17:15:00 (+0300)

That's almost correct, the zone name hasn't been filled in, mktime()
(which strftime("%s") is intended to call, or at least equivalent
functionality, should fill in the local zone offset in tm_gmtoff,
though until the next standard appears, since tm_gmtoff, that is
obviously not a requirement).   That is, once mktime() has been
called, which came before the %z conversion, the gmtoff should have
been set to represent local time.

  | g:  1659968100 -> 1659957300 = 1659968100: 2022-08-08 14:15:00 (+0000)
  | l:  1659968100 -> 1659968100 = 1659968100: 2022-08-08 17:15:00 (+0300)

The g entry there is simply wrong (as %s and mktime() are defined).   The
result from mktime() (1659957300 there) is required to be what %s produces.

  | Note that both netbsd and glibc adjust the %s value by the local tz
  | offset for struct tm returned by gmtime.

That's what they're required to do.   mktime() does not examine anything
except the tm_sec tm_min tm_hour tm_mday tm_mon tm_year and tm_isdst
fields - it sets the others to appropriate values as a side effect (it
also sets tm_isdst if its entry value was -1).

  | Note that netbsd additionally gets the %z wrong for the tm obtained
  | from gmtime(3).

Not wrong, just unexcpected, but the situation shouldn't arise, as
a tm from gmtime shouldn't be being passed to strftime at all (and
certainly not if %s is used).

kre



Home | Main Index | Thread Index | Old Index