>From 82c8b42de7bf9c69432ff175838f01f10008a512 Mon Sep 17 00:00:00 2001 From: Assaf Gordon
Date: Thu, 25 Jul 2019 02:35:46 -0600 Subject: [PATCH 1/2] date: add --date-format=FORMAT option Parse -d=STRING dates using strptime(3) instead of gnulib's parse_datetime.c heuristics. Example: print the 100th day of 2019: $ date --date-format '%Y %j' --date '2019 100' +%F 2019-04-10 TODO: coreutils.texi, NEWS, usage * src/date.c (long_options): Add --date-format/STRP_FORMAT option. (parse_datetime_flags): Replace with ... (debug): ... new variable. (strp_format): New variable to hold the user-specified FORMAT string. (parse_datetime_string): New function, wrapper for parse_datetime2/strptime. (batch_convert, main): Call parse_datetime_string instead of parse_datetime2. (main): Handle STRP_FORMAT option. * tests/misc/date-strp.pl: New tests. * tests/local.mk (TESTS): Add date-strp.pl --- src/date.c | 78 ++++++++++++++++++++++--- tests/local.mk | 1 + tests/misc/date-strp.pl | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 tests/misc/date-strp.pl diff --git a/src/date.c b/src/date.c index d97d0ae52..4879474e3 100644 --- a/src/date.c +++ b/src/date.c @@ -80,7 +80,8 @@ static char const rfc_email_format[] = "%a, %d %b %Y %H:%M:%S %z"; enum { RFC_3339_OPTION = CHAR_MAX + 1, - DEBUG_DATE_PARSING + DEBUG_DATE_PARSING, + STRP_FORMAT }; static char const short_options[] = "d:f:I::r:Rs:u"; @@ -97,6 +98,7 @@ static struct option const long_options[] = {"rfc-2822", no_argument, NULL, 'R'}, {"rfc-3339", required_argument, NULL, RFC_3339_OPTION}, {"set", required_argument, NULL, 's'}, + {"date-format", required_argument, NULL, STRP_FORMAT}, {"uct", no_argument, NULL, 'u'}, {"utc", no_argument, NULL, 'u'}, {"universal", no_argument, NULL, 'u'}, @@ -105,8 +107,11 @@ static struct option const long_options[] = {NULL, 0, NULL, 0} }; -/* flags for parse_datetime2 */ -static unsigned int parse_datetime_flags; +static bool debug ; + +/* the strp format string specified by the user */ +static char* strp_format; + #if LOCALTIME_CACHE # define TZSET tzset () @@ -142,6 +147,9 @@ Display the current time in the given FORMAT, or set the system date.\n\ -d, --date=STRING display time described by STRING, not 'now'\n\ "), stdout); fputs (_("\ + --date-format=FORMAT parse -d,-f values according to FORMAT\n\ +"), stdout); + fputs (_("\ --debug annotate the parsed date,\n\ and warn about questionable usage to stderr\n\ "), stdout); @@ -281,6 +289,57 @@ Show the local time for 9AM next Friday on the west coast of the US\n\ exit (status); } +/* A wrapper calling either gnulib's parse_datetime2() or strptime(3), + depending on whether the user specified --date-format=FORMAT argument. */ +static bool +parse_datetime_string (struct timespec *result, char const *datestr, + timezone_t tzdefault, char const *tzstring) +{ + if (strp_format) + { + struct tm t; + time_t s = time (NULL); + localtime_rz (tzdefault, &s, &t); + char *endp = strptime (datestr, strp_format, &t ); + if (!endp) + { + if (debug) + error (0, 0, _("date string %s does not match format '%s'"), + quotearg (datestr), + strp_format); + return false; + } + + if (*endp) + { + if (debug) + error (EXIT_FAILURE, 0, _("extraneous characters in date " \ + "string: %s"), + quotearg (endp)); + return false; + } + + s = mktime (&t); + if (s == (time_t)-1) + { + error (0, errno, _("mktime failed")); + return false; + } + + *result = make_timespec (s, 0); + return true; + } + else + { + unsigned int parse_datetime_flags = debug ? PARSE_DATETIME_DEBUG : 0 ; + + return parse_datetime2 (result, datestr, NULL, + parse_datetime_flags, + tzdefault, tzstring); + } +} + + /* Parse each line in INPUT_FILENAME as with --date and display each resulting time and date. If the file cannot be opened, tell why then exit. Issue a diagnostic for any lines that cannot be parsed. @@ -322,8 +381,7 @@ batch_convert (const char *input_filename, const char *format, break; } - if (! parse_datetime2 (&when, line, NULL, - parse_datetime_flags, tz, tzstring)) + if (! parse_datetime_string (&when, line, tz, tzstring)) { if (line[line_length - 1] == '\n') line[line_length - 1] = '\0'; @@ -378,7 +436,7 @@ main (int argc, char **argv) datestr = optarg; break; case DEBUG_DATE_PARSING: - parse_datetime_flags |= PARSE_DATETIME_DEBUG; + debug = true; break; case 'f': batch_file = optarg; @@ -424,6 +482,9 @@ main (int argc, char **argv) set_datestr = optarg; set_date = true; break; + case STRP_FORMAT: + strp_format = optarg; + break; case 'u': /* POSIX says that 'date -u' is equivalent to setting the TZ environment variable, so this option should do nothing other @@ -548,9 +609,8 @@ main (int argc, char **argv) { if (set_datestr) datestr = set_datestr; - valid_date = parse_datetime2 (&when, datestr, NULL, - parse_datetime_flags, - tz, tzstring); + + valid_date = parse_datetime_string (&when, datestr, tz, tzstring); } } diff --git a/tests/local.mk b/tests/local.mk index e88d99f24..2a4f277ff 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -254,6 +254,7 @@ all_tests = \ tests/tail-2/tail-n0f.sh \ tests/misc/ls-misc.pl \ tests/misc/date.pl \ + tests/misc/date-strp.pl \ tests/misc/date-next-dow.pl \ tests/misc/ptx-overrun.sh \ tests/misc/xstrtol.pl \ diff --git a/tests/misc/date-strp.pl b/tests/misc/date-strp.pl new file mode 100644 index 000000000..4fc247cee --- /dev/null +++ b/tests/misc/date-strp.pl @@ -0,0 +1,151 @@ +#!/usr/bin/perl +# Test date's --date-format=FORMAT feature + +# Copyright (C) 2019 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