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