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