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