diff options
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | src/date.c | 24 | ||||
-rw-r--r-- | src/du.c | 17 | ||||
-rw-r--r-- | src/ls.c | 23 | ||||
-rw-r--r-- | src/pr.c | 23 | ||||
-rw-r--r-- | src/stat.c | 26 | ||||
-rw-r--r-- | tests/local.mk | 1 | ||||
-rwxr-xr-x | tests/misc/date.pl | 4 | ||||
-rwxr-xr-x | tests/misc/time-style.sh | 102 |
9 files changed, 176 insertions, 51 deletions
@@ -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; } @@ -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: @@ -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 (¤t_time); - } + gettime (¤t_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) @@ -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 |