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