whoops: use puts, not fputs
[platform/upstream/coreutils.git] / src / date.c
1 /* date - print or set the system date and time
2    Copyright (C) 1989-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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_CODESET
25 # include <langinfo.h>
26 #endif
27
28 #include "system.h"
29 #include "argmatch.h"
30 #include "error.h"
31 #include "getdate.h"
32 #include "getline.h"
33 #include "inttostr.h"
34 #include "posixtm.h"
35 #include "quote.h"
36 #include "strftime.h"
37
38 /* The official name of this program (e.g., no `g' prefix).  */
39 #define PROGRAM_NAME "date"
40
41 #define AUTHORS "David MacKenzie"
42
43 int putenv ();
44
45 static bool show_date (const char *format, struct timespec when);
46
47 enum Time_spec
48 {
49   /* display only the date: 1999-03-25 */
50   TIME_SPEC_DATE=1,
51   /* display date and hour: 1999-03-25T03-0500 */
52   TIME_SPEC_HOURS,
53   /* display date, hours, and minutes: 1999-03-25T03:23-0500 */
54   TIME_SPEC_MINUTES,
55   /* display date, hours, minutes, and seconds: 1999-03-25T03:23:14-0500 */
56   TIME_SPEC_SECONDS,
57   /* similar, but display nanoseconds: 1999-03-25T03:23:14,123456789-0500 */
58   TIME_SPEC_NS
59 };
60
61 static char const *const time_spec_string[] =
62 {
63   "date", "hours", "minutes", "seconds", "ns", NULL
64 };
65
66 static enum Time_spec const time_spec[] =
67 {
68   TIME_SPEC_DATE, TIME_SPEC_HOURS, TIME_SPEC_MINUTES, TIME_SPEC_SECONDS,
69   TIME_SPEC_NS
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 true, display time in RFC-(2)822 format for mail or news. */
79 static bool rfc_format = false;
80
81 static char const short_options[] = "d:f:I::r:Rs:u";
82
83 static struct option const long_options[] =
84 {
85   {"date", required_argument, NULL, 'd'},
86   {"file", required_argument, NULL, 'f'},
87   {"iso-8601", optional_argument, NULL, 'I'},
88   {"reference", required_argument, NULL, 'r'},
89   {"rfc-822", no_argument, NULL, 'R'},
90   {"rfc-2822", 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 #if LOCALTIME_CACHE
101 # define TZSET tzset ()
102 #else
103 # define TZSET /* empty */
104 #endif
105
106 #ifdef _DATE_FMT
107 # define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT)
108 #else
109 # define DATE_FMT_LANGINFO() ""
110 #endif
111
112 void
113 usage (int status)
114 {
115   if (status != EXIT_SUCCESS)
116     fprintf (stderr, _("Try `%s --help' for more information.\n"),
117              program_name);
118   else
119     {
120       printf (_("\
121 Usage: %s [OPTION]... [+FORMAT]\n\
122   or:  %s [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]\n\
123 "),
124               program_name, program_name);
125       fputs (_("\
126 Display the current time in the given FORMAT, or set the system date.\n\
127 \n\
128   -d, --date=STRING         display time described by STRING, not `now'\n\
129   -f, --file=DATEFILE       like --date once for each line of DATEFILE\n\
130   -I[TIMESPEC], --iso-8601[=TIMESPEC]  output date/time in ISO 8601 format.\n\
131                             TIMESPEC=`date' for date only (the default),\n\
132                             `hours', `minutes', `seconds', or `ns' for date and\n\
133                             time to the indicated precision.\n\
134 "), stdout);
135       fputs (_("\
136   -r, --reference=FILE      display the last modification time of FILE\n\
137   -R, --rfc-2822            output RFC-2822 compliant date string\n\
138   -s, --set=STRING          set time described by STRING\n\
139   -u, --utc, --universal    print or set Coordinated Universal Time\n\
140 "), stdout);
141       fputs (HELP_OPTION_DESCRIPTION, stdout);
142       fputs (VERSION_OPTION_DESCRIPTION, stdout);
143       fputs (_("\
144 \n\
145 FORMAT controls the output.  The only valid option for the second form\n\
146 specifies Coordinated Universal Time.  Interpreted sequences are:\n\
147 \n\
148   %%   a literal %\n\
149   %a   locale's abbreviated weekday name (e.g., Sun)\n\
150 "), stdout);
151       fputs (_("\
152   %A   locale's full weekday name (e.g., Sunday)\n\
153   %b   locale's abbreviated month name (e.g., Jan)\n\
154   %B   locale's full month name (e.g., January)\n\
155   %c   locale's date and time (e.g., Thu Mar  3 23:05:25 2005)\n\
156 "), stdout);
157       fputs (_("\
158   %C   century; like %Y, except omit last two digits (e.g., 21)\n\
159   %d   day of month (e.g, 01)\n\
160   %D   date; same as %m/%d/%y\n\
161   %e   day of month, space padded; same as %_d\n\
162 "), stdout);
163       fputs (_("\
164   %F   full date; same as %Y-%m-%d\n\
165   %g   the last two digits of the year corresponding to the %V week number\n\
166   %G   the year corresponding to the %V week number\n\
167 "), stdout);
168       fputs (_("\
169   %h   same as %b\n\
170   %H   hour (00..23)\n\
171   %I   hour (01..12)\n\
172   %j   day of year (001..366)\n\
173 "), stdout);
174       fputs (_("\
175   %k   hour ( 0..23)\n\
176   %l   hour ( 1..12)\n\
177   %m   month (01..12)\n\
178   %M   minute (00..59)\n\
179 "), stdout);
180       fputs (_("\
181   %n   a newline\n\
182   %N   nanoseconds (000000000..999999999)\n\
183   %p   locale's equivalent of either AM or PM; blank if not known\n\
184   %P   like %p, but lower case\n\
185   %r   locale's 12-hour clock time (e.g., 11:11:04 PM)\n\
186   %R   24-hour hour and minute; same as %H:%M\n\
187   %s   seconds since 1970-01-01 00:00:00 UTC\n\
188 "), stdout);
189       fputs (_("\
190   %S   second (00..60)\n\
191   %t   a tab\n\
192   %T   time; same as %H:%M:%S\n\
193   %u   day of week (1..7); 1 is Monday\n\
194 "), stdout);
195       fputs (_("\
196   %U   week number of year with Sunday as first day of week (00..53)\n\
197   %V   week number of year with Monday as first day of week (01..53)\n\
198   %w   day of week (0..6); 0 is Sunday\n\
199   %W   week number of year with Monday as first day of week (00..53)\n\
200 "), stdout);
201       fputs (_("\
202   %x   locale's date representation (e.g., 12/31/99)\n\
203   %X   locale's time representation (e.g., 23:13:48)\n\
204   %y   last two digits of year (00..99)\n\
205   %Y   year\n\
206 "), stdout);
207       fputs (_("\
208   %z   numeric timezone (e.g., -0400)\n\
209   %Z   alphabetic time zone abbreviation (e.g., EDT)\n\
210 \n\
211 By default, date pads numeric fields with zeroes.\n\
212 The following optional flags may follow `%':\n\
213 \n\
214   - (hyphen) do not pad the field\n\
215   _ (underscore) pad with spaces\n\
216   0 (zero) pad with zeros\n\
217   ^ use upper case if possible\n\
218   # use opposite case if possible\n\
219 "), stdout);
220       fputs (_("\
221 \n\
222 After any flags comes an optional field width, as a decimal number;\n\
223 then an optional modifier, which is either\n\
224 E to use the locale's alternate representations if available, or\n\
225 O to use the locale's alternate numeric symbols if available.\n\
226 "), stdout);
227       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
228     }
229   exit (status);
230 }
231
232 /* Parse each line in INPUT_FILENAME as with --date and display each
233    resulting time and date.  If the file cannot be opened, tell why
234    then exit.  Issue a diagnostic for any lines that cannot be parsed.
235    Return true if successful.  */
236
237 static bool
238 batch_convert (const char *input_filename, const char *format)
239 {
240   bool ok;
241   FILE *in_stream;
242   char *line;
243   size_t buflen;
244   struct timespec when;
245
246   if (STREQ (input_filename, "-"))
247     {
248       input_filename = _("standard input");
249       in_stream = stdin;
250     }
251   else
252     {
253       in_stream = fopen (input_filename, "r");
254       if (in_stream == NULL)
255         {
256           error (EXIT_FAILURE, errno, "%s", quote (input_filename));
257         }
258     }
259
260   line = NULL;
261   buflen = 0;
262   ok = true;
263   while (1)
264     {
265       ssize_t line_length = getline (&line, &buflen, in_stream);
266       if (line_length < 0)
267         {
268           /* FIXME: detect/handle error here.  */
269           break;
270         }
271
272       if (! get_date (&when, line, NULL))
273         {
274           if (line[line_length - 1] == '\n')
275             line[line_length - 1] = '\0';
276           error (0, 0, _("invalid date %s"), quote (line));
277           ok = false;
278         }
279       else
280         {
281           ok &= show_date (format, when);
282         }
283     }
284
285   if (fclose (in_stream) == EOF)
286     error (EXIT_FAILURE, errno, "%s", quote (input_filename));
287
288   free (line);
289
290   return ok;
291 }
292
293 int
294 main (int argc, char **argv)
295 {
296   int optc;
297   const char *datestr = NULL;
298   const char *set_datestr = NULL;
299   struct timespec when;
300   bool set_date = false;
301   char *format;
302   char *batch_file = NULL;
303   char *reference = NULL;
304   struct stat refstats;
305   int n_args;
306   bool ok;
307   int option_specified_date;
308
309   initialize_main (&argc, &argv);
310   program_name = argv[0];
311   setlocale (LC_ALL, "");
312   bindtextdomain (PACKAGE, LOCALEDIR);
313   textdomain (PACKAGE);
314
315   atexit (close_stdout);
316
317   while ((optc = getopt_long (argc, argv, short_options, long_options, NULL))
318          != -1)
319     switch (optc)
320       {
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 = true;
338         break;
339       case 's':
340         set_datestr = optarg;
341         set_date = true;
342         break;
343       case 'u':
344         /* POSIX says that `date -u' is equivalent to setting the TZ
345            environment variable, so this option should do nothing other
346            than setting TZ.  */
347         if (putenv ("TZ=UTC0") != 0)
348           xalloc_die ();
349         TZSET;
350         break;
351       case_GETOPT_HELP_CHAR;
352       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
353       default:
354         usage (EXIT_FAILURE);
355       }
356
357   n_args = argc - optind;
358
359   option_specified_date = ((datestr ? 1 : 0)
360                            + (batch_file ? 1 : 0)
361                            + (reference ? 1 : 0));
362
363   if (option_specified_date > 1)
364     {
365       error (0, 0,
366         _("the options to specify dates for printing are mutually exclusive"));
367       usage (EXIT_FAILURE);
368     }
369
370   if (set_date && option_specified_date)
371     {
372       error (0, 0,
373           _("the options to print and set the time may not be used together"));
374       usage (EXIT_FAILURE);
375     }
376
377   if (n_args > 1)
378     {
379       error (0, 0, _("extra operand %s"), quote (argv[optind + 1]));
380       usage (EXIT_FAILURE);
381     }
382
383   if ((set_date || option_specified_date)
384       && n_args == 1 && argv[optind][0] != '+')
385     {
386       error (0, 0, _("\
387 the argument %s lacks a leading `+';\n\
388 When using an option to specify date(s), any non-option\n\
389 argument must be a format string beginning with `+'."),
390              quote (argv[optind]));
391       usage (EXIT_FAILURE);
392     }
393
394   /* Simply ignore --rfc-2822 if specified when setting the date.  */
395   if (rfc_format && !set_date && n_args > 0)
396     {
397       error (0, 0,
398              _("a format string may not be specified when using\
399  the --rfc-2822 (-R) option"));
400       usage (EXIT_FAILURE);
401     }
402
403   if (set_date)
404     datestr = set_datestr;
405
406   if (batch_file != NULL)
407     ok = batch_convert (batch_file, (n_args == 1 ? argv[optind] + 1 : NULL));
408   else
409     {
410       bool valid_date = true;
411       ok = true;
412
413       if (!option_specified_date && !set_date)
414         {
415           if (n_args == 1 && argv[optind][0] != '+')
416             {
417               /* Prepare to set system clock to the specified date/time
418                  given in the POSIX-format.  */
419               set_date = true;
420               datestr = argv[optind];
421               valid_date = posixtime (&when.tv_sec,
422                                       datestr,
423                                       (PDS_TRAILING_YEAR
424                                        | PDS_CENTURY | PDS_SECONDS));
425               when.tv_nsec = 0; /* FIXME: posixtime should set this.  */
426               format = NULL;
427             }
428           else
429             {
430               /* Prepare to print the current date/time.  */
431               datestr = _("undefined");
432               gettime (&when);
433               format = (n_args == 1 ? argv[optind] + 1 : NULL);
434             }
435         }
436       else
437         {
438           /* (option_specified_date || set_date) */
439           if (reference != NULL)
440             {
441               if (stat (reference, &refstats))
442                 error (EXIT_FAILURE, errno, "%s", reference);
443               when.tv_sec = refstats.st_mtime;
444               when.tv_nsec = TIMESPEC_NS (refstats.st_mtim);
445             }
446           else
447             {
448               valid_date = get_date (&when, datestr, NULL);
449             }
450
451           format = (n_args == 1 ? argv[optind] + 1 : NULL);
452         }
453
454       if (! valid_date)
455         error (EXIT_FAILURE, 0, _("invalid date %s"), quote (datestr));
456
457       if (set_date)
458         {
459           /* Set the system clock to the specified date, then regardless of
460              the success of that operation, format and print that date.  */
461           if (settime (&when) != 0)
462             {
463               error (0, errno, _("cannot set date"));
464               ok = false;
465             }
466         }
467
468       ok &= show_date (format, when);
469     }
470
471   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
472 }
473
474 /* Display the date and/or time in WHEN according to the format specified
475    in FORMAT, followed by a newline.  If FORMAT is NULL, use the
476    standard output format (ctime style but with a timezone inserted).
477    Return true if successful.  */
478
479 static bool
480 show_date (const char *format, struct timespec when)
481 {
482   struct tm *tm;
483   char *out = NULL;
484   size_t out_length = 0;
485   /* ISO 8601 formats.  See below regarding %z */
486   static char const * const iso_format_string[] =
487   {
488     "%Y-%m-%d",
489     "%Y-%m-%dT%H%z",
490     "%Y-%m-%dT%H:%M%z",
491     "%Y-%m-%dT%H:%M:%S%z",
492     "%Y-%m-%dT%H:%M:%S,%N%z"
493   };
494
495   if (format == NULL)
496     {
497       if (rfc_format)
498         format = "%a, %d %b %Y %H:%M:%S %z";
499       else if (iso_8601_format)
500         format = iso_format_string[iso_8601_format - 1];
501       else
502         {
503           char *date_fmt = DATE_FMT_LANGINFO ();
504           /* Do not wrap the following literal format string with _(...).
505              For example, suppose LC_ALL is unset, LC_TIME="POSIX",
506              and LANG="ko_KR".  In that case, POSIX says that LC_TIME
507              determines the format and contents of date and time strings
508              written by date, which means "date" must generate output
509              using the POSIX locale; but adding _() would cause "date"
510              to use a Korean translation of the format.  */
511           format = *date_fmt ? date_fmt : "%a %b %e %H:%M:%S %Z %Y";
512         }
513     }
514   else if (*format == '\0')
515     {
516       printf ("\n");
517       return true;
518     }
519
520   tm = localtime (&when.tv_sec);
521   if (! tm)
522     {
523       char buf[INT_BUFSIZE_BOUND (intmax_t)];
524       error (0, 0, _("time %s is out of range"),
525              (TYPE_SIGNED (time_t)
526               ? imaxtostr (when.tv_sec, buf)
527               : umaxtostr (when.tv_sec, buf)));
528       puts (buf);
529       return false;
530     }
531
532   while (1)
533     {
534       bool done;
535       out = x2nrealloc (out, &out_length, sizeof *out);
536
537       /* Mark the first byte of the buffer so we can detect the case
538          of nstrftime producing an empty string.  Otherwise, this loop
539          would not terminate when date was invoked like this
540          `LANG=de date +%p' on a system with good language support.  */
541       out[0] = '\1';
542
543       if (rfc_format)
544         setlocale (LC_ALL, "C");
545
546       done = (nstrftime (out, out_length, format, tm, 0, when.tv_nsec)
547               || out[0] == '\0');
548
549       if (rfc_format)
550         setlocale (LC_ALL, "");
551
552       if (done)
553         break;
554     }
555
556   puts (out);
557   free (out);
558   return true;
559 }