diff options
Diffstat (limited to 'src/date.c')
-rw-r--r-- | src/date.c | 448 |
1 files changed, 232 insertions, 216 deletions
@@ -1,10 +1,10 @@ /* date - print or set the system date and time - Copyright (C) 1989-2007 Free Software Foundation, Inc. + Copyright (C) 1989-2016 Free Software Foundation, Inc. - This program is free software; you can redistribute it and/or modify + 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 2, or (at your option) - any later version. + 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 @@ -12,8 +12,7 @@ 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, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + along with this program. If not, see <http://www.gnu.org/licenses/>. David MacKenzie <djm@gnu.ai.mit.edu> */ @@ -28,22 +27,18 @@ #include "system.h" #include "argmatch.h" #include "error.h" -#include "getdate.h" -#include "getline.h" -#include "inttostr.h" +#include "parse-datetime.h" #include "posixtm.h" #include "quote.h" #include "stat-time.h" #include "fprintftime.h" -/* The official name of this program (e.g., no `g' prefix). */ +/* The official name of this program (e.g., no 'g' prefix). */ #define PROGRAM_NAME "date" -#define AUTHORS "David MacKenzie" +#define AUTHORS proper_name ("David MacKenzie") -int putenv (); - -static bool show_date (const char *format, struct timespec when); +static bool show_date (const char *, struct timespec, timezone_t); enum Time_spec { @@ -79,9 +74,6 @@ ARGMATCH_VERIFY (time_spec_string, time_spec); /* A format suitable for Internet RFC 2822. */ static char const rfc_2822_format[] = "%a, %d %b %Y %H:%M:%S %z"; -/* The name this program was run with, for error messages. */ -char *program_name; - /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum @@ -95,7 +87,7 @@ static struct option const long_options[] = { {"date", required_argument, NULL, 'd'}, {"file", required_argument, NULL, 'f'}, - {"iso-8601", optional_argument, NULL, 'I'}, /* Deprecated. */ + {"iso-8601", optional_argument, NULL, 'I'}, {"reference", required_argument, NULL, 'r'}, {"rfc-822", no_argument, NULL, 'R'}, {"rfc-2822", no_argument, NULL, 'R'}, @@ -125,41 +117,53 @@ void usage (int status) { if (status != EXIT_SUCCESS) - fprintf (stderr, _("Try `%s --help' for more information.\n"), - program_name); + emit_try_help (); else { printf (_("\ Usage: %s [OPTION]... [+FORMAT]\n\ or: %s [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]\n\ "), - program_name, program_name); + program_name, program_name); fputs (_("\ Display the current time in the given FORMAT, or set the system date.\n\ -\n\ - -d, --date=STRING display time described by STRING, not `now'\n\ - -f, --file=DATEFILE like --date once for each line of DATEFILE\n\ +"), stdout); + + emit_mandatory_arg_note (); + + fputs (_("\ + -d, --date=STRING display time described by STRING, not 'now'\n\ + -f, --file=DATEFILE like --date; once for each line of DATEFILE\n\ "), stdout); fputs (_("\ - -r, --reference=FILE display the last modification time of FILE\n\ - -R, --rfc-2822 output date and time in RFC 2822 format.\n\ - Example: Mon, 07 Aug 2006 12:34:56 -0600\n\ + -I[FMT], --iso-8601[=FMT] output date/time in ISO 8601 format.\n\ + FMT='date' for date only (the default),\n\ + 'hours', 'minutes', 'seconds', or 'ns'\n\ + for date and time to the indicated precision.\n\ + Example: 2006-08-14T02:34:56-0600\n\ "), stdout); fputs (_("\ - --rfc-3339=TIMESPEC output date and time in RFC 3339 format.\n\ - TIMESPEC=`date', `seconds', or `ns' for\n\ - date and time to the indicated precision.\n\ - Date and time components are separated by\n\ - a single space: 2006-08-07 12:34:56-06:00\n\ - -s, --set=STRING set time described by STRING\n\ - -u, --utc, --universal print or set Coordinated Universal Time\n\ + -R, --rfc-2822 output date and time in RFC 2822 format.\n\ + Example: Mon, 14 Aug 2006 02:34:56 -0600\n\ +"), stdout); + fputs (_("\ + --rfc-3339=FMT output date/time in RFC 3339 format.\n\ + FMT='date', 'seconds', or 'ns'\n\ + for date and time to the indicated precision.\n\ + Example: 2006-08-14 02:34:56-06:00\n\ +"), stdout); + fputs (_("\ + -r, --reference=FILE display the last modification time of FILE\n\ +"), stdout); + fputs (_("\ + -s, --set=STRING set time described by STRING\n\ + -u, --utc, --universal print or set Coordinated Universal Time (UTC)\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ -FORMAT controls the output. The only valid option for the second form\n\ -specifies Coordinated Universal Time. Interpreted sequences are:\n\ +FORMAT controls the output. Interpreted sequences are:\n\ \n\ %% a literal %\n\ %a locale's abbreviated weekday name (e.g., Sun)\n\ @@ -171,8 +175,8 @@ specifies Coordinated Universal Time. Interpreted sequences are:\n\ %c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)\n\ "), stdout); fputs (_("\ - %C century; like %Y, except omit last two digits (e.g., 21)\n\ - %d day of month (e.g, 01)\n\ + %C century; like %Y, except omit last two digits (e.g., 20)\n\ + %d day of month (e.g., 01)\n\ %D date; same as %m/%d/%y\n\ %e day of month, space padded; same as %_d\n\ "), stdout); @@ -188,8 +192,8 @@ specifies Coordinated Universal Time. Interpreted sequences are:\n\ %j day of year (001..366)\n\ "), stdout); fputs (_("\ - %k hour ( 0..23)\n\ - %l hour ( 1..12)\n\ + %k hour, space padded ( 0..23); same as %_H\n\ + %l hour, space padded ( 1..12); same as %_I\n\ %m month (01..12)\n\ %M minute (00..59)\n\ "), stdout); @@ -221,8 +225,8 @@ specifies Coordinated Universal Time. Interpreted sequences are:\n\ %Y year\n\ "), stdout); fputs (_("\ - %z +hhmm numeric timezone (e.g., -0400)\n\ - %:z +hh:mm numeric timezone (e.g., -04:00)\n\ + %z +hhmm numeric time zone (e.g., -0400)\n\ + %:z +hh:mm numeric time zone (e.g., -04:00)\n\ %::z +hh:mm:ss numeric time zone (e.g., -04:00:00)\n\ %:::z numeric time zone with : to necessary precision (e.g., -04, +05:30)\n\ %Z alphabetic time zone abbreviation (e.g., EDT)\n\ @@ -230,7 +234,7 @@ specifies Coordinated Universal Time. Interpreted sequences are:\n\ By default, date pads numeric fields with zeroes.\n\ "), stdout); fputs (_("\ -The following optional flags may follow `%':\n\ +The following optional flags may follow '%':\n\ \n\ - (hyphen) do not pad the field\n\ _ (underscore) pad with spaces\n\ @@ -245,7 +249,19 @@ then an optional modifier, which is either\n\ E to use the locale's alternate representations if available, or\n\ O to use the locale's alternate numeric symbols if available.\n\ "), stdout); - printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + fputs (_("\ +\n\ +Examples:\n\ +Convert seconds since the epoch (1970-01-01 UTC) to a date\n\ + $ date --date='@2147483647'\n\ +\n\ +Show the time on the west coast of the US (use tzselect(1) to find TZ)\n\ + $ TZ='America/Los_Angeles' date\n\ +\n\ +Show the local time for 9AM next Friday on the west coast of the US\n\ + $ date --date='TZ=\"America/Los_Angeles\" 09:00 next Fri'\n\ +"), stdout); + emit_ancillary_info (PROGRAM_NAME); } exit (status); } @@ -256,7 +272,7 @@ O to use the locale's alternate numeric symbols if available.\n\ Return true if successful. */ static bool -batch_convert (const char *input_filename, const char *format) +batch_convert (const char *input_filename, const char *format, timezone_t tz) { bool ok; FILE *in_stream; @@ -273,9 +289,9 @@ batch_convert (const char *input_filename, const char *format) { in_stream = fopen (input_filename, "r"); if (in_stream == NULL) - { - error (EXIT_FAILURE, errno, "%s", quote (input_filename)); - } + { + error (EXIT_FAILURE, errno, "%s", quotef (input_filename)); + } } line = NULL; @@ -285,26 +301,26 @@ batch_convert (const char *input_filename, const char *format) { ssize_t line_length = getline (&line, &buflen, in_stream); if (line_length < 0) - { - /* FIXME: detect/handle error here. */ - break; - } - - if (! get_date (&when, line, NULL)) - { - if (line[line_length - 1] == '\n') - line[line_length - 1] = '\0'; - error (0, 0, _("invalid date %s"), quote (line)); - ok = false; - } + { + /* FIXME: detect/handle error here. */ + break; + } + + if (! parse_datetime (&when, line, NULL)) + { + if (line[line_length - 1] == '\n') + line[line_length - 1] = '\0'; + error (0, 0, _("invalid date %s"), quote (line)); + ok = false; + } else - { - ok &= show_date (format, when); - } + { + ok &= show_date (format, when, tz); + } } if (fclose (in_stream) == EOF) - error (EXIT_FAILURE, errno, "%s", quote (input_filename)); + error (EXIT_FAILURE, errno, "%s", quotef (input_filename)); free (line); @@ -327,7 +343,7 @@ main (int argc, char **argv) int option_specified_date; initialize_main (&argc, &argv); - program_name = argv[0]; + set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); @@ -335,209 +351,211 @@ main (int argc, char **argv) atexit (close_stdout); while ((optc = getopt_long (argc, argv, short_options, long_options, NULL)) - != -1) + != -1) { char const *new_format = NULL; switch (optc) - { - case 'd': - datestr = optarg; - break; - case 'f': - batch_file = optarg; - break; - case RFC_3339_OPTION: - { - static char const rfc_3339_format[][32] = - { - "%Y-%m-%d", - "%Y-%m-%d %H:%M:%S%:z", - "%Y-%m-%d %H:%M:%S.%N%:z" - }; - enum Time_spec i = - XARGMATCH ("--rfc-3339", optarg, - time_spec_string + 2, time_spec + 2); - new_format = rfc_3339_format[i]; - break; - } - case 'I': - { - static char const iso_8601_format[][32] = - { - "%Y-%m-%d", - "%Y-%m-%dT%H:%M:%S%z", - "%Y-%m-%dT%H:%M:%S,%N%z", - "%Y-%m-%dT%H%z", - "%Y-%m-%dT%H:%M%z" - }; - enum Time_spec i = - (optarg - ? XARGMATCH ("--iso-8601", optarg, time_spec_string, time_spec) - : TIME_SPEC_DATE); - new_format = iso_8601_format[i]; - break; - } - case 'r': - reference = optarg; - break; - case 'R': - new_format = rfc_2822_format; - break; - case 's': - set_datestr = optarg; - set_date = true; - break; - case 'u': - /* POSIX says that `date -u' is equivalent to setting the TZ - environment variable, so this option should do nothing other - than setting TZ. */ - if (putenv ("TZ=UTC0") != 0) - xalloc_die (); - TZSET; - break; - case_GETOPT_HELP_CHAR; - case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); - default: - usage (EXIT_FAILURE); - } + { + case 'd': + datestr = optarg; + break; + case 'f': + batch_file = optarg; + break; + case RFC_3339_OPTION: + { + static char const rfc_3339_format[][32] = + { + "%Y-%m-%d", + "%Y-%m-%d %H:%M:%S%:z", + "%Y-%m-%d %H:%M:%S.%N%:z" + }; + enum Time_spec i = + XARGMATCH ("--rfc-3339", optarg, + time_spec_string + 2, time_spec + 2); + new_format = rfc_3339_format[i]; + break; + } + case 'I': + { + static char const iso_8601_format[][32] = + { + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S%:z", + "%Y-%m-%dT%H:%M:%S,%N%:z", + "%Y-%m-%dT%H%:z", + "%Y-%m-%dT%H:%M%:z" + }; + enum Time_spec i = + (optarg + ? XARGMATCH ("--iso-8601", optarg, time_spec_string, time_spec) + : TIME_SPEC_DATE); + new_format = iso_8601_format[i]; + break; + } + case 'r': + reference = optarg; + break; + case 'R': + new_format = rfc_2822_format; + break; + case 's': + set_datestr = optarg; + set_date = true; + break; + case 'u': + /* POSIX says that 'date -u' is equivalent to setting the TZ + environment variable, so this option should do nothing other + than setting TZ. */ + if (putenv (bad_cast ("TZ=UTC0")) != 0) + xalloc_die (); + TZSET; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } if (new_format) - { - if (format) - error (EXIT_FAILURE, 0, _("multiple output formats specified")); - format = new_format; - } + { + if (format) + error (EXIT_FAILURE, 0, _("multiple output formats specified")); + format = new_format; + } } option_specified_date = ((datestr ? 1 : 0) - + (batch_file ? 1 : 0) - + (reference ? 1 : 0)); + + (batch_file ? 1 : 0) + + (reference ? 1 : 0)); if (option_specified_date > 1) { error (0, 0, - _("the options to specify dates for printing are mutually exclusive")); + _("the options to specify dates for printing are mutually exclusive")); usage (EXIT_FAILURE); } if (set_date && option_specified_date) { error (0, 0, - _("the options to print and set the time may not be used together")); + _("the options to print and set the time may not be used together")); usage (EXIT_FAILURE); } if (optind < argc) { if (optind + 1 < argc) - { - error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); - usage (EXIT_FAILURE); - } + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } if (argv[optind][0] == '+') - { - if (format) - error (EXIT_FAILURE, 0, _("multiple output formats specified")); - format = argv[optind++] + 1; - } + { + if (format) + error (EXIT_FAILURE, 0, _("multiple output formats specified")); + format = argv[optind++] + 1; + } else if (set_date || option_specified_date) - { - error (0, 0, - _("the argument %s lacks a leading `+';\n" - "When using an option to specify date(s), any non-option\n" - "argument must be a format string beginning with `+'."), - quote (argv[optind])); - usage (EXIT_FAILURE); - } + { + error (0, 0, + _("the argument %s lacks a leading '+';\n" + "when using an option to specify date(s), any non-option\n" + "argument must be a format string beginning with '+'"), + quote (argv[optind])); + usage (EXIT_FAILURE); + } } if (!format) { format = DATE_FMT_LANGINFO (); if (! *format) - { - /* Do not wrap the following literal format string with _(...). - For example, suppose LC_ALL is unset, LC_TIME="POSIX", - and LANG="ko_KR". In that case, POSIX says that LC_TIME - determines the format and contents of date and time strings - written by date, which means "date" must generate output - using the POSIX locale; but adding _() would cause "date" - to use a Korean translation of the format. */ - format = "%a %b %e %H:%M:%S %Z %Y"; - } + { + /* Do not wrap the following literal format string with _(...). + For example, suppose LC_ALL is unset, LC_TIME=POSIX, + and LANG="ko_KR". In that case, POSIX says that LC_TIME + determines the format and contents of date and time strings + written by date, which means "date" must generate output + using the POSIX locale; but adding _() would cause "date" + to use a Korean translation of the format. */ + format = "%a %b %e %H:%M:%S %Z %Y"; + } } + timezone_t tz = tzalloc (getenv ("TZ")); + if (batch_file != NULL) - ok = batch_convert (batch_file, format); + ok = batch_convert (batch_file, format, tz); else { bool valid_date = true; ok = true; if (!option_specified_date && !set_date) - { - if (optind < argc) - { - /* Prepare to set system clock to the specified date/time - given in the POSIX-format. */ - set_date = true; - datestr = argv[optind]; - valid_date = posixtime (&when.tv_sec, - datestr, - (PDS_TRAILING_YEAR - | PDS_CENTURY | PDS_SECONDS)); - when.tv_nsec = 0; /* FIXME: posixtime should set this. */ - } - else - { - /* Prepare to print the current date/time. */ - gettime (&when); - } - } + { + if (optind < argc) + { + /* Prepare to set system clock to the specified date/time + given in the POSIX-format. */ + set_date = true; + datestr = argv[optind]; + valid_date = posixtime (&when.tv_sec, + datestr, + (PDS_TRAILING_YEAR + | PDS_CENTURY | PDS_SECONDS)); + when.tv_nsec = 0; /* FIXME: posixtime should set this. */ + } + else + { + /* Prepare to print the current date/time. */ + gettime (&when); + } + } else - { - /* (option_specified_date || set_date) */ - if (reference != NULL) - { - if (stat (reference, &refstats) != 0) - error (EXIT_FAILURE, errno, "%s", reference); - when = get_stat_mtime (&refstats); - } - else - { - if (set_datestr) - datestr = set_datestr; - valid_date = get_date (&when, datestr, NULL); - } - } + { + /* (option_specified_date || set_date) */ + if (reference != NULL) + { + if (stat (reference, &refstats) != 0) + error (EXIT_FAILURE, errno, "%s", quotef (reference)); + when = get_stat_mtime (&refstats); + } + else + { + if (set_datestr) + datestr = set_datestr; + valid_date = parse_datetime (&when, datestr, NULL); + } + } if (! valid_date) - error (EXIT_FAILURE, 0, _("invalid date %s"), quote (datestr)); + error (EXIT_FAILURE, 0, _("invalid date %s"), quote (datestr)); if (set_date) - { - /* Set the system clock to the specified date, then regardless of - the success of that operation, format and print that date. */ - if (settime (&when) != 0) - { - error (0, errno, _("cannot set date")); - ok = false; - } - } - - ok &= show_date (format, when); + { + /* Set the system clock to the specified date, then regardless of + the success of that operation, format and print that date. */ + if (settime (&when) != 0) + { + error (0, errno, _("cannot set date")); + ok = false; + } + } + + ok &= show_date (format, when, tz); } - exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); + return ok ? EXIT_SUCCESS : EXIT_FAILURE; } /* Display the date and/or time in WHEN according to the format specified in FORMAT, followed by a newline. Return true if successful. */ static bool -show_date (const char *format, struct timespec when) +show_date (const char *format, struct timespec when, timezone_t tz) { struct tm *tm; @@ -546,15 +564,13 @@ show_date (const char *format, struct timespec when) { char buf[INT_BUFSIZE_BOUND (intmax_t)]; error (0, 0, _("time %s is out of range"), - (TYPE_SIGNED (time_t) - ? imaxtostr (when.tv_sec, buf) - : umaxtostr (when.tv_sec, buf))); + quote (timetostr (when.tv_sec, buf))); return false; } if (format == rfc_2822_format) setlocale (LC_TIME, "C"); - fprintftime (stdout, format, tm, 0, when.tv_nsec); + fprintftime (stdout, format, tm, tz, when.tv_nsec); fputc ('\n', stdout); if (format == rfc_2822_format) setlocale (LC_TIME, ""); |