/* date - print or set the system date and time Copyright (C) 1989-2007 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, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. David MacKenzie */ #include #include #include #include #if HAVE_LANGINFO_CODESET # include #endif #include "system.h" #include "argmatch.h" #include "error.h" #include "getdate.h" #include "getline.h" #include "inttostr.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). */ #define PROGRAM_NAME "date" #define AUTHORS "David MacKenzie" int putenv (); static bool show_date (const char *format, struct timespec when); enum Time_spec { /* Display only the date. */ TIME_SPEC_DATE, /* Display date, hours, minutes, and seconds. */ TIME_SPEC_SECONDS, /* 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[] = { /* 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_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; /* 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"; static struct option const long_options[] = { {"date", required_argument, NULL, 'd'}, {"file", required_argument, NULL, 'f'}, {"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'}, {"universal", no_argument, NULL, 'u'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; #if LOCALTIME_CACHE # define TZSET tzset () #else # define TZSET /* empty */ #endif #ifdef _DATE_FMT # define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT) #else # define DATE_FMT_LANGINFO() "" #endif void usage (int status) { if (status != EXIT_SUCCESS) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [+FORMAT]\n\ or: %s [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]\n\ "), 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); 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\ "), 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\ "), 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\ \n\ %% a literal %\n\ %a locale's abbreviated weekday name (e.g., Sun)\n\ "), stdout); fputs (_("\ %A locale's full weekday name (e.g., Sunday)\n\ %b locale's abbreviated month name (e.g., Jan)\n\ %B locale's full month name (e.g., January)\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\ %D date; same as %m/%d/%y\n\ %e day of month, space padded; same as %_d\n\ "), stdout); fputs (_("\ %F full date; same as %Y-%m-%d\n\ %g last two digits of year of ISO week number (see %G)\n\ %G year of ISO week number (see %V); normally useful only with %V\n\ "), stdout); fputs (_("\ %h same as %b\n\ %H hour (00..23)\n\ %I hour (01..12)\n\ %j day of year (001..366)\n\ "), stdout); fputs (_("\ %k hour ( 0..23)\n\ %l hour ( 1..12)\n\ %m month (01..12)\n\ %M minute (00..59)\n\ "), stdout); fputs (_("\ %n a newline\n\ %N nanoseconds (000000000..999999999)\n\ %p locale's equivalent of either AM or PM; blank if not known\n\ %P like %p, but lower case\n\ %r locale's 12-hour clock time (e.g., 11:11:04 PM)\n\ %R 24-hour hour and minute; same as %H:%M\n\ %s seconds since 1970-01-01 00:00:00 UTC\n\ "), stdout); fputs (_("\ %S second (00..60)\n\ %t a tab\n\ %T time; same as %H:%M:%S\n\ %u day of week (1..7); 1 is Monday\n\ "), stdout); fputs (_("\ %U week number of year, with Sunday as first day of week (00..53)\n\ %V ISO week number, with Monday as first day of week (01..53)\n\ %w day of week (0..6); 0 is Sunday\n\ %W week number of year, with Monday as first day of week (00..53)\n\ "), stdout); fputs (_("\ %x locale's date representation (e.g., 12/31/99)\n\ %X locale's time representation (e.g., 23:13:48)\n\ %y last two digits of year (00..99)\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 +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\ "), stdout); fputs (_("\ The following optional flags may follow `%':\n\ \n\ - (hyphen) do not pad the field\n\ _ (underscore) pad with spaces\n\ 0 (zero) pad with zeros\n\ ^ use upper case if possible\n\ # use opposite case if possible\n\ "), stdout); fputs (_("\ \n\ After any flags comes an optional field width, as a decimal number;\n\ 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); emit_bug_reporting_address (); } exit (status); } /* 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. Return true if successful. */ static bool batch_convert (const char *input_filename, const char *format) { bool ok; FILE *in_stream; char *line; size_t buflen; struct timespec when; if (STREQ (input_filename, "-")) { input_filename = _("standard input"); in_stream = stdin; } else { in_stream = fopen (input_filename, "r"); if (in_stream == NULL) { error (EXIT_FAILURE, errno, "%s", quote (input_filename)); } } line = NULL; buflen = 0; ok = true; while (1) { 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; } else { ok &= show_date (format, when); } } if (fclose (in_stream) == EOF) error (EXIT_FAILURE, errno, "%s", quote (input_filename)); free (line); return ok; } int main (int argc, char **argv) { int optc; const char *datestr = NULL; const char *set_datestr = NULL; struct timespec when; bool set_date = false; char const *format = NULL; char *batch_file = NULL; char *reference = NULL; struct stat refstats; bool ok; int option_specified_date; initialize_main (&argc, &argv); program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); while ((optc = getopt_long (argc, argv, short_options, long_options, NULL)) != -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); } 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) + (reference ? 1 : 0)); if (option_specified_date > 1) { error (0, 0, _("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")); usage (EXIT_FAILURE); } if (optind < argc) { if (optind + 1 < argc) { 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; } 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); } } 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"; } } if (batch_file != NULL) ok = batch_convert (batch_file, format); 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); } } 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); } } if (! valid_date) 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); } exit (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) { struct tm *tm; tm = localtime (&when.tv_sec); if (! tm) { 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))); return false; } if (format == rfc_2822_format) setlocale (LC_TIME, "C"); fprintftime (stdout, format, tm, 0, when.tv_nsec); fputc ('\n', stdout); if (format == rfc_2822_format) setlocale (LC_TIME, ""); return true; }