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