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