NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: lib/60452: gmtime, gmtime_r put wrong time zone into result
The problem is not gmtime() or gmtime_r(), which making the following
(not 100% portable, but works on current POSIX and all NetBSD) change
to your jq test program illustrates:
---- cut here ----
#include <err.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
void f_gmtime(time_t secs, struct tm *tmp) {
memset(tmp, 0, sizeof(*tmp));
if (gmtime_r(&secs, tmp) == NULL)
err(1, "gmtime_r failed");
}
char *f_strftime(time_t secs) {
char buf[1000];
struct tm tm;
memset(&tm, 0, sizeof(struct tm));
f_gmtime(secs, &tm);
printf("UTC Offset = %d (%s)\n", tm.tm_gmtoff, tm.tm_zone);
// struct tm *tmp;
// tmp = gmtime(&secs);
const char *fmt = " %FT%T%Z";
size_t n = strftime(buf, sizeof(buf), fmt, &tm);
// size_t n = strftime(buf, sizeof(buf), fmt, tmp);
/* POSIX doesn't provide errno values for strftime() failures; weird */
if ((n == 0) || n > sizeof(buf))
err(1, "strftime failed");
return strdup(buf + 1);
}
void f_localtime(time_t secs, struct tm *tmp) {
memset(tmp, 0, sizeof(*tmp));
if (localtime_r(&secs, tmp) == NULL)
err(1, "localtime_r failed");
}
char *f_strflocaltime(time_t secs) {
struct tm tm;
char buf[1000];
f_localtime(secs, &tm);
printf("Localtime Offset = %d (%s)\n", tm.tm_gmtoff, tm.tm_zone);
const char *fmt = " %FT%T%z";
size_t n = strftime(buf, sizeof(buf), fmt, &tm);
/* POSIX doesn't provide errno values for strftime() failures; weird */
if ((n == 0) || n > sizeof(buf))
err(1, "strftime failed");
return strdup(buf + 1);
}
int main(int argc, char *argv[]) {
time_t epoch_secs = 1731627341;
printf("%s\n", f_strftime(epoch_secs));
printf("%s\n", f_strflocaltime(epoch_secs));
}
---- cut here ----
Then:
jacaranda$ ./jq
UTC Offset = 0 (UTC)
2024-11-14T23:35:41+07
Localtime Offset = 25200 (+07)
2024-11-15T06:35:41+0700
jacaranda$ TZ=Etc/GMT+7 ./jq
UTC Offset = 0 (UTC)
2024-11-14T23:35:41-07
Localtime Offset = -25200 (-07)
2024-11-14T16:35:41-0700
In both cases, the UTC tm_gmtoff is zero, and tm_zone is "UTC".
(For me, localtime happens to be +7 rather than your +1, in the
default timezone, but that's actually convenient, as it shows more
obviously that the tm_gmtoff is correct for localtime as well,
without having to think about converting hours to seconds...)
All I changed for this was to add the printf statements to look at the
actual values of tm_gmtoff and tm_zone before strftime() is applied.
The problem you're seeing is from tzcode's attempt to obey the C
standard when it comes to %z and %Z conversions - according to which
the values that are in tm_zone and tm_gmtoff cannot be used to generate
those values - that's because those fields aren't required in C (they
are now in POSIX) and so a conforming application can't set them.
The strftime() implementation doesn't know (cannot possibly know) whether
the struct tm passed to it was generated by localtime/gmtime, or hand
crafted as in "tm.tm_secs = 33; tm.tm_hour = 2; ...." - in the former
it could reasonably expect tm_gmtoff and tm_zone to be set. In the latter
it cannot, they may simply be uninit'd stack garbage.
All that strftime() has to rely upon in those cases are the "timezone"
and "altzone" variables, which gmtime() doesn't set, they always reflect
the offsets for the local timezone.
The bug here is attempting to use either the %z or %Z strftime() formats
when the data comes from gmtime() - that's really fairly dumb, as in that
case you know the zone is UTC and the offset is 0, asking strftime() to
provide constant known strings doesn't make a lot of sense.
Note that if we actually use UTC as the local time, then the localtime()
version works as it should...
jacaranda$ TZ=UTC0 ./jq
UTC Offset = 0 (UTC)
2024-11-14T23:35:41UTC
Localtime Offset = 0 (UTC)
2024-11-14T23:35:41+0000
I'm not 100% sure that there is anything that we can do about this, we
could obviously violate the C standard (make conforming C programs fail)
but I'm not sure that's a good idea really.
kre
ps: if you look closely, you'll see I made one other small change to
the way that strftime() is called so that its return value can be
correctly interpreted - it makes no difference in the cases here,
as the %F format can never (if the struct tm is valid) return "",
but for some other conversions that might be used, it is good practice
to always include a dummy char at the start of the format string, which
cannot fail to be in the result, so that any 0 return value is guaranteed
to mean "failed" and not just "the output conversion is empty". Then
that dummy char (which can be anything except \0 or '%') is simply ignored
after strftime() returns.
And it is not at all weird that there are no errno values for strftime(),
there's nothing it does which can possibly generate any of the defined
errors, having it return something so strerr() could return "permission
denied" or whatever, would be stupid. If you consider what it is defined
to do, about all that can go wrong, is the use of an undefined '%' sequence,
so any error really means that, but except perhaps EINVAL (which never
really provides any useful info) what errno value would you expect? None
are needed. nb: if you set tm_mday = 999; then strftime("%d") will just
generate 999, the values aren't validated, if you set tm_wday = 19; then
strftime("%A") (or %a) will simply return nothing, as there is no locale
I know of which provides a name for the 19th day of the week.
Home |
Main Index |
Thread Index |
Old Index