stty: fix columns command. closes bug 791.
[oweals/busybox.git] / coreutils / date.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini date implementation for busybox
4  *
5  * by Matthew Grant <grantma@anathoth.gen.nz>
6  *
7  * iso-format handling added by Robert Griebl <griebl@gmx.de>
8  * bugfixes and cleanup by Bernhard Reutner-Fischer
9  *
10  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
11 */
12
13 /* This 'date' command supports only 2 time setting formats,
14    all the GNU strftime stuff (its in libc, lets use it),
15    setting time using UTC and displaying it, as well as
16    an RFC 2822 compliant date output for shell scripting
17    mail commands */
18
19 /* Input parsing code is always bulky - used heavy duty libc stuff as
20    much as possible, missed out a lot of bounds checking */
21
22 /* Default input handling to save surprising some people */
23
24 /* GNU coreutils 6.9 man page:
25  * date [OPTION]... [+FORMAT]
26  * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
27  * -d, --date=STRING
28  *      display time described by STRING, not `now'
29  * -f, --file=DATEFILE
30  *      like --date once for each line of DATEFILE
31  * -r, --reference=FILE
32  *      display the last modification time of FILE
33  * -R, --rfc-2822
34  *      output date and time in RFC 2822 format.
35  *      Example: Mon, 07 Aug 2006 12:34:56 -0600
36  * --rfc-3339=TIMESPEC
37  *      output date and time in RFC 3339 format.
38  *      TIMESPEC='date', 'seconds', or 'ns'
39  *      Date and time components are separated by a single space:
40  *      2006-08-07 12:34:56-06:00
41  * -s, --set=STRING
42  *      set time described by STRING
43  * -u, --utc, --universal
44  *      print or set Coordinated Universal Time
45  *
46  * Busybox:
47  * long options are not supported
48  * -f is not supported
49  * -I seems to roughly match --rfc-3339, but -I has _optional_ param
50  *    (thus "-I seconds" doesn't work, only "-Iseconds"),
51  *    and does not support -Ins
52  * -D FMT is a bbox extension for _input_ conversion of -d DATE
53  */
54 #include "libbb.h"
55
56 enum {
57         OPT_RFC2822   = (1 << 0), /* R */
58         OPT_SET       = (1 << 1), /* s */
59         OPT_UTC       = (1 << 2), /* u */
60         OPT_DATE      = (1 << 3), /* d */
61         OPT_REFERENCE = (1 << 4), /* r */
62         OPT_TIMESPEC  = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
63         OPT_HINT      = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
64 };
65
66 static void maybe_set_utc(int opt)
67 {
68         if (opt & OPT_UTC)
69                 putenv((char*)"TZ=UTC0");
70 }
71
72 #if ENABLE_LONG_OPTS
73 static const char date_longopts[] ALIGN1 =
74                 "rfc-822\0" No_argument "R"
75                 "rfc-2822\0" No_argument "R"
76                 "set\0" Required_argument "s"
77                 "utc\0" No_argument "u"
78                 /*"universal\0" No_argument "u"*/
79                 "date\0" Required_argument "d"
80                 "reference\0" Required_argument "r"
81                 ;
82 #endif
83
84 int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
85 int date_main(int argc UNUSED_PARAM, char **argv)
86 {
87         struct tm tm_time;
88         time_t tm;
89         unsigned opt;
90         int ifmt = -1;
91         char *date_str;
92         char *fmt_dt2str;
93         char *fmt_str2dt;
94         char *filename;
95         char *isofmt_arg = NULL;
96
97         opt_complementary = "d--s:s--d"
98                 IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
99         IF_LONG_OPTS(applet_long_options = date_longopts;)
100         opt = getopt32(argv, "Rs:ud:r:"
101                         IF_FEATURE_DATE_ISOFMT("I::D:"),
102                         &date_str, &date_str, &filename
103                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
104         argv += optind;
105         maybe_set_utc(opt);
106
107         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
108                 ifmt = 0; /* default is date */
109                 if (isofmt_arg) {
110                         static const char isoformats[] ALIGN1 =
111                                 "date\0""hours\0""minutes\0""seconds\0";
112                         ifmt = index_in_strings(isoformats, isofmt_arg);
113                         if (ifmt < 0)
114                                 bb_show_usage();
115                 }
116         }
117
118         fmt_dt2str = NULL;
119         if (argv[0] && argv[0][0] == '+') {
120                 fmt_dt2str = &argv[0][1];       /* Skip over the '+' */
121                 argv++;
122         }
123         if (!(opt & (OPT_SET | OPT_DATE))) {
124                 opt |= OPT_SET;
125                 date_str = argv[0]; /* can be NULL */
126                 if (date_str) {
127 #if ENABLE_DESKTOP
128                         int len = strspn(date_str, "0123456789");
129                         if (date_str[len] == '\0'
130                          || (date_str[len] == '.'
131                             && isdigit(date_str[len+1])
132                             && isdigit(date_str[len+2])
133                             && date_str[len+3] == '\0'
134                             )
135                         ) {
136                                 /* Dreaded [MMDDhhmm[[CC]YY][.ss]] format!
137                                  * It does not match -d or -s format.
138                                  * Some users actually do use it.
139                                  */
140                                 len -= 8;
141                                 if (len < 0 || len > 4 || (len & 1))
142                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
143                                 if (len != 0) { /* move YY or CCYY to front */
144                                         char buf[4];
145                                         memcpy(buf, date_str + 8, len);
146                                         memmove(date_str + len, date_str, 8);
147                                         memcpy(date_str, buf, len);
148                                 }
149                         }
150 #endif
151                         argv++;
152                 }
153         }
154         if (*argv)
155                 bb_show_usage();
156
157         /* Now we have parsed all the information except the date format
158            which depends on whether the clock is being set or read */
159
160         if (opt & OPT_REFERENCE) {
161                 struct stat statbuf;
162                 xstat(filename, &statbuf);
163                 tm = statbuf.st_mtime;
164         } else {
165                 time(&tm);
166         }
167         localtime_r(&tm, &tm_time);
168
169         /* If date string is given, update tm_time, and maybe set date */
170         if (date_str != NULL) {
171                 /* Zero out fields - take her back to midnight! */
172                 tm_time.tm_sec = 0;
173                 tm_time.tm_min = 0;
174                 tm_time.tm_hour = 0;
175
176                 /* Process any date input to UNIX time since 1 Jan 1970 */
177                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
178                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
179                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
180                 } else {
181                         parse_datestr(date_str, &tm_time);
182                 }
183
184                 /* Correct any day of week and day of year etc. fields */
185                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
186                 tm = validate_tm_time(date_str, &tm_time);
187
188                 maybe_set_utc(opt);
189
190                 /* if setting time, set it */
191                 if ((opt & OPT_SET) && stime(&tm) < 0) {
192                         bb_perror_msg("can't set date");
193                 }
194         }
195
196         /* Display output */
197
198         /* Deal with format string */
199         if (fmt_dt2str == NULL) {
200                 int i;
201                 fmt_dt2str = xzalloc(32);
202                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
203                         strcpy(fmt_dt2str, "%Y-%m-%d");
204                         if (ifmt > 0) {
205                                 i = 8;
206                                 fmt_dt2str[i++] = 'T';
207                                 fmt_dt2str[i++] = '%';
208                                 fmt_dt2str[i++] = 'H';
209                                 if (ifmt > 1) {
210                                         fmt_dt2str[i++] = ':';
211                                         fmt_dt2str[i++] = '%';
212                                         fmt_dt2str[i++] = 'M';
213                                         if (ifmt > 2) {
214                                                 fmt_dt2str[i++] = ':';
215                                                 fmt_dt2str[i++] = '%';
216                                                 fmt_dt2str[i++] = 'S';
217                                         }
218                                 }
219  format_utc:
220                                 fmt_dt2str[i++] = '%';
221                                 fmt_dt2str[i] = (opt & OPT_UTC) ? 'Z' : 'z';
222                         }
223                 } else if (opt & OPT_RFC2822) {
224                         /* Undo busybox.c for date -R */
225                         if (ENABLE_LOCALE_SUPPORT)
226                                 setlocale(LC_TIME, "C");
227                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
228                         i = 22;
229                         goto format_utc;
230                 } else /* default case */
231                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
232         }
233
234 #define date_buf bb_common_bufsiz1
235         if (*fmt_dt2str == '\0') {
236                 /* With no format string, just print a blank line */
237                 date_buf[0] = '\0';
238         } else {
239                 /* Handle special conversions */
240                 if (strncmp(fmt_dt2str, "%f", 2) == 0) {
241                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
242                 }
243
244                 /* Generate output string */
245                 strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
246         }
247         puts(date_buf);
248
249         return EXIT_SUCCESS;
250 }