summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS7
-rw-r--r--src/date.c24
-rw-r--r--src/du.c17
-rw-r--r--src/ls.c23
-rw-r--r--src/pr.c23
-rw-r--r--src/stat.c26
-rw-r--r--tests/local.mk1
-rwxr-xr-xtests/misc/date.pl4
-rwxr-xr-xtests/misc/time-style.sh102
9 files changed, 176 insertions, 51 deletions
diff --git a/NEWS b/NEWS
index ce46c46b5..d3597d0c1 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 269570b1a..e73196b96 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 45c37037c..45339fefb 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 d97603698..35720608c 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 8885fff4f..d4549a3bb 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 e11e4318f..1742ff128 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 8898897d0..a83c3d080 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 2d1925496..3454dcc5b 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 000000000..444996158
--- /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