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