summaryrefslogtreecommitdiff
path: root/src/date.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/date.c')
-rw-r--r--src/date.c448
1 files changed, 232 insertions, 216 deletions
diff --git a/src/date.c b/src/date.c
index c64ab1c..a42eb3c 100644
--- a/src/date.c
+++ b/src/date.c
@@ -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, "");