change the hardcoded error constant (0x80000000UL) to a nice flexible define (BB_GETO...
[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  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  *
23 */
24
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <sys/time.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <getopt.h>
33 #include "busybox.h"
34
35
36 /* This 'date' command supports only 2 time setting formats,
37    all the GNU strftime stuff (its in libc, lets use it),
38    setting time using UTC and displaying int, as well as
39    an RFC 822 complient date output for shell scripting
40    mail commands */
41
42 /* Input parsing code is always bulky - used heavy duty libc stuff as
43    much as possible, missed out a lot of bounds checking */
44
45 /* Default input handling to save surprising some people */
46
47 static struct tm *date_conv_time(struct tm *tm_time, const char *t_string)
48 {
49         int nr;
50         char *cp;
51
52         nr = sscanf(t_string, "%2d%2d%2d%2d%d", &(tm_time->tm_mon),
53                                 &(tm_time->tm_mday), &(tm_time->tm_hour), &(tm_time->tm_min),
54                                 &(tm_time->tm_year));
55
56         if (nr < 4 || nr > 5) {
57                 bb_error_msg_and_die(bb_msg_invalid_date, t_string);
58         }
59
60         cp = strchr(t_string, '.');
61         if (cp) {
62                 nr = sscanf(cp + 1, "%2d", &(tm_time->tm_sec));
63                 if (nr != 1) {
64                         bb_error_msg_and_die(bb_msg_invalid_date, t_string);
65                 }
66         }
67
68         /* correct for century  - minor Y2K problem here? */
69         if (tm_time->tm_year >= 1900) {
70                 tm_time->tm_year -= 1900;
71         }
72         /* adjust date */
73         tm_time->tm_mon -= 1;
74
75         return (tm_time);
76
77 }
78
79
80 /* The new stuff for LRP */
81
82 static struct tm *date_conv_ftime(struct tm *tm_time, const char *t_string)
83 {
84         struct tm t;
85
86         /* Parse input and assign appropriately to tm_time */
87
88         if (t =
89                 *tm_time, sscanf(t_string, "%d:%d:%d", &t.tm_hour, &t.tm_min,
90                                                  &t.tm_sec) == 3) {
91                 /* no adjustments needed */
92         } else if (t =
93                            *tm_time, sscanf(t_string, "%d:%d", &t.tm_hour,
94                                                                 &t.tm_min) == 2) {
95                 /* no adjustments needed */
96         } else if (t =
97                            *tm_time, sscanf(t_string, "%d.%d-%d:%d:%d", &t.tm_mon,
98                                                                 &t.tm_mday, &t.tm_hour, &t.tm_min,
99                                                                 &t.tm_sec) == 5) {
100                 /* Adjust dates from 1-12 to 0-11 */
101                 t.tm_mon -= 1;
102         } else if (t =
103                            *tm_time, sscanf(t_string, "%d.%d-%d:%d", &t.tm_mon,
104                                                                 &t.tm_mday, &t.tm_hour, &t.tm_min) == 4) {
105                 /* Adjust dates from 1-12 to 0-11 */
106                 t.tm_mon -= 1;
107         } else if (t =
108                            *tm_time, sscanf(t_string, "%d.%d.%d-%d:%d:%d", &t.tm_year,
109                                                                 &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min,
110                                                                 &t.tm_sec) == 6) {
111                 t.tm_year -= 1900;      /* Adjust years */
112                 t.tm_mon -= 1;  /* Adjust dates from 1-12 to 0-11 */
113         } else if (t =
114                            *tm_time, sscanf(t_string, "%d.%d.%d-%d:%d", &t.tm_year,
115                                                                 &t.tm_mon, &t.tm_mday, &t.tm_hour,
116                                                                 &t.tm_min) == 5) {
117                 t.tm_year -= 1900;      /* Adjust years */
118                 t.tm_mon -= 1;  /* Adjust dates from 1-12 to 0-11 */
119         } else {
120                 bb_error_msg_and_die(bb_msg_invalid_date, t_string);
121         }
122         *tm_time = t;
123         return (tm_time);
124 }
125
126 #define DATE_OPT_RFC2822        0x01
127 #define DATE_OPT_SET            0x02
128 #define DATE_OPT_UTC            0x04
129 #define DATE_OPT_DATE           0x08
130 #define DATE_OPT_REFERENCE      0x10
131 #ifdef CONFIG_FEATURE_DATE_ISOFMT
132 # define DATE_OPT_TIMESPEC      0x20
133 #endif
134
135 int date_main(int argc, char **argv)
136 {
137         char *date_str = NULL;
138         char *date_fmt = NULL;
139         char *t_buff;
140         int set_time;
141         int utc;
142         int use_arg = 0;
143         time_t tm;
144         unsigned long opt;
145         struct tm tm_time;
146         char *filename = NULL;
147
148 #ifdef CONFIG_FEATURE_DATE_ISOFMT
149         int ifmt = 0;
150         char *isofmt_arg;
151
152 # define GETOPT_ISOFMT  "I::"
153 #else
154 # define GETOPT_ISOFMT
155 #endif
156         bb_opt_complementaly = "d~ds:s~ds";
157         opt = bb_getopt_ulflags(argc, argv, "Rs:ud:r:" GETOPT_ISOFMT,
158                                         &date_str, &date_str, &filename
159 #ifdef CONFIG_FEATURE_DATE_ISOFMT
160                                         , &isofmt_arg
161 #endif
162                                         );
163         set_time = opt & DATE_OPT_SET;
164         utc = opt & DATE_OPT_UTC;
165         if ((utc) && (putenv("TZ=UTC0") != 0)) {
166                 bb_error_msg_and_die(bb_msg_memory_exhausted);
167         }
168         use_arg = opt & DATE_OPT_DATE;
169         if(opt & BB_GETOPT_ERROR)
170                 bb_show_usage();
171 #ifdef CONFIG_FEATURE_DATE_ISOFMT
172         if(opt & DATE_OPT_TIMESPEC) {
173                 if (!isofmt_arg) {
174                         ifmt = 1;
175                 } else {
176                         int ifmt_len = bb_strlen(isofmt_arg);
177
178                         if ((ifmt_len <= 4)
179                                 && (strncmp(isofmt_arg, "date", ifmt_len) == 0)) {
180                                 ifmt = 1;
181                         } else if ((ifmt_len <= 5)
182                                    && (strncmp(isofmt_arg, "hours", ifmt_len) == 0)) {
183                                 ifmt = 2;
184                         } else if ((ifmt_len <= 7)
185                                    && (strncmp(isofmt_arg, "minutes", ifmt_len) == 0)) {
186                                 ifmt = 3;
187                         } else if ((ifmt_len <= 7)
188                                    && (strncmp(isofmt_arg, "seconds", ifmt_len) == 0)) {
189                                 ifmt = 4;
190                         }
191                 }
192                 if (!ifmt) {
193                         bb_show_usage();
194                 }
195         }
196 #endif
197
198         if ((date_fmt == NULL) && (optind < argc) && (argv[optind][0] == '+')) {
199                 date_fmt = &argv[optind][1];    /* Skip over the '+' */
200         } else if (date_str == NULL) {
201                 set_time = 1;
202                 date_str = argv[optind];
203         }
204
205         /* Now we have parsed all the information except the date format
206            which depends on whether the clock is being set or read */
207
208         if(filename) {
209                 struct stat statbuf;
210                 if(stat(filename,&statbuf))
211                         bb_perror_msg_and_die("File '%s' not found.\n",filename);
212                 tm=statbuf.st_mtime;
213         } else time(&tm);
214         memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
215         /* Zero out fields - take her back to midnight! */
216         if (date_str != NULL) {
217                 tm_time.tm_sec = 0;
218                 tm_time.tm_min = 0;
219                 tm_time.tm_hour = 0;
220
221                 /* Process any date input to UNIX time since 1 Jan 1970 */
222                 if (strchr(date_str, ':') != NULL) {
223                         date_conv_ftime(&tm_time, date_str);
224                 } else {
225                         date_conv_time(&tm_time, date_str);
226                 }
227
228                 /* Correct any day of week and day of year etc. fields */
229                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst. */
230                 tm = mktime(&tm_time);
231                 if (tm < 0) {
232                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
233                 }
234                 if (utc && (putenv("TZ=UTC0") != 0)) {
235                         bb_error_msg_and_die(bb_msg_memory_exhausted);
236                 }
237
238                 /* if setting time, set it */
239                 if (set_time && (stime(&tm) < 0)) {
240                         bb_perror_msg("cannot set date");
241                 }
242         }
243
244         /* Display output */
245
246         /* Deal with format string */
247         if (date_fmt == NULL) {
248 #ifdef CONFIG_FEATURE_DATE_ISOFMT
249                 switch (ifmt) {
250                 case 4:
251                         date_fmt = utc ? "%Y-%m-%dT%H:%M:%SZ" : "%Y-%m-%dT%H:%M:%S%z";
252                         break;
253                 case 3:
254                         date_fmt = utc ? "%Y-%m-%dT%H:%MZ" : "%Y-%m-%dT%H:%M%z";
255                         break;
256                 case 2:
257                         date_fmt = utc ? "%Y-%m-%dT%HZ" : "%Y-%m-%dT%H%z";
258                         break;
259                 case 1:
260                         date_fmt = "%Y-%m-%d";
261                         break;
262                 case 0:
263                 default:
264 #endif
265                         date_fmt = (opt & DATE_OPT_RFC2822 ?
266                                         (utc ? "%a, %d %b %Y %H:%M:%S GMT" :
267                                         "%a, %d %b %Y %H:%M:%S %z") :
268                                         "%a %b %e %H:%M:%S %Z %Y");
269
270 #ifdef CONFIG_FEATURE_DATE_ISOFMT
271                         break;
272                 }
273 #endif
274         } else if (*date_fmt == '\0') {
275                 /* Imitate what GNU 'date' does with NO format string! */
276                 printf("\n");
277                 return EXIT_SUCCESS;
278         }
279
280         /* Handle special conversions */
281
282         if (strncmp(date_fmt, "%f", 2) == 0) {
283                 date_fmt = "%Y.%m.%d-%H:%M:%S";
284         }
285
286         /* Print OUTPUT (after ALL that!) */
287         t_buff = xmalloc(201);
288         strftime(t_buff, 200, date_fmt, &tm_time);
289         puts(t_buff);
290
291         return EXIT_SUCCESS;
292 }