(batch_convert): Use IF_LINT macro instead of #ifdef lint.
[platform/upstream/coreutils.git] / src / date.c
1 /* date - print or set the system date and time
2    Copyright (C) 1989-1999 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
25 #include "system.h"
26 #include "argmatch.h"
27 #include "closeout.h"
28 #include "error.h"
29 #include "getdate.h"
30 #include "getline.h"
31 #include "posixtm.h"
32
33 /* The official name of this program (e.g., no `g' prefix).  */
34 #define PROGRAM_NAME "date"
35
36 #define AUTHORS "David MacKenzie"
37
38 #ifndef STDC_HEADERS
39 size_t strftime ();
40 time_t time ();
41 #endif
42
43 int putenv ();
44 int stime ();
45
46 char *xstrdup ();
47
48 static void show_date PARAMS ((const char *format, time_t when));
49
50 enum Time_spec
51 {
52   /* display only the date: 1999-03-25 */
53   TIME_SPEC_DATE=1,
54   /* display date and hour: 1999-03-25T03-0500 */
55   TIME_SPEC_HOURS,
56   /* display date, hours, and minutes: 1999-03-25T03:23-0500 */
57   TIME_SPEC_MINUTES,
58   /* display date, hours, minutes, and seconds: 1999-03-25T03:23:14-0500 */
59   TIME_SPEC_SECONDS
60 };
61
62 static char const *const time_spec_string[] =
63 {
64   "date", "hours", "minutes", "seconds", 0
65 };
66
67 static enum Time_spec const time_spec[] =
68 {
69   TIME_SPEC_DATE, TIME_SPEC_HOURS, TIME_SPEC_MINUTES, TIME_SPEC_SECONDS
70 };
71
72 /* The name this program was run with, for error messages. */
73 char *program_name;
74
75 /* If nonzero, display an ISO 8601 format date/time string */
76 static int iso_8601_format = 0;
77
78 /* If non-zero, display time in RFC-822 format for mail or news. */
79 static int rfc_format = 0;
80
81 /* If nonzero, print or set Coordinated Universal Time.  */
82 static int universal_time = 0;
83
84 static struct option const long_options[] =
85 {
86   {"date", required_argument, NULL, 'd'},
87   {"file", required_argument, NULL, 'f'},
88   {"iso-8601", optional_argument, NULL, 'I'},
89   {"reference", required_argument, NULL, 'r'},
90   {"rfc-822", no_argument, NULL, 'R'},
91   {"set", required_argument, NULL, 's'},
92   {"uct", no_argument, NULL, 'u'},
93   {"utc", no_argument, NULL, 'u'},
94   {"universal", no_argument, NULL, 'u'},
95   {GETOPT_HELP_OPTION_DECL},
96   {GETOPT_VERSION_OPTION_DECL},
97   {NULL, 0, NULL, 0}
98 };
99
100 #define TZ_UTC0 "TZ=UTC0"
101
102 #if LOCALTIME_CACHE
103 # define TZSET tzset ()
104 #else
105 # define TZSET /* empty */
106 #endif
107
108 #define MAYBE_SET_TZ_UTC0 \
109   do { if (universal_time) set_tz (TZ_UTC0); } while (0)
110
111 void
112 usage (int status)
113 {
114   if (status != 0)
115     fprintf (stderr, _("Try `%s --help' for more information.\n"),
116              program_name);
117   else
118     {
119       printf (_("\
120 Usage: %s [OPTION]... [+FORMAT]\n\
121   or:  %s [OPTION] [MMDDhhmm[[CC]YY][.ss]]\n\
122 "),
123               program_name, program_name);
124       printf (_("\
125 Display the current time in the given FORMAT, or set the system date.\n\
126 \n\
127   -d, --date=STRING         display time described by STRING, not `now'\n\
128   -f, --file=DATEFILE       like --date once for each line of DATEFILE\n\
129   -I, --iso-8601[=TIMESPEC] output an ISO-8601 compliant date/time string.\n\
130                             TIMESPEC=`date' (or missing) for date only,\n\
131                             `hours', `minutes', or `seconds' for date and\n\
132                             time to the indicated precision.\n\
133   -r, --reference=FILE      display the last modification time of FILE\n\
134   -R, --rfc-822             output RFC-822 compliant date string\n\
135   -s, --set=STRING          set time described by STRING\n\
136   -u, --utc, --universal    print or set Coordinated Universal Time\n\
137       --help                display this help and exit\n\
138       --version             output version information and exit\n\
139 "));
140       printf (_("\
141 \n\
142 FORMAT controls the output.  The only valid option for the second form\n\
143 specifies Coordinated Universal Time.  Interpreted sequences are:\n\
144 \n\
145   %%%%   a literal %%\n\
146   %%a   locale's abbreviated weekday name (Sun..Sat)\n\
147   %%A   locale's full weekday name, variable length (Sunday..Saturday)\n\
148   %%b   locale's abbreviated month name (Jan..Dec)\n\
149   %%B   locale's full month name, variable length (January..December)\n\
150   %%c   locale's date and time (Sat Nov 04 12:02:33 EST 1989)\n\
151   %%d   day of month (01..31)\n\
152   %%D   date (mm/dd/yy)\n\
153   %%e   day of month, blank padded ( 1..31)\n\
154   %%h   same as %%b\n\
155   %%H   hour (00..23)\n\
156   %%I   hour (01..12)\n\
157   %%j   day of year (001..366)\n\
158   %%k   hour ( 0..23)\n\
159   %%l   hour ( 1..12)\n\
160   %%m   month (01..12)\n\
161   %%M   minute (00..59)\n\
162   %%n   a newline\n\
163   %%p   locale's AM or PM\n\
164   %%r   time, 12-hour (hh:mm:ss [AP]M)\n\
165   %%s   seconds since 00:00:00, Jan 1, 1970 (a GNU extension)\n\
166   %%S   second (00..60)\n\
167   %%t   a horizontal tab\n\
168   %%T   time, 24-hour (hh:mm:ss)\n\
169   %%U   week number of year with Sunday as first day of week (00..53)\n\
170   %%V   week number of year with Monday as first day of week (01..52)\n\
171   %%w   day of week (0..6);  0 represents Sunday\n\
172   %%W   week number of year with Monday as first day of week (00..53)\n\
173   %%x   locale's date representation (mm/dd/yy)\n\
174   %%X   locale's time representation (%%H:%%M:%%S)\n\
175   %%y   last two digits of year (00..99)\n\
176   %%Y   year (1970...)\n\
177   %%z   RFC-822 style numeric timezone (-0500) (a nonstandard extension)\n\
178   %%Z   time zone (e.g., EDT), or nothing if no time zone is determinable\n\
179 \n\
180 By default, date pads numeric fields with zeroes.  GNU date recognizes\n\
181 the following modifiers between `%%' and a numeric directive.\n\
182 \n\
183   `-' (hyphen) do not pad the field\n\
184   `_' (underscore) pad the field with spaces\n\
185 "));
186       puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
187     }
188   exit (status);
189 }
190
191 /* Set the TZ environment variable.  */
192
193 static void
194 set_tz (const char *tz_eq_zone)
195 {
196   if (putenv (tz_eq_zone) != 0)
197     error (1, 0, "memory exhausted");
198   TZSET;
199 }
200
201 /* Parse each line in INPUT_FILENAME as with --date and display each
202    resulting time and date.  If the file cannot be opened, tell why
203    then exit.  Issue a diagnostic for any lines that cannot be parsed.
204    If any line cannot be parsed, return nonzero;  otherwise return zero.  */
205
206 static int
207 batch_convert (const char *input_filename, const char *format)
208 {
209   int status;
210   FILE *in_stream;
211   char *line;
212   int line_length;
213   size_t buflen;
214   time_t when;
215   char *initial_TZ IF_LINT (= NULL);
216
217   if (strcmp (input_filename, "-") == 0)
218     {
219       input_filename = _("standard input");
220       in_stream = stdin;
221     }
222   else
223     {
224       in_stream = fopen (input_filename, "r");
225       if (in_stream == NULL)
226         {
227           error (1, errno, "`%s'", input_filename);
228         }
229     }
230
231   line = NULL;
232   buflen = 0;
233
234   if (universal_time)
235     {
236       initial_TZ = getenv ("TZ");
237       if (initial_TZ == NULL)
238         {
239           initial_TZ = xstrdup ("TZ=");
240         }
241       else
242         {
243           size_t tz_len = strlen (initial_TZ);
244           char *buf = xmalloc (3 + tz_len + 1);
245           memcpy (mempcpy (buf, "TZ=", 3), initial_TZ, tz_len + 1);
246           initial_TZ = buf;
247         }
248     }
249
250   status = 0;
251   while (1)
252     {
253       line_length = getline (&line, &buflen, in_stream);
254       if (line_length < 0)
255         {
256           /* FIXME: detect/handle error here.  */
257           break;
258         }
259
260       if (universal_time)
261         {
262           /* When given a universal time option, restore the initial
263              value of TZ before parsing each string.  */
264           set_tz (initial_TZ);
265         }
266
267       when = get_date (line, NULL);
268
269       if (when == -1)
270         {
271           if (line[line_length - 1] == '\n')
272             line[line_length - 1] = '\0';
273           error (0, 0, _("invalid date `%s'"), line);
274           status = 1;
275         }
276       else
277         {
278           MAYBE_SET_TZ_UTC0;
279           show_date (format, when);
280         }
281     }
282
283   free (initial_TZ);
284
285   if (fclose (in_stream) == EOF)
286     error (2, errno, "`%s'", input_filename);
287
288   if (line != NULL)
289     free (line);
290
291   return status;
292 }
293
294 int
295 main (int argc, char **argv)
296 {
297   int optc;
298   const char *datestr = NULL;
299   const char *set_datestr = NULL;
300   time_t when;
301   int set_date = 0;
302   char *format;
303   char *batch_file = NULL;
304   char *reference = NULL;
305   struct stat refstats;
306   int n_args;
307   int status;
308   int option_specified_date;
309
310   program_name = argv[0];
311   setlocale (LC_ALL, "");
312   bindtextdomain (PACKAGE, LOCALEDIR);
313   textdomain (PACKAGE);
314
315   while ((optc = getopt_long (argc, argv, "d:f:I::r:Rs:u", 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         universal_time = 1;
345         break;
346       case_GETOPT_HELP_CHAR;
347       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
348       default:
349         usage (1);
350       }
351
352   n_args = argc - optind;
353
354   option_specified_date = ((datestr ? 1 : 0)
355                            + (batch_file ? 1 : 0)
356                            + (reference ? 1 : 0));
357
358   if (option_specified_date > 1)
359     {
360       error (0, 0,
361         _("the options to specify dates for printing are mutually exclusive"));
362       usage (1);
363     }
364
365   if (set_date && option_specified_date)
366     {
367       error (0, 0,
368           _("the options to print and set the time may not be used together"));
369       usage (1);
370     }
371
372   if (n_args > 1)
373     {
374       error (0, 0, _("too many non-option arguments"));
375       usage (1);
376     }
377
378   if ((set_date || option_specified_date)
379       && n_args == 1 && argv[optind][0] != '+')
380     {
381       error (0, 0, _("\
382 the argument `%s' lacks a leading `+';\n\
383 When using an option to specify date(s), any non-option\n\
384 argument must be a format string beginning with `+'."),
385              argv[optind]);
386       usage (1);
387     }
388
389   if (set_date)
390     datestr = set_datestr;
391
392   if (batch_file != NULL)
393     {
394       status = batch_convert (batch_file,
395                               (n_args == 1 ? argv[optind] + 1 : NULL));
396     }
397   else
398     {
399       status = 0;
400
401       if (!option_specified_date && !set_date)
402         {
403           if (n_args == 1 && argv[optind][0] != '+')
404             {
405               /* Prepare to set system clock to the specified date/time
406                  given in the POSIX-format.  */
407               set_date = 1;
408               datestr = argv[optind];
409               when = posixtime (datestr,
410                                 PDS_TRAILING_YEAR | PDS_CENTURY | PDS_SECONDS);
411               format = NULL;
412             }
413           else
414             {
415               /* Prepare to print the current date/time.  */
416               datestr = _("undefined");
417               time (&when);
418               format = (n_args == 1 ? argv[optind] + 1 : NULL);
419             }
420         }
421       else
422         {
423           /* (option_specified_date || set_date) */
424           if (reference != NULL)
425             {
426               if (stat (reference, &refstats))
427                 error (1, errno, "%s", reference);
428               when = refstats.st_mtime;
429             }
430           else
431             {
432               when = get_date (datestr, NULL);
433             }
434
435           format = (n_args == 1 ? argv[optind] + 1 : NULL);
436         }
437
438       if (when == -1)
439         error (1, 0, _("invalid date `%s'"), datestr);
440
441       if (set_date)
442         {
443           /* Set the system clock to the specified date, then regardless of
444              the success of that operation, format and print that date.  */
445           if (stime (&when) == -1)
446             {
447               error (0, errno, _("cannot set date"));
448               status = 1;
449             }
450         }
451
452       /* When given a universal time option, set TZ to UTC0 after
453          parsing the specified date, but before printing it.  */
454       MAYBE_SET_TZ_UTC0;
455
456       show_date (format, when);
457     }
458
459   close_stdout_status (2);
460
461   exit (status);
462 }
463
464 /* Display the date and/or time in WHEN according to the format specified
465    in FORMAT, followed by a newline.  If FORMAT is NULL, use the
466    standard output format (ctime style but with a timezone inserted). */
467
468 static void
469 show_date (const char *format, time_t when)
470 {
471   struct tm *tm;
472   char *out = NULL;
473   size_t out_length = 0;
474   /* ISO 8601 formats, in local and UTC.  See below regarding %z */
475   static char *iso_format_string[5][2] =
476   {
477     {"", ""},
478     {"%Y-%m-%d", "%Y-%m-%d"},
479     {"%Y-%m-%dT%H%z", "%Y-%m-%dT%HZ"},
480     {"%Y-%m-%dT%H:%M%z", "%Y-%m-%dT%H:%MZ"},
481     {"%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%dT%H:%M:%SZ"}
482   };
483
484   tm = localtime (&when);
485
486   if (format == NULL)
487     {
488       /* Print the date in the default format.  Vanilla ANSI C strftime
489          doesn't support %e, but POSIX requires it.  If you don't use
490          a GNU strftime, make sure yours supports %e.
491          If you are not using GNU strftime, you want to change %z
492          in the RFC format to %Z; this gives, however, an invalid
493          RFC time format outside the continental United States and GMT. */
494
495       format = (rfc_format
496                 ? (universal_time
497                    ? "%a, %_d %b %Y %H:%M:%S GMT"
498                    : "%a, %_d %b %Y %H:%M:%S %z")
499                 : (iso_8601_format
500                    ? iso_format_string[iso_8601_format][universal_time]
501                    : "%a %b %e %H:%M:%S %Z %Y"));
502     }
503   else if (*format == '\0')
504     {
505       printf ("\n");
506       return;
507     }
508
509   do
510     {
511       out_length += 200;
512       out = (char *) xrealloc (out, out_length);
513
514       /* Mark the first byte of the buffer so we can detect the case
515          of strftime producing an empty string.  Otherwise, this loop
516          would not terminate when date was invoked like this
517          `LANG=de date +%p' on a system with good language support.  */
518       out[0] = '\1';
519     }
520   while (strftime (out, out_length, format, tm) == 0 && out[0] != '\0');
521
522   printf ("%s\n", out);
523   free (out);
524 }