lib/60452: gmtime, gmtime_r put wrong time zone into result

Thomas Klausner <[email protected]>
Newsgroups gmane.os.netbsd.bugs
Message-ID <[email protected]>
This is now PR 60452, but I hadn't set the Category correctly, so it
wasn't sent to the mailing list.
 Thomas


----- Forwarded message from [email protected] -----

>Submitter-Id:	net
>Originator:	Thomas Klausner
>Organization:
	
>Confidential:	no
>Synopsis:	gmtime, gmtime_r put TZ into struct tm
>Severity:	serious
>Priority:	medium
>Category:	
>Class:		sw-bug
>Release:	NetBSD 11.99.7/x86_64
	
	
>Environment:
	
	
Architecture: x86_64
Machine: amd64
>Description:
gmtime() and gmtime_r() are documented to return Coordinated
Universal Time (UTC), but they put the time zone offset of the
current timezone (from the system, or from the TZ environment
variable) into the resulting struct tm, while the actual time value
_is_ UTC.

I found this while debugging a jq test failure.
>How-To-Repeat:
Save this to jq.c (based on the actual code in jq, but heavily
simplified):

--- begin ---
#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);
  // 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);
}

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);
  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);
}

int main(int argc, char *argv[]) {
  uint64_t epoch_secs = 1731627341;
  printf("%s\n", f_strftime(epoch_secs));
  printf("%s\n", f_strflocaltime(epoch_secs));
}
--- end ---

and run this script:
--- begin ---
#!/bin/sh
make jq
./jq
TZ=Etc/GMT+7 ./jq
--- end ---

On my system this reports:
--- begin ---
2024-11-14T23:35:41CET
2024-11-15T00:35:41+0100
2024-11-14T23:35:41-07
2024-11-14T16:35:41-0700
--- end ---

The second and fourth lines are the correct result.

The first and third lines are wrong. They report the (correct) time
in UTC, but the reported time zones are CET (my system default) or
-7 (from TZ) respectively.
>Fix:
Yes, please.

----- End forwarded message -----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.