(usage): Describe %F, %g, %G, %P, and %R.
[platform/upstream/coreutils.git] / src / date.c
1 /* date - print or set the system date and time
2    Copyright (C) 1989-2002 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 2, or (at your option)
7    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, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18    David MacKenzie <djm@gnu.ai.mit.edu> */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <getopt.h>
23 #include <sys/types.h>
24 #if HAVE_LANGINFO_H
25 # include <langinfo.h>
26 #endif
27
28 #include "system.h"
29 #include "argmatch.h"
30 #include "closeout.h"
31 #include "error.h"
32 #include "getdate.h"
33 #include "getline.h"
34 #include "posixtm.h"
35
36 /* The official name of this program (e.g., no `g' prefix).  */
37 #define PROGRAM_NAME "date"
38
39 #define AUTHORS "David MacKenzie"
40
41 #ifndef STDC_HEADERS
42 size_t strftime ();
43 time_t time ();
44 #endif
45
46 int putenv ();
47 int stime ();
48
49 static void show_date PARAMS ((const char *format, time_t when));
50
51 enum Time_spec
52 {
53   /* display only the date: 1999-03-25 */
54   TIME_SPEC_DATE=1,
55   /* display date and hour: 1999-03-25T03-0500 */
56   TIME_SPEC_HOURS,
57   /* display date, hours, and minutes: 1999-03-25T03:23-0500 */
58   TIME_SPEC_MINUTES,
59   /* display date, hours, minutes, and seconds: 1999-03-25T03:23:14-0500 */
60   TIME_SPEC_SECONDS
61 };
62
63 static char const *const time_spec_string[] =
64 {
65   "date", "hours", "minutes", "seconds", 0
66 };
67
68 static enum Time_spec const time_spec[] =
69 {
70   TIME_SPEC_DATE, TIME_SPEC_HOURS, TIME_SPEC_MINUTES, TIME_SPEC_SECONDS
71 };
72
73 /* The name this program was run with, for error messages. */
74 char *program_name;
75
76 /* If nonzero, display an ISO 8601 format date/time string */
77 static int iso_8601_format = 0;
78
79 /* If non-zero, display time in RFC-(2)822 format for mail or news. */
80 static int rfc_format = 0;
81
82 static struct option const long_options[] =
83 {
84   {"date", required_argument, NULL, 'd'},
85   {"file", required_argument, NULL, 'f'},
86   {"iso-8601", optional_argument, NULL, 'I'},
87   {"reference", required_argument, NULL, 'r'},
88   {"rfc-822", no_argument, NULL, 'R'},
89   {"set", required_argument, NULL, 's'},
90   {"uct", no_argument, NULL, 'u'},
91   {"utc", no_argument, NULL, 'u'},
92   {"universal", no_argument, NULL, 'u'},
93   {GETOPT_HELP_OPTION_DECL},
94   {GETOPT_VERSION_OPTION_DECL},
95   {NULL, 0, NULL, 0}
96 };
97
98 #if LOCALTIME_CACHE
99 # define TZSET tzset ()
100 #else
101 # define TZSET /* empty */
102 #endif
103
104 #ifdef _DATE_FMT
105 # define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT)
106 #else
107 # define DATE_FMT_LANGINFO() ""
108 #endif
109
110 void
111 usage (int status)
112 {
113   if (status != 0)
114     fprintf (stderr, _("Try `%s --help' for more information.\n"),
115              program_name);
116   else
117     {
118       printf (_("\
119 Usage: %s [OPTION]... [+FORMAT]\n\
120   or:  %s [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]\n\
121 "),
122               program_name, program_name);
123       fputs (_("\
124 Display the current time in the given FORMAT, or set the system date.\n\
125 \n\
126   -d, --date=STRING         display time described by STRING, not `now'\n\
127   -f, --file=DATEFILE       like --date once for each line of DATEFILE\n\
128   -I, --iso-8601[=TIMESPEC] output an ISO-8601 compliant date/time string.\n\
129                             TIMESPEC=`date' (or missing) for date only,\n\
130                             `hours', `minutes', or `seconds' for date and\n\
131                             time to the indicated precision.\n\
132 "), stdout);
133       fputs (_("\
134   -r, --reference=FILE      display the last modification time of FILE\n\
135   -R, --rfc-822             output RFC-822 compliant date string\n\
136   -s, --set=STRING          set time described by STRING\n\
137   -u, --utc, --universal    print or set Coordinated Universal Time\n\
138 "), stdout);
139       fputs (HELP_OPTION_DESCRIPTION, stdout);
140       fputs (VERSION_OPTION_DESCRIPTION, stdout);
141       fputs (_("\
142 \n\
143 FORMAT controls the output.  The only valid option for the second form\n\
144 specifies Coordinated Universal Time.  Interpreted sequences are:\n\
145 \n\
146   %%   a literal %\n\
147   %a   locale's abbreviated weekday name (Sun..Sat)\n\
148 "), stdout);
149       fputs (_("\
150   %A   locale's full weekday name, variable length (Sunday..Saturday)\n\
151   %b   locale's abbreviated month name (Jan..Dec)\n\
152   %B   locale's full month name, variable length (January..December)\n\
153   %c   locale's date and time (Sat Nov 04 12:02:33 EST 1989)\n\
154 "), stdout);
155       fputs (_("\
156   %C   century (year divided by 100 and truncated to an integer) [00-99]\n\
157   %d   day of month (01..31)\n\
158   %D   date (mm/dd/yy)\n\
159   %e   day of month, blank padded ( 1..31)\n\
160 "), stdout);
161       fputs (_("\
162   %F   same as %Y-%m-%d\n\
163   %g   the 2-digit year corresponding to the %V week number\n\
164   %G   the 4-digit year corresponding to the %V week number\n\
165 "), stdout);
166       fputs (_("\
167   %h   same as %b\n\
168   %H   hour (00..23)\n\
169   %I   hour (01..12)\n\
170   %j   day of year (001..366)\n\
171 "), stdout);
172       fputs (_("\
173   %k   hour ( 0..23)\n\
174   %l   hour ( 1..12)\n\
175   %m   month (01..12)\n\
176   %M   minute (00..59)\n\
177 "), stdout);
178       fputs (_("\
179   %n   a newline\n\
180   %p   locale's upper case AM or PM indicator\n\
181   %P   locale's lower case am or pm indicator\n\
182   %r   time, 12-hour (hh:mm:ss [AP]M)\n\
183   %R   time, 24-hour (hh:mm)\n\
184   %s   seconds since `00:00:00 1970-01-01 UTC' (a GNU extension)\n\
185 "), stdout);
186       fputs (_("\
187   %S   second (00..60)\n\
188   %t   a horizontal tab\n\
189   %T   time, 24-hour (hh:mm:ss)\n\
190   %u   day of week (1..7);  1 represents Monday\n\
191 "), stdout);
192       fputs (_("\
193   %U   week number of year with Sunday as first day of week (00..53)\n\
194   %V   week number of year with Monday as first day of week (01..53)\n\
195   %w   day of week (0..6);  0 represents Sunday\n\
196   %W   week number of year with Monday as first day of week (00..53)\n\
197 "), stdout);
198       fputs (_("\
199   %x   locale's date representation (mm/dd/yy)\n\
200   %X   locale's time representation (%H:%M:%S)\n\
201   %y   last two digits of year (00..99)\n\
202   %Y   year (1970...)\n\
203 "), stdout);
204       fputs (_("\
205   %z   RFC-822 style numeric timezone (-0500) (a nonstandard extension)\n\
206   %Z   time zone (e.g., EDT), or nothing if no time zone is determinable\n\
207 \n\
208 By default, date pads numeric fields with zeroes.  GNU date recognizes\n\
209 the following modifiers between `%' and a numeric directive.\n\
210 \n\
211   `-' (hyphen) do not pad the field\n\
212   `_' (underscore) pad the field with spaces\n\
213 "), stdout);
214       puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
215     }
216   exit (status);
217 }
218
219 /* Parse each line in INPUT_FILENAME as with --date and display each
220    resulting time and date.  If the file cannot be opened, tell why
221    then exit.  Issue a diagnostic for any lines that cannot be parsed.
222    If any line cannot be parsed, return nonzero;  otherwise return zero.  */
223
224 static int
225 batch_convert (const char *input_filename, const char *format)
226 {
227   int status;
228   FILE *in_stream;
229   char *line;
230   int line_length;
231   size_t buflen;
232   time_t when;
233
234   if (strcmp (input_filename, "-") == 0)
235     {
236       input_filename = _("standard input");
237       in_stream = stdin;
238     }
239   else
240     {
241       in_stream = fopen (input_filename, "r");
242       if (in_stream == NULL)
243         {
244           error (1, errno, "`%s'", input_filename);
245         }
246     }
247
248   line = NULL;
249   buflen = 0;
250   status = 0;
251   while (1)
252     {
253       line_length = getline (&line, &buflen, in_stream);
254       if (line_length < 0)
255         {
256           /* FIXME: detect/handle error here.  */
257           break;
258         }
259
260       when = get_date (line, NULL);
261
262       if (when == -1)
263         {
264           if (line[line_length - 1] == '\n')
265             line[line_length - 1] = '\0';
266           error (0, 0, _("invalid date `%s'"), line);
267           status = 1;
268         }
269       else
270         {
271           show_date (format, when);
272         }
273     }
274
275   if (fclose (in_stream) == EOF)
276     error (2, errno, "`%s'", input_filename);
277
278   if (line != NULL)
279     free (line);
280
281   return status;
282 }
283
284 int
285 main (int argc, char **argv)
286 {
287   int optc;
288   const char *datestr = NULL;
289   const char *set_datestr = NULL;
290   time_t when;
291   int set_date = 0;
292   char *format;
293   char *batch_file = NULL;
294   char *reference = NULL;
295   struct stat refstats;
296   int n_args;
297   int status;
298   int option_specified_date;
299
300   program_name = argv[0];
301   setlocale (LC_ALL, "");
302   bindtextdomain (PACKAGE, LOCALEDIR);
303   textdomain (PACKAGE);
304
305   close_stdout_set_status (2);
306   atexit (close_stdout);
307
308   while ((optc = getopt_long (argc, argv, "d:f:I::r:Rs:u", long_options, NULL))
309          != -1)
310     switch (optc)
311       {
312       case 0:
313         break;
314       case 'd':
315         datestr = optarg;
316         break;
317       case 'f':
318         batch_file = optarg;
319         break;
320       case 'I':
321         iso_8601_format = (optarg
322                            ? XARGMATCH ("--iso-8601", optarg,
323                                         time_spec_string, time_spec)
324                            : TIME_SPEC_DATE);
325         break;
326       case 'r':
327         reference = optarg;
328         break;
329       case 'R':
330         rfc_format = 1;
331         break;
332       case 's':
333         set_datestr = optarg;
334         set_date = 1;
335         break;
336       case 'u':
337         /* POSIX says that `date -u' is equivalent to setting the TZ
338            environment variable, so this option should do nothing other
339            than setting TZ.  */
340         if (putenv ("TZ=UTC0") != 0)
341           xalloc_die ();
342         TZSET;
343         break;
344       case_GETOPT_HELP_CHAR;
345       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
346       default:
347         usage (1);
348       }
349
350   n_args = argc - optind;
351
352   option_specified_date = ((datestr ? 1 : 0)
353                            + (batch_file ? 1 : 0)
354                            + (reference ? 1 : 0));
355
356   if (option_specified_date > 1)
357     {
358       error (0, 0,
359         _("the options to specify dates for printing are mutually exclusive"));
360       usage (1);
361     }
362
363   if (set_date && option_specified_date)
364     {
365       error (0, 0,
366           _("the options to print and set the time may not be used together"));
367       usage (1);
368     }
369
370   if (n_args > 1)
371     {
372       error (0, 0, _("too many non-option arguments: %s%s"),
373              argv[optind + 1], n_args == 2 ? "" : " ...");
374       usage (1);
375     }
376
377   if ((set_date || option_specified_date)
378       && n_args == 1 && argv[optind][0] != '+')
379     {
380       error (0, 0, _("\
381 the argument `%s' lacks a leading `+';\n\
382 When using an option to specify date(s), any non-option\n\
383 argument must be a format string beginning with `+'."),
384              argv[optind]);
385       usage (1);
386     }
387
388   /* Simply ignore --rfc-822 if specified when setting the date.  */
389   if (rfc_format && !set_date && n_args > 0)
390     {
391       error (0, 0,
392              _("a format string may not be specified when using\
393  the --rfc-822 (-R) option"));
394       usage (1);
395     }
396
397   if (set_date)
398     datestr = set_datestr;
399
400   if (batch_file != NULL)
401     {
402       status = batch_convert (batch_file,
403                               (n_args == 1 ? argv[optind] + 1 : NULL));
404     }
405   else
406     {
407       status = 0;
408
409       if (!option_specified_date && !set_date)
410         {
411           if (n_args == 1 && argv[optind][0] != '+')
412             {
413               /* Prepare to set system clock to the specified date/time
414                  given in the POSIX-format.  */
415               set_date = 1;
416               datestr = argv[optind];
417               when = posixtime (datestr,
418                                 PDS_TRAILING_YEAR | PDS_CENTURY | PDS_SECONDS);
419               format = NULL;
420             }
421           else
422             {
423               /* Prepare to print the current date/time.  */
424               datestr = _("undefined");
425               time (&when);
426               format = (n_args == 1 ? argv[optind] + 1 : NULL);
427             }
428         }
429       else
430         {
431           /* (option_specified_date || set_date) */
432           if (reference != NULL)
433             {
434               if (stat (reference, &refstats))
435                 error (1, errno, "%s", reference);
436               when = refstats.st_mtime;
437             }
438           else
439             {
440               when = get_date (datestr, NULL);
441             }
442
443           format = (n_args == 1 ? argv[optind] + 1 : NULL);
444         }
445
446       if (when == -1)
447         error (1, 0, _("invalid date `%s'"), datestr);
448
449       if (set_date)
450         {
451           /* Set the system clock to the specified date, then regardless of
452              the success of that operation, format and print that date.  */
453           if (stime (&when) == -1)
454             {
455               error (0, errno, _("cannot set date"));
456               status = 1;
457             }
458         }
459
460       show_date (format, when);
461     }
462
463   exit (status);
464 }
465
466 /* Display the date and/or time in WHEN according to the format specified
467    in FORMAT, followed by a newline.  If FORMAT is NULL, use the
468    standard output format (ctime style but with a timezone inserted). */
469
470 static void
471 show_date (const char *format, time_t when)
472 {
473   struct tm *tm;
474   char *out = NULL;
475   size_t out_length = 0;
476   /* ISO 8601 formats.  See below regarding %z */
477   static char const * const iso_format_string[] =
478   {
479     "%Y-%m-%d",
480     "%Y-%m-%dT%H%z",
481     "%Y-%m-%dT%H:%M%z",
482     "%Y-%m-%dT%H:%M:%S%z"
483   };
484
485   tm = localtime (&when);
486
487   if (format == NULL)
488     {
489       /* Print the date in the default format.  Vanilla ANSI C strftime
490          doesn't support %e, but POSIX requires it.  If you don't use
491          a GNU strftime, make sure yours supports %e.
492          If you are not using GNU strftime, you want to change %z
493          in the RFC format to %Z; this gives, however, an invalid
494          RFC time format outside the continental United States and GMT. */
495
496       if (rfc_format)
497         format = "%a, %d %b %Y %H:%M:%S %z";
498       else if (iso_8601_format)
499         format = iso_format_string[iso_8601_format - 1];
500       else
501         {
502           char *date_fmt = DATE_FMT_LANGINFO ();
503           /* Do not wrap the following literal format string with _(...).
504              For example, suppose LC_ALL is unset, LC_TIME="POSIX",
505              and LANG="ko_KR".  In that case, POSIX says that LC_TIME
506              determines the format and contents of date and time strings
507              written by date, which means "date" must generate output
508              using the POSIX locale; but adding _() would cause "date"
509              to use a Korean translation of the format.  */
510           format = *date_fmt ? date_fmt : "%a %b %e %H:%M:%S %Z %Y";
511         }
512     }
513   else if (*format == '\0')
514     {
515       printf ("\n");
516       return;
517     }
518
519   while (1)
520     {
521       int done;
522       out_length += 200;
523       out = (char *) xrealloc (out, out_length);
524
525       /* Mark the first byte of the buffer so we can detect the case
526          of strftime producing an empty string.  Otherwise, this loop
527          would not terminate when date was invoked like this
528          `LANG=de date +%p' on a system with good language support.  */
529       out[0] = '\1';
530
531       if (rfc_format)
532         setlocale (LC_ALL, "C");
533
534       done = (strftime (out, out_length, format, tm) || out[0] == '\0');
535
536       if (rfc_format)
537         setlocale (LC_ALL, "");
538
539       if (done)
540         break;
541     }
542
543   printf ("%s\n", out);
544   free (out);
545 }