On 03/16/2016 08:51 PM, Assaf Gordon wrote:
I suspect it has something to do with this commit:
   commit    037e3b9847feb46cf6b58d99ce960d3987faaf52

You're right, and thanks for that detailed bug report. I installed the attached patch, which fixed the bug for me.
From 8d3b0efa6319daa3fb7451582f9fda7db5f3c2e3 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 17 Mar 2016 10:35:18 -0700
Subject: [PATCH] date ls pr: fix time zone abbrs on SysV platforms

The problematic code computed a struct tm in one time zone, and
then printed it or converted it to a string in another.  To be
portable the same time zone needs to be used for both operations.
On GNU platforms this is not an issue, but incorrect output can be
generated on System V style platforms like AIX where time zone
abbreviations are available only in the 'tzname' global variable.
Problem reported by Assaf Gordon in: http://bugs.gnu.org/23035
* NEWS: Document the bug.
* src/date.c (show_date):
* src/ls.c (long_time_expected_width, print_long_format):
* src/pr.c (init_header):
* src/stat.c (human_time): Use localtime_rz instead of localtime,
so that the time zone information is consistent for both localtime
and time-formatting functions like fprintftime and nstrftime.  For
'stat' this change is mostly just a code cleanup but it also
causes stat to also print nanoseconds when printing time stamps
that are out of localtime range, as this is more consistent with
what other programs do.  For programs other than 'stat' this fixes
bugs with time zone formats that use %Z.
* src/du.c, src/pr.c (localtz): New static var.
(main): Initialize it.
* src/du.c (show_date): New time zone argument, so that localtime
and fprintftime use the same time zone information.  All callers
changed.
* tests/misc/time-style.sh: New file.
* tests/local.mk (all_tests): Add it.
* tests/misc/date.pl: Test alphabetic time zone abbreviations.
---
 NEWS                     |   7 ++++
 src/date.c               |  24 +++++------
 src/du.c                 |  17 ++++----
 src/ls.c                 |  23 ++++-------
 src/pr.c                 |  23 ++++++-----
 src/stat.c               |  26 ++++++++----
 tests/local.mk           |   1 +
 tests/misc/date.pl       |   4 ++
 tests/misc/time-style.sh | 102 +++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 176 insertions(+), 51 deletions(-)
 create mode 100755 tests/misc/time-style.sh

diff --git a/NEWS b/NEWS
index ce46c46..d3597d0 100644
--- a/NEWS
+++ b/NEWS
@@ -8,11 +8,18 @@ GNU coreutils NEWS                                    -*- outline -*-
    handling ACLs on Cygwin and Solaris platforms. [bug introduced in
    coreutils-8.24]
 
+   date, du, ls, and pr no longer mishandle time zone abbreviations on
+   System V style platforms where this information is available only
+   in the global variable 'tzname'. [bug introduced in coreutils-8.24]
+
    stty --help no longer outputs extraneous gettext header lines
    for translated languages. [bug introduced in coreutils-8.24]
 
 ** Changes in behavior
 
+   stat now outputs nanosecond information for time stamps even if
+   they are out of localtime range.
+
    sort, tail, and uniq now support traditional usage like 'sort +2'
    and 'tail +10' on systems conforming to POSIX 1003.1-2008 and later.
    The 2008 edition of POSIX dropped the requirement that arguments
diff --git a/src/date.c b/src/date.c
index 269570b..e73196b 100644
--- a/src/date.c
+++ b/src/date.c
@@ -559,23 +559,23 @@ main (int argc, char **argv)
 static bool
 show_date (const char *format, struct timespec when, timezone_t tz)
 {
-  struct tm *tm;
+  struct tm tm;
 
-  tm = localtime (&when.tv_sec);
-  if (! tm)
+  if (localtime_rz (tz, &when.tv_sec, &tm))
+    {
+      if (format == rfc_2822_format)
+        setlocale (LC_TIME, "C");
+      fprintftime (stdout, format, &tm, tz, when.tv_nsec);
+      if (format == rfc_2822_format)
+        setlocale (LC_TIME, "");
+      fputc ('\n', stdout);
+      return true;
+    }
+  else
     {
       char buf[INT_BUFSIZE_BOUND (intmax_t)];
       error (0, 0, _("time %s is out of range"),
              quote (timetostr (when.tv_sec, buf)));
       return false;
     }
-
-  if (format == rfc_2822_format)
-    setlocale (LC_TIME, "C");
-  fprintftime (stdout, format, tm, tz, when.tv_nsec);
-  fputc ('\n', stdout);
-  if (format == rfc_2822_format)
-    setlocale (LC_TIME, "");
-
-  return true;
 }
diff --git a/src/du.c b/src/du.c
index 45c3703..45339fe 100644
--- a/src/du.c
+++ b/src/du.c
@@ -182,6 +182,9 @@ static char const *time_style = NULL;
 /* Format used to display date / time. Controlled by --time-style */
 static char const *time_format = NULL;
 
+/* The local time zone rules, as per the TZ environment variable.  */
+static timezone_t localtz;
+
 /* The units to use when printing sizes.  */
 static uintmax_t output_block_size;
 
@@ -372,19 +375,18 @@ hash_ins (struct di_set *di_set, ino_t ino, dev_t dev)
    in FORMAT.  */
 
 static void
-show_date (const char *format, struct timespec when)
+show_date (const char *format, struct timespec when, timezone_t tz)
 {
-  struct tm *tm = localtime (&when.tv_sec);
-  if (! tm)
+  struct tm tm;
+  if (localtime_rz (tz, &when.tv_sec, &tm))
+    fprintftime (stdout, format, &tm, tz, when.tv_nsec);
+  else
     {
       char buf[INT_BUFSIZE_BOUND (intmax_t)];
       char *when_str = timetostr (when.tv_sec, buf);
       error (0, 0, _("time %s is out of range"), quote (when_str));
       fputs (when_str, stdout);
-      return;
     }
-
-  fprintftime (stdout, format, tm, 0, when.tv_nsec);
 }
 
 /* Print N_BYTES.  Convert it to a readable value before printing.  */
@@ -412,7 +414,7 @@ print_size (const struct duinfo *pdui, const char *string)
   if (opt_time)
     {
       putchar ('\t');
-      show_date (time_format, pdui->tmax);
+      show_date (time_format, pdui->tmax, localtz);
     }
   printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
   fflush (stdout);
@@ -905,6 +907,7 @@ main (int argc, char **argv)
             (optarg
              ? XARGMATCH ("--time", optarg, time_args, time_types)
              : time_mtime);
+          localtz = tzalloc (getenv ("TZ"));
           break;
 
         case TIME_STYLE_OPTION:
diff --git a/src/ls.c b/src/ls.c
index d976036..3572060 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -3720,20 +3720,20 @@ long_time_expected_width (void)
   if (width < 0)
     {
       time_t epoch = 0;
-      struct tm const *tm = localtime (&epoch);
+      struct tm tm;
       char buf[TIME_STAMP_LEN_MAXIMUM + 1];
 
-      /* In case you're wondering if localtime can fail with an input time_t
+      /* In case you're wondering if localtime_rz can fail with an input time_t
          value of 0, let's just say it's very unlikely, but not inconceivable.
          The TZ environment variable would have to specify a time zone that
          is 2**31-1900 years or more ahead of UTC.  This could happen only on
          a 64-bit system that blindly accepts e.g., TZ=UTC+20000000000000.
          However, this is not possible with Solaris 10 or glibc-2.3.5, since
          their implementations limit the offset to 167:59 and 24:00, resp.  */
-      if (tm)
+      if (localtime_rz (localtz, &epoch, &tm))
         {
           size_t len =
-            align_nstrftime (buf, sizeof buf, long_time_format[0], tm,
+            align_nstrftime (buf, sizeof buf, long_time_format[0], &tm,
                              localtz, 0);
           if (len != 0)
             width = mbsnwidth (buf, len, 0);
@@ -3856,7 +3856,7 @@ print_long_format (const struct fileinfo *f)
   size_t s;
   char *p;
   struct timespec when_timespec;
-  struct tm *when_local;
+  struct tm when_local;
 
   /* Compute the mode string, except remove the trailing space if no
      file in this directory has an ACL or security context.  */
@@ -3983,11 +3983,10 @@ print_long_format (const struct fileinfo *f)
       p[-1] = ' ';
     }
 
-  when_local = localtime (&when_timespec.tv_sec);
   s = 0;
   *p = '\1';
 
-  if (f->stat_ok && when_local)
+  if (f->stat_ok && localtime_rz (localtz, &when_timespec.tv_sec, &when_local))
     {
       struct timespec six_months_ago;
       bool recent;
@@ -3997,13 +3996,7 @@ print_long_format (const struct fileinfo *f)
          time, in case the file happens to have been modified since
          the last time we checked the clock.  */
       if (timespec_cmp (current_time, when_timespec) < 0)
-        {
-          /* Note that gettime may call gettimeofday which, on some non-
-             compliant systems, clobbers the buffer used for localtime's result.
-             But it's ok here, because we use a gettimeofday wrapper that
-             saves and restores the buffer around the gettimeofday call.  */
-          gettime (&current_time);
-        }
+        gettime (&current_time);
 
       /* Consider a time to be recent if it is within the past six months.
          A Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds
@@ -4019,7 +4012,7 @@ print_long_format (const struct fileinfo *f)
       /* We assume here that all time zones are offset from UTC by a
          whole number of seconds.  */
       s = align_nstrftime (p, TIME_STAMP_LEN_MAXIMUM + 1, fmt,
-                           when_local, localtz, when_timespec.tv_nsec);
+                           &when_local, localtz, when_timespec.tv_nsec);
     }
 
   if (s || !*p)
diff --git a/src/pr.c b/src/pr.c
index 8885fff..d4549a3 100644
--- a/src/pr.c
+++ b/src/pr.c
@@ -710,6 +710,9 @@ static char *custom_header;
 /* (-D) Date format for the header.  */
 static char const *date_format;
 
+/* The local time zone rules, as per the TZ environment variable.  */
+static timezone_t localtz;
+
 /* Date and file name for the header.  */
 static char *date_text;
 static char const *file_text;
@@ -1049,6 +1052,8 @@ main (int argc, char **argv)
                    ? "%b %e %H:%M %Y"
                    : "%Y-%m-%d %H:%M");
 
+  localtz = tzalloc (getenv ("TZ"));
+
   /* Now we can set a reasonable initial value: */
   if (first_page_number == 0)
     first_page_number = 1;
@@ -1611,7 +1616,7 @@ init_header (char const *filename, int desc)
   struct stat st;
   struct timespec t;
   int ns;
-  struct tm *tm;
+  struct tm tm;
 
   /* If parallel files or standard input, use current date. */
   if (STREQ (filename, "-"))
@@ -1627,18 +1632,18 @@ init_header (char const *filename, int desc)
     }
 
   ns = t.tv_nsec;
-  tm = localtime (&t.tv_sec);
-  if (tm == NULL)
+  if (localtime_rz (localtz, &t.tv_sec, &tm))
     {
-      buf = xmalloc (INT_BUFSIZE_BOUND (long int)
-                     + MAX (10, INT_BUFSIZE_BOUND (int)));
-      sprintf (buf, "%ld.%09d", (long int) t.tv_sec, ns);
+      size_t bufsize
+        = nstrftime (NULL, SIZE_MAX, date_format, &tm, localtz, ns) + 1;
+      buf = xmalloc (bufsize);
+      nstrftime (buf, bufsize, date_format, &tm, localtz, ns);
     }
   else
     {
-      size_t bufsize = nstrftime (NULL, SIZE_MAX, date_format, tm, 0, ns) + 1;
-      buf = xmalloc (bufsize);
-      nstrftime (buf, bufsize, date_format, tm, 0, ns);
+      char secbuf[INT_BUFSIZE_BOUND (intmax_t)];
+      buf = xmalloc (sizeof secbuf + MAX (10, INT_BUFSIZE_BOUND (int)));
+      sprintf (buf, "%s.%09d", timetostr (t.tv_sec, secbuf), ns);
     }
 
   free (date_text);
diff --git a/src/stat.c b/src/stat.c
index e11e431..1742ff1 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -557,17 +557,27 @@ human_access (struct stat const *statbuf)
 static char * ATTRIBUTE_WARN_UNUSED_RESULT
 human_time (struct timespec t)
 {
-  static char str[MAX (INT_BUFSIZE_BOUND (intmax_t),
-                       (INT_STRLEN_BOUND (int) /* YYYY */
-                        + 1 /* because YYYY might equal INT_MAX + 1900 */
-                        + sizeof "-MM-DD HH:MM:SS.NNNNNNNNN +ZZZZ"))];
+  /* STR must be at least this big, either because localtime_rz fails,
+     or because the time zone is truly outlandish so that %z expands
+     to a long string.  */
+  enum { intmax_bufsize = INT_BUFSIZE_BOUND (intmax_t) };
+
+  static char str[intmax_bufsize
+                  + INT_STRLEN_BOUND (int) /* YYYY */
+                  + 1 /* because YYYY might equal INT_MAX + 1900 */
+                  + sizeof "-MM-DD HH:MM:SS.NNNNNNNNN +"];
   static timezone_t tz;
   if (!tz)
     tz = tzalloc (getenv ("TZ"));
-  struct tm const *tm = localtime (&t.tv_sec);
-  if (tm == NULL)
-    return timetostr (t.tv_sec, str);
-  nstrftime (str, sizeof str, "%Y-%m-%d %H:%M:%S.%N %z", tm, tz, t.tv_nsec);
+  struct tm tm;
+  int ns = t.tv_nsec;
+  if (localtime_rz (tz, &t.tv_sec, &tm))
+    nstrftime (str, sizeof str, "%Y-%m-%d %H:%M:%S.%N %z", &tm, tz, ns);
+  else
+    {
+      char secbuf[INT_BUFSIZE_BOUND (intmax_t)];
+      sprintf (str, "%s.%09d", timetostr (t.tv_sec, secbuf), ns);
+    }
   return str;
 }
 
diff --git a/tests/local.mk b/tests/local.mk
index 8898897..a83c3d0 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -390,6 +390,7 @@ all_tests =					\
   tests/misc/tail.pl				\
   tests/misc/tee.sh				\
   tests/misc/test-diag.pl			\
+  tests/misc/time-style.sh			\
   tests/misc/timeout.sh				\
   tests/misc/timeout-blocked.pl			\
   tests/misc/timeout-group.sh			\
diff --git a/tests/misc/date.pl b/tests/misc/date.pl
index 2d19254..3454dcc 100755
--- a/tests/misc/date.pl
+++ b/tests/misc/date.pl
@@ -218,6 +218,10 @@ my @Tests =
      # Don't recognize %:z with a field width between the ':' and the 'z'.
      ['tz-5wf', '+%:8z', {OUT=>"%:8z"}, {ENV=>'TZ=XXX0:01'}],
 
+     # Test alphabetic timezone abbrv
+     ['tz-6', '+%Z', {OUT=>"UTC"}],
+     ['tz-7', '+%Z', {OUT=>"JST"}, {ENV=>'TZ=JST-9'}],
+
      ['ns-relative',
       '--iso=ns',
       "-d'1970-01-01 00:00:00.1234567 UTC +961062237.987654321 sec'",
diff --git a/tests/misc/time-style.sh b/tests/misc/time-style.sh
new file mode 100755
index 0000000..4449961
--- /dev/null
+++ b/tests/misc/time-style.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+# Test --time-style in programs like 'ls'.
+
+# Copyright (C) 2016 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ du
+print_ver_ ls
+print_ver_ pr
+
+echo hello >a || framework_failure_
+
+# The tests assume this is an old time stamp in northern hemisphere summer.
+TZ=UTC0 touch -d '1970-07-08 09:10:11' a || framework_failure_
+
+for tz in UTC0 PST8 PST8PDT,M3.2.0,M11.1.0 XXXYYY-12:30; do
+  for style in full-iso long-iso iso locale '+%Y-%m-%d %H:%M:%S %z (%Z)'; do
+    test "$style" = locale ||
+      TZ=$tz LC_ALL=C du --time --time-style="$style" a >>duout 2>>err || fail=1
+    TZ=$tz LC_ALL=C ls -no --time-style="$style" a >>lsout 2>>err || fail=1
+    case $style in
+      (+*) TZ=$tz LC_ALL=C pr -D"$style" a >>prout 2>>err || fail=1 ;;
+    esac
+  done
+done
+
+sed 's/[^	]*	//' duout >dued || framework_failure_
+sed 's/[^ ]* *[^ ]* *[^ ]* *[^ ]* *//' lsout >lsed || framework_failure_
+sed '/^$/d' prout >pred || framework_failure_
+
+cat <<\EOF > duexp || fail=1
+1970-07-08 09:10:11.000000000 +0000	a
+1970-07-08 09:10	a
+1970-07-08	a
+1970-07-08 09:10:11 +0000 (UTC)	a
+1970-07-08 01:10:11.000000000 -0800	a
+1970-07-08 01:10	a
+1970-07-08	a
+1970-07-08 01:10:11 -0800 (PST)	a
+1970-07-08 02:10:11.000000000 -0700	a
+1970-07-08 02:10	a
+1970-07-08	a
+1970-07-08 02:10:11 -0700 (PDT)	a
+1970-07-08 21:40:11.000000000 +1230	a
+1970-07-08 21:40	a
+1970-07-08	a
+1970-07-08 21:40:11 +1230 (XXXYYY)	a
+EOF
+
+cat <<\EOF > lsexp || fail=1
+1970-07-08 09:10:11.000000000 +0000 a
+1970-07-08 09:10 a
+1970-07-08  a
+Jul  8  1970 a
+1970-07-08 09:10:11 +0000 (UTC) a
+1970-07-08 01:10:11.000000000 -0800 a
+1970-07-08 01:10 a
+1970-07-08  a
+Jul  8  1970 a
+1970-07-08 01:10:11 -0800 (PST) a
+1970-07-08 02:10:11.000000000 -0700 a
+1970-07-08 02:10 a
+1970-07-08  a
+Jul  8  1970 a
+1970-07-08 02:10:11 -0700 (PDT) a
+1970-07-08 21:40:11.000000000 +1230 a
+1970-07-08 21:40 a
+1970-07-08  a
+Jul  8  1970 a
+1970-07-08 21:40:11 +1230 (XXXYYY) a
+EOF
+
+cat <<\EOF > prexp || fail=1
++1970-07-08 09:10:11 +0000 (UTC)                a                 Page 1
+hello
++1970-07-08 01:10:11 -0800 (PST)                a                 Page 1
+hello
++1970-07-08 02:10:11 -0700 (PDT)                a                 Page 1
+hello
++1970-07-08 21:40:11 +1230 (XXXYYY)               a               Page 1
+hello
+EOF
+
+compare duexp dued || fail=1
+compare lsexp lsed || fail=1
+compare prexp pred || fail=1
+compare /dev/null err || fail=1
+
+Exit $fail
-- 
2.5.0

Reply via email to