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