Comment fixes to bring us up to date with respect to POSIX 1003.1-2001.
[platform/upstream/coreutils.git] / src / date.c
1 /* date - print or set the system date and time
2    Copyright (C) 1989-2001 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   %h   same as %b\n\
163   %H   hour (00..23)\n\
164   %I   hour (01..12)\n\
165   %j   day of year (001..366)\n\
166 "), stdout);
167       fputs (_("\
168   %k   hour ( 0..23)\n\
169   %l   hour ( 1..12)\n\
170   %m   month (01..12)\n\
171   %M   minute (00..59)\n\
172 "), stdout);
173       fputs (_("\
174   %n   a newline\n\
175   %p   locale's AM or PM\n\
176   %r   time, 12-hour (hh:mm:ss [AP]M)\n\
177   %s   seconds since `00:00:00 1970-01-01 UTC' (a GNU extension)\n\
178 "), stdout);
179       fputs (_("\
180   %S   second (00..60)\n\
181   %t   a horizontal tab\n\
182   %T   time, 24-hour (hh:mm:ss)\n\
183   %u   day of week (1..7);  1 represents Monday\n\
184 "), stdout);
185       fputs (_("\
186   %U   week number of year with Sunday as first day of week (00..53)\n\
187   %V   week number of year with Monday as first day of week (01..53)\n\
188   %w   day of week (0..6);  0 represents Sunday\n\
189   %W   week number of year with Monday as first day of week (00..53)\n\
190 "), stdout);
191       fputs (_("\
192   %x   locale's date representation (mm/dd/yy)\n\
193   %X   locale's time representation (%H:%M:%S)\n\
194   %y   last two digits of year (00..99)\n\
195   %Y   year (1970...)\n\
196 "), stdout);
197       fputs (_("\
198   %z   RFC-822 style numeric timezone (-0500) (a nonstandard extension)\n\
199   %Z   time zone (e.g., EDT), or nothing if no time zone is determinable\n\
200 \n\
201 By default, date pads numeric fields with zeroes.  GNU date recognizes\n\
202 the following modifiers between `%' and a numeric directive.\n\
203 \n\
204   `-' (hyphen) do not pad the field\n\
205   `_' (underscore) pad the field with spaces\n\
206 "), stdout);
207       puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
208     }
209   exit (status);
210 }
211
212 /* Parse each line in INPUT_FILENAME as with --date and display each
213    resulting time and date.  If the file cannot be opened, tell why
214    then exit.  Issue a diagnostic for any lines that cannot be parsed.
215    If any line cannot be parsed, return nonzero;  otherwise return zero.  */
216
217 static int
218 batch_convert (const char *input_filename, const char *format)
219 {
220   int status;
221   FILE *in_stream;
222   char *line;
223   int line_length;
224   size_t buflen;
225   time_t when;
226
227   if (strcmp (input_filename, "-") == 0)
228     {
229       input_filename = _("standard input");
230       in_stream = stdin;
231     }
232   else
233     {
234       in_stream = fopen (input_filename, "r");
235       if (in_stream == NULL)
236         {
237           error (1, errno, "`%s'", input_filename);
238         }
239     }
240
241   line = NULL;
242   buflen = 0;
243   status = 0;
244   while (1)
245     {
246       line_length = getline (&line, &buflen, in_stream);
247       if (line_length < 0)
248         {
249           /* FIXME: detect/handle error here.  */
250           break;
251         }
252
253       when = get_date (line, NULL);
254
255       if (when == -1)
256         {
257           if (line[line_length - 1] == '\n')
258             line[line_length - 1] = '\0';
259           error (0, 0, _("invalid date `%s'"), line);
260           status = 1;
261         }
262       else
263         {
264           show_date (format, when);
265         }
266     }
267
268   if (fclose (in_stream) == EOF)
269     error (2, errno, "`%s'", input_filename);
270
271   if (line != NULL)
272     free (line);
273
274   return status;
275 }
276
277 int
278 main (int argc, char **argv)
279 {
280   int optc;
281   const char *datestr = NULL;
282   const char *set_datestr = NULL;
283   time_t when;
284   int set_date = 0;
285   char *format;
286   char *batch_file = NULL;
287   char *reference = NULL;
288   struct stat refstats;
289   int n_args;
290   int status;
291   int option_specified_date;
292
293   program_name = argv[0];
294   setlocale (LC_ALL, "");
295   bindtextdomain (PACKAGE, LOCALEDIR);
296   textdomain (PACKAGE);
297
298   close_stdout_set_status (2);
299   atexit (close_stdout);
300
301   while ((optc = getopt_long (argc, argv, "d:f:I::r:Rs:u", long_options, NULL))
302          != -1)
303     switch (optc)
304       {
305       case 0:
306         break;
307       case 'd':
308         datestr = optarg;
309         break;
310       case 'f':
311         batch_file = optarg;
312         break;
313       case 'I':
314         iso_8601_format = (optarg
315                            ? XARGMATCH ("--iso-8601", optarg,
316                                         time_spec_string, time_spec)
317                            : TIME_SPEC_DATE);
318         break;
319       case 'r':
320         reference = optarg;
321         break;
322       case 'R':
323         rfc_format = 1;
324         break;
325       case 's':
326         set_datestr = optarg;
327         set_date = 1;
328         break;
329       case 'u':
330         /* POSIX says that `date -u' is equivalent to setting the TZ
331            environment variable, so this option should do nothing other
332            than setting TZ.  */
333         if (putenv ("TZ=UTC0") != 0)
334           xalloc_die ();
335         TZSET;
336         break;
337       case_GETOPT_HELP_CHAR;
338       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
339       default:
340         usage (1);
341       }
342
343   n_args = argc - optind;
344
345   option_specified_date = ((datestr ? 1 : 0)
346                            + (batch_file ? 1 : 0)
347                            + (reference ? 1 : 0));
348
349   if (option_specified_date > 1)
350     {
351       error (0, 0,
352         _("the options to specify dates for printing are mutually exclusive"));
353       usage (1);
354     }
355
356   if (set_date && option_specified_date)
357     {
358       error (0, 0,
359           _("the options to print and set the time may not be used together"));
360       usage (1);
361     }
362
363   if (n_args > 1)
364     {
365       error (0, 0, _("too many non-option arguments: %s%s"),
366              argv[optind + 1], n_args == 2 ? "" : " ...");
367       usage (1);
368     }
369
370   if ((set_date || option_specified_date)
371       && n_args == 1 && argv[optind][0] != '+')
372     {
373       error (0, 0, _("\
374 the argument `%s' lacks a leading `+';\n\
375 When using an option to specify date(s), any non-option\n\
376 argument must be a format string beginning with `+'."),
377              argv[optind]);
378       usage (1);
379     }
380
381   /* Simply ignore --rfc-822 if specified when setting the date.  */
382   if (rfc_format && !set_date && n_args > 0)
383     {
384       error (0, 0,
385              _("a format string may not be specified when using\
386  the --rfc-822 (-R) option"));
387       usage (1);
388     }
389
390   if (set_date)
391     datestr = set_datestr;
392
393   if (batch_file != NULL)
394     {
395       status = batch_convert (batch_file,
396                               (n_args == 1 ? argv[optind] + 1 : NULL));
397     }
398   else
399     {
400       status = 0;
401
402       if (!option_specified_date && !set_date)
403         {
404           if (n_args == 1 && argv[optind][0] != '+')
405             {
406               /* Prepare to set system clock to the specified date/time
407                  given in the POSIX-format.  */
408               set_date = 1;
409               datestr = argv[optind];
410               when = posixtime (datestr,
411                                 PDS_TRAILING_YEAR | PDS_CENTURY | PDS_SECONDS);
412               format = NULL;
413             }
414           else
415             {
416               /* Prepare to print the current date/time.  */
417               datestr = _("undefined");
418               time (&when);
419               format = (n_args == 1 ? argv[optind] + 1 : NULL);
420             }
421         }
422       else
423         {
424           /* (option_specified_date || set_date) */
425           if (reference != NULL)
426             {
427               if (stat (reference, &refstats))
428                 error (1, errno, "%s", reference);
429               when = refstats.st_mtime;
430             }
431           else
432             {
433               when = get_date (datestr, NULL);
434             }
435
436           format = (n_args == 1 ? argv[optind] + 1 : NULL);
437         }
438
439       if (when == -1)
440         error (1, 0, _("invalid date `%s'"), datestr);
441
442       if (set_date)
443         {
444           /* Set the system clock to the specified date, then regardless of
445              the success of that operation, format and print that date.  */
446           if (stime (&when) == -1)
447             {
448               error (0, errno, _("cannot set date"));
449               status = 1;
450             }
451         }
452
453       show_date (format, when);
454     }
455
456   exit (status);
457 }
458
459 /* Display the date and/or time in WHEN according to the format specified
460    in FORMAT, followed by a newline.  If FORMAT is NULL, use the
461    standard output format (ctime style but with a timezone inserted). */
462
463 static void
464 show_date (const char *format, time_t when)
465 {
466   struct tm *tm;
467   char *out = NULL;
468   size_t out_length = 0;
469   /* ISO 8601 formats.  See below regarding %z */
470   static char const * const iso_format_string[] =
471   {
472     "%Y-%m-%d",
473     "%Y-%m-%dT%H%z",
474     "%Y-%m-%dT%H:%M%z",
475     "%Y-%m-%dT%H:%M:%S%z"
476   };
477
478   tm = localtime (&when);
479
480   if (format == NULL)
481     {
482       /* Print the date in the default format.  Vanilla ANSI C strftime
483          doesn't support %e, but POSIX requires it.  If you don't use
484          a GNU strftime, make sure yours supports %e.
485          If you are not using GNU strftime, you want to change %z
486          in the RFC format to %Z; this gives, however, an invalid
487          RFC time format outside the continental United States and GMT. */
488
489       if (rfc_format)
490         format = "%a, %d %b %Y %H:%M:%S %z";
491       else if (iso_8601_format)
492         format = iso_format_string[iso_8601_format - 1];
493       else
494         {
495           char *date_fmt = DATE_FMT_LANGINFO ();
496           /* Do not wrap the following literal format string with _(...).
497              For example, suppose LC_ALL is unset, LC_TIME="POSIX",
498              and LANG="ko_KR".  In that case, POSIX says that LC_TIME
499              determines the format and contents of date and time strings
500              written by date, which means "date" must generate output
501              using the POSIX locale; but adding _() would cause "date"
502              to use a Korean translation of the format.  */
503           format = *date_fmt ? date_fmt : "%a %b %e %H:%M:%S %Z %Y";
504         }
505     }
506   else if (*format == '\0')
507     {
508       printf ("\n");
509       return;
510     }
511
512   while (1)
513     {
514       int done;
515       out_length += 200;
516       out = (char *) xrealloc (out, out_length);
517
518       /* Mark the first byte of the buffer so we can detect the case
519          of strftime producing an empty string.  Otherwise, this loop
520          would not terminate when date was invoked like this
521          `LANG=de date +%p' on a system with good language support.  */
522       out[0] = '\1';
523
524       if (rfc_format)
525         setlocale (LC_ALL, "C");
526
527       done = (strftime (out, out_length, format, tm) || out[0] == '\0');
528
529       if (rfc_format)
530         setlocale (LC_ALL, "");
531
532       if (done)
533         break;
534     }
535
536   printf ("%s\n", out);
537   free (out);
538 }