From 2c29ba03acebe73681b1eee3227d48723bee2a6b Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Tue, 13 Sep 2005 22:08:41 +0000 Subject: (TIME_SPEC_DATE): No longer needs to be nonzero, so remove the "=1". (TIME_SOEC_HOURS, TIME_SPEC_MINUTES): Must be at end now, so put them there. (time_spec_string, time_spec): Hours and minutes must be at start now, so put them there. (rfc_2822_format): Now a string constant, not a boolean. All uses changed. (iso_8601_format, rfc_format): Remove. (RFC_3339_OPTION): New constant. (long_options): Add --rfc-3339. (usage): Add --rfc-3339. Don't mention --iso-8601. Mention %:z, %::z, %:::z. (main): Simplify calculation of 'format'; it was getting too hairy to follow. Add --rfc-3339. (show_date): Assume format arg is not NULL, which is the case now. The default code is moved to 'main'. This simplifies things and allows the default to be calculated just once. --- src/date.c | 276 +++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 151 insertions(+), 125 deletions(-) (limited to 'src/date.c') diff --git a/src/date.c b/src/date.c index da2bf558e..58fe96ea5 100644 --- a/src/date.c +++ b/src/date.c @@ -47,37 +47,47 @@ static bool show_date (const char *format, struct timespec when); enum Time_spec { - /* display only the date: 1999-03-25 */ - TIME_SPEC_DATE=1, - /* display date and hour: 1999-03-25T03-0500 */ - TIME_SPEC_HOURS, - /* display date, hours, and minutes: 1999-03-25T03:23-0500 */ - TIME_SPEC_MINUTES, - /* display date, hours, minutes, and seconds: 1999-03-25T03:23:14-0500 */ + /* Display only the date. */ + TIME_SPEC_DATE, + /* Display date, hours, minutes, and seconds. */ TIME_SPEC_SECONDS, - /* similar, but display nanoseconds: 1999-03-25T03:23:14,123456789-0500 */ - TIME_SPEC_NS + /* Similar, but display nanoseconds. */ + TIME_SPEC_NS, + + /* Put these last, since they aren't valid for --rfc-3339. */ + + /* Display date and hour. */ + TIME_SPEC_HOURS, + /* Display date, hours, and minutes. */ + TIME_SPEC_MINUTES }; static char const *const time_spec_string[] = { - "date", "hours", "minutes", "seconds", "ns", NULL + /* Put "hours" and "minutes" first, since they aren't valid for + --rfc-3339. */ + "hours", "minutes", + "date", "seconds", "ns", NULL }; static enum Time_spec const time_spec[] = { - TIME_SPEC_DATE, TIME_SPEC_HOURS, TIME_SPEC_MINUTES, TIME_SPEC_SECONDS, - TIME_SPEC_NS + TIME_SPEC_HOURS, TIME_SPEC_MINUTES, + TIME_SPEC_DATE, TIME_SPEC_SECONDS, TIME_SPEC_NS }; 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; -/* If nonzero, display an ISO 8601 format date/time string */ -static int iso_8601_format = 0; - -/* If true, display time in RFC-(2)822 format for mail or news. */ -static bool rfc_format = false; +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + RFC_3339_OPTION = CHAR_MAX + 1 +}; static char const short_options[] = "d:f:I::r:Rs:u"; @@ -85,10 +95,11 @@ static struct option const long_options[] = { {"date", required_argument, NULL, 'd'}, {"file", required_argument, NULL, 'f'}, - {"iso-8601", optional_argument, NULL, 'I'}, + {"iso-8601", optional_argument, NULL, 'I'}, /* Deprecated. */ {"reference", required_argument, NULL, 'r'}, {"rfc-822", no_argument, NULL, 'R'}, {"rfc-2822", no_argument, NULL, 'R'}, + {"rfc-3339", required_argument, NULL, RFC_3339_OPTION}, {"set", required_argument, NULL, 's'}, {"uct", no_argument, NULL, 'u'}, {"utc", no_argument, NULL, 'u'}, @@ -128,14 +139,13 @@ 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\ - -I[TIMESPEC], --iso-8601[=TIMESPEC] output date/time in ISO 8601 format.\n\ - TIMESPEC=`date' for date only (the default),\n\ - `hours', `minutes', `seconds', or `ns' for date and\n\ - time to the indicated precision.\n\ "), stdout); fputs (_("\ -r, --reference=FILE display the last modification time of FILE\n\ - -R, --rfc-2822 output RFC-2822 compliant date string\n\ + -R, --rfc-2822 output date and time in RFC 2822 format\n\ + --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\ -s, --set=STRING set time described by STRING\n\ -u, --utc, --universal print or set Coordinated Universal Time\n\ "), stdout); @@ -206,7 +216,10 @@ specifies Coordinated Universal Time. Interpreted sequences are:\n\ %Y year\n\ "), stdout); fputs (_("\ - %z numeric timezone (e.g., -0400)\n\ + %z +hhmm numeric timezone (e.g., -0400)\n\ + %:z +hh:mm numeric timezone (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\ \n\ By default, date pads numeric fields with zeroes.\n\ @@ -299,11 +312,10 @@ main (int argc, char **argv) const char *set_datestr = NULL; struct timespec when; bool set_date = false; - char *format; + char const *format = NULL; char *batch_file = NULL; char *reference = NULL; struct stat refstats; - int n_args; bool ok; int option_specified_date; @@ -317,45 +329,79 @@ main (int argc, char **argv) while ((optc = getopt_long (argc, argv, short_options, long_options, NULL)) != -1) - switch (optc) - { - case 'd': - datestr = optarg; - break; - case 'f': - batch_file = optarg; - break; - case 'I': - iso_8601_format = (optarg - ? XARGMATCH ("--iso-8601", optarg, - time_spec_string, time_spec) - : TIME_SPEC_DATE); - break; - case 'r': - reference = optarg; - break; - case 'R': - rfc_format = true; - 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); - } - - n_args = argc - optind; + { + 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); + } + + if (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) @@ -375,37 +421,49 @@ main (int argc, char **argv) usage (EXIT_FAILURE); } - if (n_args > 1) + if (optind < argc) { - error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); - usage (EXIT_FAILURE); - } + if (optind + 1 < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } - if ((set_date || option_specified_date) - && n_args == 1 && argv[optind][0] != '+') - { - 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 (argv[optind][0] == '+') + { + 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); + } } - /* Simply ignore --rfc-2822 if specified when setting the date. */ - if (rfc_format && !set_date && n_args > 0) + if (!format) { - error (0, 0, - _("a format string may not be specified when using\ - the --rfc-2822 (-R) option")); - usage (EXIT_FAILURE); + 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"; + } } - if (set_date) - datestr = set_datestr; - if (batch_file != NULL) - ok = batch_convert (batch_file, (n_args == 1 ? argv[optind] + 1 : NULL)); + ok = batch_convert (batch_file, format); else { bool valid_date = true; @@ -413,7 +471,7 @@ argument must be a format string beginning with `+'."), if (!option_specified_date && !set_date) { - if (n_args == 1 && argv[optind][0] != '+') + if (optind < argc) { /* Prepare to set system clock to the specified date/time given in the POSIX-format. */ @@ -424,14 +482,11 @@ argument must be a format string beginning with `+'."), (PDS_TRAILING_YEAR | PDS_CENTURY | PDS_SECONDS)); when.tv_nsec = 0; /* FIXME: posixtime should set this. */ - format = NULL; } else { /* Prepare to print the current date/time. */ - datestr = _("undefined"); gettime (&when); - format = (n_args == 1 ? argv[optind] + 1 : NULL); } } else @@ -446,10 +501,10 @@ argument must be a format string beginning with `+'."), } else { + if (set_datestr) + datestr = set_datestr; valid_date = get_date (&when, datestr, NULL); } - - format = (n_args == 1 ? argv[optind] + 1 : NULL); } if (! valid_date) @@ -481,35 +536,6 @@ static bool show_date (const char *format, struct timespec when) { struct tm *tm; - /* ISO 8601 formats. See below regarding %z */ - static char const * const iso_format_string[] = - { - "%Y-%m-%d", - "%Y-%m-%dT%H%z", - "%Y-%m-%dT%H:%M%z", - "%Y-%m-%dT%H:%M:%S%z", - "%Y-%m-%dT%H:%M:%S,%N%z" - }; - - if (format == NULL) - { - if (rfc_format) - format = "%a, %d %b %Y %H:%M:%S %z"; - else if (iso_8601_format) - format = iso_format_string[iso_8601_format - 1]; - else - { - char *date_fmt = DATE_FMT_LANGINFO (); - /* 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 = *date_fmt ? date_fmt : "%a %b %e %H:%M:%S %Z %Y"; - } - } tm = localtime (&when.tv_sec); if (! tm) @@ -525,10 +551,10 @@ show_date (const char *format, struct timespec when) { char *out; - if (rfc_format) + if (format == rfc_2822_format) setlocale (LC_TIME, "C"); out = xanstrftime (format, tm, 0, when.tv_nsec); - if (rfc_format) + if (format == rfc_2822_format) setlocale (LC_TIME, ""); puts (out); -- cgit v1.2.3-54-g00ecf