Line data Source code
1 : /* date - print or set the system date and time
2 : Copyright (C) 1989-2007 Free Software Foundation, Inc.
3 :
4 : This program is free software: you can redistribute it and/or modify
5 : it under the terms of the GNU General Public License as published by
6 : the Free Software Foundation, either version 3 of the License, or
7 : (at your option) any later version.
8 :
9 : This program is distributed in the hope that it will be useful,
10 : but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : GNU General Public License for more details.
13 :
14 : You should have received a copy of the GNU General Public License
15 : along with this program. If not, see <http://www.gnu.org/licenses/>.
16 :
17 : David MacKenzie <djm@gnu.ai.mit.edu> */
18 :
19 : #include <config.h>
20 : #include <stdio.h>
21 : #include <getopt.h>
22 : #include <sys/types.h>
23 : #if HAVE_LANGINFO_CODESET
24 : # include <langinfo.h>
25 : #endif
26 :
27 : #include "system.h"
28 : #include "argmatch.h"
29 : #include "error.h"
30 : #include "getdate.h"
31 : #include "inttostr.h"
32 : #include "posixtm.h"
33 : #include "quote.h"
34 : #include "stat-time.h"
35 : #include "fprintftime.h"
36 :
37 : /* The official name of this program (e.g., no `g' prefix). */
38 : #define PROGRAM_NAME "date"
39 :
40 : #define AUTHORS "David MacKenzie"
41 :
42 : int putenv ();
43 :
44 : static bool show_date (const char *format, struct timespec when);
45 :
46 : enum Time_spec
47 : {
48 : /* Display only the date. */
49 : TIME_SPEC_DATE,
50 : /* Display date, hours, minutes, and seconds. */
51 : TIME_SPEC_SECONDS,
52 : /* Similar, but display nanoseconds. */
53 : TIME_SPEC_NS,
54 :
55 : /* Put these last, since they aren't valid for --rfc-3339. */
56 :
57 : /* Display date and hour. */
58 : TIME_SPEC_HOURS,
59 : /* Display date, hours, and minutes. */
60 : TIME_SPEC_MINUTES
61 : };
62 :
63 : static char const *const time_spec_string[] =
64 : {
65 : /* Put "hours" and "minutes" first, since they aren't valid for
66 : --rfc-3339. */
67 : "hours", "minutes",
68 : "date", "seconds", "ns", NULL
69 : };
70 : static enum Time_spec const time_spec[] =
71 : {
72 : TIME_SPEC_HOURS, TIME_SPEC_MINUTES,
73 : TIME_SPEC_DATE, TIME_SPEC_SECONDS, TIME_SPEC_NS
74 : };
75 : ARGMATCH_VERIFY (time_spec_string, time_spec);
76 :
77 : /* A format suitable for Internet RFC 2822. */
78 : static char const rfc_2822_format[] = "%a, %d %b %Y %H:%M:%S %z";
79 :
80 : /* The name this program was run with, for error messages. */
81 : char *program_name;
82 :
83 : /* For long options that have no equivalent short option, use a
84 : non-character as a pseudo short option, starting with CHAR_MAX + 1. */
85 : enum
86 : {
87 : RFC_3339_OPTION = CHAR_MAX + 1
88 : };
89 :
90 : static char const short_options[] = "d:f:I::r:Rs:u";
91 :
92 : static struct option const long_options[] =
93 : {
94 : {"date", required_argument, NULL, 'd'},
95 : {"file", required_argument, NULL, 'f'},
96 : {"iso-8601", optional_argument, NULL, 'I'}, /* Deprecated. */
97 : {"reference", required_argument, NULL, 'r'},
98 : {"rfc-822", no_argument, NULL, 'R'},
99 : {"rfc-2822", no_argument, NULL, 'R'},
100 : {"rfc-3339", required_argument, NULL, RFC_3339_OPTION},
101 : {"set", required_argument, NULL, 's'},
102 : {"uct", no_argument, NULL, 'u'},
103 : {"utc", no_argument, NULL, 'u'},
104 : {"universal", no_argument, NULL, 'u'},
105 : {GETOPT_HELP_OPTION_DECL},
106 : {GETOPT_VERSION_OPTION_DECL},
107 : {NULL, 0, NULL, 0}
108 : };
109 :
110 : #if LOCALTIME_CACHE
111 : # define TZSET tzset ()
112 : #else
113 : # define TZSET /* empty */
114 : #endif
115 :
116 : #ifdef _DATE_FMT
117 : # define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT)
118 : #else
119 : # define DATE_FMT_LANGINFO() ""
120 : #endif
121 :
122 : void
123 29 : usage (int status)
124 : {
125 29 : if (status != EXIT_SUCCESS)
126 28 : fprintf (stderr, _("Try `%s --help' for more information.\n"),
127 : program_name);
128 : else
129 : {
130 1 : printf (_("\
131 : Usage: %s [OPTION]... [+FORMAT]\n\
132 : or: %s [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]\n\
133 : "),
134 : program_name, program_name);
135 1 : fputs (_("\
136 : Display the current time in the given FORMAT, or set the system date.\n\
137 : \n\
138 : -d, --date=STRING display time described by STRING, not `now'\n\
139 : -f, --file=DATEFILE like --date once for each line of DATEFILE\n\
140 : "), stdout);
141 1 : fputs (_("\
142 : -r, --reference=FILE display the last modification time of FILE\n\
143 : -R, --rfc-2822 output date and time in RFC 2822 format.\n\
144 : Example: Mon, 07 Aug 2006 12:34:56 -0600\n\
145 : "), stdout);
146 1 : fputs (_("\
147 : --rfc-3339=TIMESPEC output date and time in RFC 3339 format.\n\
148 : TIMESPEC=`date', `seconds', or `ns' for\n\
149 : date and time to the indicated precision.\n\
150 : Date and time components are separated by\n\
151 : a single space: 2006-08-07 12:34:56-06:00\n\
152 : -s, --set=STRING set time described by STRING\n\
153 : -u, --utc, --universal print or set Coordinated Universal Time\n\
154 : "), stdout);
155 1 : fputs (HELP_OPTION_DESCRIPTION, stdout);
156 1 : fputs (VERSION_OPTION_DESCRIPTION, stdout);
157 1 : fputs (_("\
158 : \n\
159 : FORMAT controls the output. The only valid option for the second form\n\
160 : specifies Coordinated Universal Time. Interpreted sequences are:\n\
161 : \n\
162 : %% a literal %\n\
163 : %a locale's abbreviated weekday name (e.g., Sun)\n\
164 : "), stdout);
165 1 : fputs (_("\
166 : %A locale's full weekday name (e.g., Sunday)\n\
167 : %b locale's abbreviated month name (e.g., Jan)\n\
168 : %B locale's full month name (e.g., January)\n\
169 : %c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)\n\
170 : "), stdout);
171 1 : fputs (_("\
172 : %C century; like %Y, except omit last two digits (e.g., 21)\n\
173 : %d day of month (e.g, 01)\n\
174 : %D date; same as %m/%d/%y\n\
175 : %e day of month, space padded; same as %_d\n\
176 : "), stdout);
177 1 : fputs (_("\
178 : %F full date; same as %Y-%m-%d\n\
179 : %g last two digits of year of ISO week number (see %G)\n\
180 : %G year of ISO week number (see %V); normally useful only with %V\n\
181 : "), stdout);
182 1 : fputs (_("\
183 : %h same as %b\n\
184 : %H hour (00..23)\n\
185 : %I hour (01..12)\n\
186 : %j day of year (001..366)\n\
187 : "), stdout);
188 1 : fputs (_("\
189 : %k hour ( 0..23)\n\
190 : %l hour ( 1..12)\n\
191 : %m month (01..12)\n\
192 : %M minute (00..59)\n\
193 : "), stdout);
194 1 : fputs (_("\
195 : %n a newline\n\
196 : %N nanoseconds (000000000..999999999)\n\
197 : %p locale's equivalent of either AM or PM; blank if not known\n\
198 : %P like %p, but lower case\n\
199 : %r locale's 12-hour clock time (e.g., 11:11:04 PM)\n\
200 : %R 24-hour hour and minute; same as %H:%M\n\
201 : %s seconds since 1970-01-01 00:00:00 UTC\n\
202 : "), stdout);
203 1 : fputs (_("\
204 : %S second (00..60)\n\
205 : %t a tab\n\
206 : %T time; same as %H:%M:%S\n\
207 : %u day of week (1..7); 1 is Monday\n\
208 : "), stdout);
209 1 : fputs (_("\
210 : %U week number of year, with Sunday as first day of week (00..53)\n\
211 : %V ISO week number, with Monday as first day of week (01..53)\n\
212 : %w day of week (0..6); 0 is Sunday\n\
213 : %W week number of year, with Monday as first day of week (00..53)\n\
214 : "), stdout);
215 1 : fputs (_("\
216 : %x locale's date representation (e.g., 12/31/99)\n\
217 : %X locale's time representation (e.g., 23:13:48)\n\
218 : %y last two digits of year (00..99)\n\
219 : %Y year\n\
220 : "), stdout);
221 1 : fputs (_("\
222 : %z +hhmm numeric timezone (e.g., -0400)\n\
223 : %:z +hh:mm numeric timezone (e.g., -04:00)\n\
224 : %::z +hh:mm:ss numeric time zone (e.g., -04:00:00)\n\
225 : %:::z numeric time zone with : to necessary precision (e.g., -04, +05:30)\n\
226 : %Z alphabetic time zone abbreviation (e.g., EDT)\n\
227 : \n\
228 : By default, date pads numeric fields with zeroes.\n\
229 : "), stdout);
230 1 : fputs (_("\
231 : The following optional flags may follow `%':\n\
232 : \n\
233 : - (hyphen) do not pad the field\n\
234 : _ (underscore) pad with spaces\n\
235 : 0 (zero) pad with zeros\n\
236 : ^ use upper case if possible\n\
237 : # use opposite case if possible\n\
238 : "), stdout);
239 1 : fputs (_("\
240 : \n\
241 : After any flags comes an optional field width, as a decimal number;\n\
242 : then an optional modifier, which is either\n\
243 : E to use the locale's alternate representations if available, or\n\
244 : O to use the locale's alternate numeric symbols if available.\n\
245 : "), stdout);
246 1 : emit_bug_reporting_address ();
247 : }
248 29 : exit (status);
249 : }
250 :
251 : /* Parse each line in INPUT_FILENAME as with --date and display each
252 : resulting time and date. If the file cannot be opened, tell why
253 : then exit. Issue a diagnostic for any lines that cannot be parsed.
254 : Return true if successful. */
255 :
256 : static bool
257 11 : batch_convert (const char *input_filename, const char *format)
258 : {
259 : bool ok;
260 : FILE *in_stream;
261 : char *line;
262 : size_t buflen;
263 : struct timespec when;
264 :
265 11 : if (STREQ (input_filename, "-"))
266 : {
267 3 : input_filename = _("standard input");
268 3 : in_stream = stdin;
269 : }
270 : else
271 : {
272 8 : in_stream = fopen (input_filename, "r");
273 8 : if (in_stream == NULL)
274 : {
275 1 : error (EXIT_FAILURE, errno, "%s", quote (input_filename));
276 : }
277 : }
278 :
279 10 : line = NULL;
280 10 : buflen = 0;
281 10 : ok = true;
282 : while (1)
283 16 : {
284 26 : ssize_t line_length = getline (&line, &buflen, in_stream);
285 26 : if (line_length < 0)
286 : {
287 : /* FIXME: detect/handle error here. */
288 10 : break;
289 : }
290 :
291 16 : if (! get_date (&when, line, NULL))
292 : {
293 0 : if (line[line_length - 1] == '\n')
294 0 : line[line_length - 1] = '\0';
295 0 : error (0, 0, _("invalid date %s"), quote (line));
296 0 : ok = false;
297 : }
298 : else
299 : {
300 16 : ok &= show_date (format, when);
301 : }
302 : }
303 :
304 10 : if (fclose (in_stream) == EOF)
305 0 : error (EXIT_FAILURE, errno, "%s", quote (input_filename));
306 :
307 10 : free (line);
308 :
309 10 : return ok;
310 : }
311 :
312 : int
313 216 : main (int argc, char **argv)
314 : {
315 : int optc;
316 216 : const char *datestr = NULL;
317 216 : const char *set_datestr = NULL;
318 : struct timespec when;
319 216 : bool set_date = false;
320 216 : char const *format = NULL;
321 216 : char *batch_file = NULL;
322 216 : char *reference = NULL;
323 : struct stat refstats;
324 : bool ok;
325 : int option_specified_date;
326 :
327 : initialize_main (&argc, &argv);
328 216 : program_name = argv[0];
329 216 : setlocale (LC_ALL, "");
330 : bindtextdomain (PACKAGE, LOCALEDIR);
331 : textdomain (PACKAGE);
332 :
333 216 : atexit (close_stdout);
334 :
335 505 : while ((optc = getopt_long (argc, argv, short_options, long_options, NULL))
336 : != -1)
337 : {
338 84 : char const *new_format = NULL;
339 :
340 84 : switch (optc)
341 : {
342 43 : case 'd':
343 43 : datestr = optarg;
344 43 : break;
345 11 : case 'f':
346 11 : batch_file = optarg;
347 11 : break;
348 0 : case RFC_3339_OPTION:
349 : {
350 : static char const rfc_3339_format[][32] =
351 : {
352 : "%Y-%m-%d",
353 : "%Y-%m-%d %H:%M:%S%:z",
354 : "%Y-%m-%d %H:%M:%S.%N%:z"
355 : };
356 0 : enum Time_spec i =
357 0 : XARGMATCH ("--rfc-3339", optarg,
358 : time_spec_string + 2, time_spec + 2);
359 0 : new_format = rfc_3339_format[i];
360 0 : break;
361 : }
362 9 : case 'I':
363 : {
364 : static char const iso_8601_format[][32] =
365 : {
366 : "%Y-%m-%d",
367 : "%Y-%m-%dT%H:%M:%S%z",
368 : "%Y-%m-%dT%H:%M:%S,%N%z",
369 : "%Y-%m-%dT%H%z",
370 : "%Y-%m-%dT%H:%M%z"
371 : };
372 8 : enum Time_spec i =
373 : (optarg
374 12 : ? XARGMATCH ("--iso-8601", optarg, time_spec_string, time_spec)
375 11 : : TIME_SPEC_DATE);
376 8 : new_format = iso_8601_format[i];
377 8 : break;
378 : }
379 7 : case 'r':
380 7 : reference = optarg;
381 7 : break;
382 1 : case 'R':
383 1 : new_format = rfc_2822_format;
384 1 : break;
385 2 : case 's':
386 2 : set_datestr = optarg;
387 2 : set_date = true;
388 2 : break;
389 2 : case 'u':
390 : /* POSIX says that `date -u' is equivalent to setting the TZ
391 : environment variable, so this option should do nothing other
392 : than setting TZ. */
393 2 : if (putenv ("TZ=UTC0") != 0)
394 0 : xalloc_die ();
395 : TZSET;
396 2 : break;
397 1 : case_GETOPT_HELP_CHAR;
398 1 : case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
399 7 : default:
400 7 : usage (EXIT_FAILURE);
401 : }
402 :
403 74 : if (new_format)
404 : {
405 9 : if (format)
406 1 : error (EXIT_FAILURE, 0, _("multiple output formats specified"));
407 8 : format = new_format;
408 : }
409 : }
410 :
411 410 : option_specified_date = ((datestr ? 1 : 0)
412 205 : + (batch_file ? 1 : 0)
413 205 : + (reference ? 1 : 0));
414 :
415 205 : if (option_specified_date > 1)
416 : {
417 0 : error (0, 0,
418 : _("the options to specify dates for printing are mutually exclusive"));
419 0 : usage (EXIT_FAILURE);
420 : }
421 :
422 205 : if (set_date && option_specified_date)
423 : {
424 0 : error (0, 0,
425 : _("the options to print and set the time may not be used together"));
426 0 : usage (EXIT_FAILURE);
427 : }
428 :
429 205 : if (optind < argc)
430 : {
431 150 : if (optind + 1 < argc)
432 : {
433 18 : error (0, 0, _("extra operand %s"), quote (argv[optind + 1]));
434 18 : usage (EXIT_FAILURE);
435 : }
436 :
437 132 : if (argv[optind][0] == '+')
438 : {
439 125 : if (format)
440 1 : error (EXIT_FAILURE, 0, _("multiple output formats specified"));
441 124 : format = argv[optind++] + 1;
442 : }
443 7 : else if (set_date || option_specified_date)
444 : {
445 2 : error (0, 0,
446 : _("the argument %s lacks a leading `+';\n"
447 : "When using an option to specify date(s), any non-option\n"
448 : "argument must be a format string beginning with `+'."),
449 2 : quote (argv[optind]));
450 2 : usage (EXIT_FAILURE);
451 : }
452 : }
453 :
454 184 : if (!format)
455 : {
456 54 : format = DATE_FMT_LANGINFO ();
457 54 : if (! *format)
458 : {
459 : /* Do not wrap the following literal format string with _(...).
460 : For example, suppose LC_ALL is unset, LC_TIME="POSIX",
461 : and LANG="ko_KR". In that case, POSIX says that LC_TIME
462 : determines the format and contents of date and time strings
463 : written by date, which means "date" must generate output
464 : using the POSIX locale; but adding _() would cause "date"
465 : to use a Korean translation of the format. */
466 0 : format = "%a %b %e %H:%M:%S %Z %Y";
467 : }
468 : }
469 :
470 184 : if (batch_file != NULL)
471 11 : ok = batch_convert (batch_file, format);
472 : else
473 : {
474 173 : bool valid_date = true;
475 173 : ok = true;
476 :
477 173 : if (!option_specified_date && !set_date)
478 : {
479 248 : if (optind < argc)
480 : {
481 : /* Prepare to set system clock to the specified date/time
482 : given in the POSIX-format. */
483 5 : set_date = true;
484 5 : datestr = argv[optind];
485 5 : valid_date = posixtime (&when.tv_sec,
486 : datestr,
487 : (PDS_TRAILING_YEAR
488 : | PDS_CENTURY | PDS_SECONDS));
489 5 : when.tv_nsec = 0; /* FIXME: posixtime should set this. */
490 : }
491 : else
492 : {
493 : /* Prepare to print the current date/time. */
494 119 : gettime (&when);
495 : }
496 : }
497 : else
498 : {
499 : /* (option_specified_date || set_date) */
500 49 : if (reference != NULL)
501 : {
502 7 : if (stat (reference, &refstats) != 0)
503 2 : error (EXIT_FAILURE, errno, "%s", reference);
504 5 : when = get_stat_mtime (&refstats);
505 : }
506 : else
507 : {
508 42 : if (set_datestr)
509 2 : datestr = set_datestr;
510 42 : valid_date = get_date (&when, datestr, NULL);
511 : }
512 : }
513 :
514 171 : if (! valid_date)
515 31 : error (EXIT_FAILURE, 0, _("invalid date %s"), quote (datestr));
516 :
517 140 : if (set_date)
518 : {
519 : /* Set the system clock to the specified date, then regardless of
520 : the success of that operation, format and print that date. */
521 2 : if (settime (&when) != 0)
522 : {
523 2 : error (0, errno, _("cannot set date"));
524 2 : ok = false;
525 : }
526 : }
527 :
528 140 : ok &= show_date (format, when);
529 : }
530 :
531 150 : exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
532 : }
533 :
534 : /* Display the date and/or time in WHEN according to the format specified
535 : in FORMAT, followed by a newline. Return true if successful. */
536 :
537 : static bool
538 156 : show_date (const char *format, struct timespec when)
539 : {
540 : struct tm *tm;
541 :
542 156 : tm = localtime (&when.tv_sec);
543 156 : if (! tm)
544 : {
545 : char buf[INT_BUFSIZE_BOUND (intmax_t)];
546 1 : error (0, 0, _("time %s is out of range"),
547 : (TYPE_SIGNED (time_t)
548 : ? imaxtostr (when.tv_sec, buf)
549 : : umaxtostr (when.tv_sec, buf)));
550 1 : return false;
551 : }
552 :
553 155 : if (format == rfc_2822_format)
554 1 : setlocale (LC_TIME, "C");
555 155 : fprintftime (stdout, format, tm, 0, when.tv_nsec);
556 155 : fputc ('\n', stdout);
557 155 : if (format == rfc_2822_format)
558 1 : setlocale (LC_TIME, "");
559 :
560 155 : return true;
561 : }
|