Typo fix.
[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  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
10 */
11
12 #include <stdlib.h>
13 #include <errno.h>
14 #include <sys/time.h>
15 #include <unistd.h>
16 #include <time.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include "busybox.h"
20
21 /* This 'date' command supports only 2 time setting formats,
22    all the GNU strftime stuff (its in libc, lets use it),
23    setting time using UTC and displaying int, as well as
24    an RFC 822 complient date output for shell scripting
25    mail commands */
26
27 /* Input parsing code is always bulky - used heavy duty libc stuff as
28    much as possible, missed out a lot of bounds checking */
29
30 /* Default input handling to save surprising some people */
31
32 static struct tm *date_conv_time(struct tm *tm_time, const char *t_string)
33 {
34         int nr;
35         char *cp;
36
37         nr = sscanf(t_string, "%2d%2d%2d%2d%d", &(tm_time->tm_mon),
38                                 &(tm_time->tm_mday), &(tm_time->tm_hour), &(tm_time->tm_min),
39                                 &(tm_time->tm_year));
40
41         if (nr < 4 || nr > 5) {
42                 bb_error_msg_and_die(bb_msg_invalid_date, t_string);
43         }
44
45         cp = strchr(t_string, '.');
46         if (cp) {
47                 nr = sscanf(cp + 1, "%2d", &(tm_time->tm_sec));
48                 if (nr != 1) {
49                         bb_error_msg_and_die(bb_msg_invalid_date, t_string);
50                 }
51         }
52
53         /* correct for century  - minor Y2K problem here? */
54         if (tm_time->tm_year >= 1900) {
55                 tm_time->tm_year -= 1900;
56         }
57         /* adjust date */
58         tm_time->tm_mon -= 1;
59
60         return (tm_time);
61
62 }
63
64
65 /* The new stuff for LRP */
66
67 static struct tm *date_conv_ftime(struct tm *tm_time, const char *t_string)
68 {
69         struct tm t;
70
71         /* Parse input and assign appropriately to tm_time */
72
73         if (t = *tm_time, sscanf(t_string, "%d:%d:%d", &t.tm_hour, &t.tm_min,
74                                                  &t.tm_sec) == 3) {
75                 /* no adjustments needed */
76         } else if (t = *tm_time, sscanf(t_string, "%d:%d", &t.tm_hour,
77                                                                 &t.tm_min) == 2) {
78                 /* no adjustments needed */
79         } else if (t = *tm_time, sscanf(t_string, "%d.%d-%d:%d:%d", &t.tm_mon,
80                                                 &t.tm_mday, &t.tm_hour,
81                                                 &t.tm_min, &t.tm_sec) == 5) {
82                 /* Adjust dates from 1-12 to 0-11 */
83                 t.tm_mon -= 1;
84         } else if (t = *tm_time, sscanf(t_string, "%d.%d-%d:%d", &t.tm_mon,
85                                                 &t.tm_mday,
86                                                 &t.tm_hour, &t.tm_min) == 4) {
87                 /* Adjust dates from 1-12 to 0-11 */
88                 t.tm_mon -= 1;
89         } else if (t = *tm_time, sscanf(t_string, "%d.%d.%d-%d:%d:%d", &t.tm_year,
90                                                 &t.tm_mon, &t.tm_mday,
91                                                 &t.tm_hour, &t.tm_min,
92                                                         &t.tm_sec) == 6) {
93                 t.tm_year -= 1900;      /* Adjust years */
94                 t.tm_mon -= 1;  /* Adjust dates from 1-12 to 0-11 */
95         } else if (t = *tm_time, sscanf(t_string, "%d.%d.%d-%d:%d", &t.tm_year,
96                                                 &t.tm_mon, &t.tm_mday,
97                                                 &t.tm_hour, &t.tm_min) == 5) {
98                 t.tm_year -= 1900;      /* Adjust years */
99                 t.tm_mon -= 1;  /* Adjust dates from 1-12 to 0-11 */
100         } else {
101                 bb_error_msg_and_die(bb_msg_invalid_date, t_string);
102         }
103         *tm_time = t;
104         return (tm_time);
105 }
106
107 #define DATE_OPT_RFC2822        0x01
108 #define DATE_OPT_SET            0x02
109 #define DATE_OPT_UTC            0x04
110 #define DATE_OPT_DATE           0x08
111 #define DATE_OPT_REFERENCE      0x10
112 #define DATE_OPT_TIMESPEC       0x20
113 #define DATE_OPT_HINT           0x40
114
115 int date_main(int argc, char **argv)
116 {
117         char *date_str = NULL;
118         char *date_fmt = NULL;
119         int set_time;
120         int utc;
121         time_t tm;
122         unsigned long opt;
123         struct tm tm_time;
124         char *filename = NULL;
125
126         int ifmt = 0;
127         char *isofmt_arg;
128         char *hintfmt_arg;
129
130         bb_opt_complementally = "?:d--s:s--d";
131         opt = bb_getopt_ulflags(argc, argv, "Rs:ud:r:"
132                                         USE_FEATURE_DATE_ISOFMT("I::D:"),
133                                         &date_str, &date_str, &filename
134                                         USE_FEATURE_DATE_ISOFMT(, &isofmt_arg, &hintfmt_arg));
135         set_time = opt & DATE_OPT_SET;
136         utc = opt & DATE_OPT_UTC;
137         if (utc && putenv("TZ=UTC0") != 0) {
138                 bb_error_msg_and_die(bb_msg_memory_exhausted);
139         }
140
141         if(ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) {
142                 if (!isofmt_arg) {
143                         ifmt = 1;
144                 } else {
145                         char *isoformats[]={"date","hours","minutes","seconds"};
146                         for(ifmt = 4; ifmt;)
147                                 if(!strcmp(isofmt_arg,isoformats[--ifmt]))
148                                         break;
149                 }
150                 if (!ifmt) {
151                         bb_show_usage();
152                 }
153         }
154
155         /* XXX, date_fmt == NULL from this always */
156         if ((date_fmt == NULL) && (optind < argc) && (argv[optind][0] == '+')) {
157                 date_fmt = &argv[optind][1];    /* Skip over the '+' */
158         } else if (date_str == NULL) {
159                 set_time = 1;
160                 date_str = argv[optind];
161         }
162
163         /* Now we have parsed all the information except the date format
164            which depends on whether the clock is being set or read */
165
166         if(filename) {
167                 struct stat statbuf;
168                 xstat(filename,&statbuf);
169                 tm=statbuf.st_mtime;
170         } else time(&tm);
171         memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
172         /* Zero out fields - take her back to midnight! */
173         if (date_str != NULL) {
174                 tm_time.tm_sec = 0;
175                 tm_time.tm_min = 0;
176                 tm_time.tm_hour = 0;
177
178                 /* Process any date input to UNIX time since 1 Jan 1970 */
179                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) {
180                         strptime(date_str, hintfmt_arg, &tm_time);
181                 } else if (strchr(date_str, ':') != NULL) {
182                         date_conv_ftime(&tm_time, date_str);
183                 } else {
184                         date_conv_time(&tm_time, date_str);
185                 }
186
187                 /* Correct any day of week and day of year etc. fields */
188                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst. */
189                 tm = mktime(&tm_time);
190                 if (tm < 0) {
191                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
192                 }
193                 if (utc && putenv("TZ=UTC0") != 0) {
194                         bb_error_msg_and_die(bb_msg_memory_exhausted);
195                 }
196
197                 /* if setting time, set it */
198                 if (set_time && stime(&tm) < 0) {
199                         bb_perror_msg("cannot set date");
200                 }
201         }
202
203         /* Display output */
204
205         /* Deal with format string */
206         if (date_fmt == NULL) {
207                 /* Start with the default case */
208                 
209                 date_fmt = (opt & DATE_OPT_RFC2822 ?
210                                         (utc ? "%a, %d %b %Y %H:%M:%S GMT" :
211                                         "%a, %d %b %Y %H:%M:%S %z") :
212                                         "%a %b %e %H:%M:%S %Z %Y");
213
214                 if (ENABLE_FEATURE_DATE_ISOFMT) {
215                         if (ifmt == 4)
216                                 date_fmt = utc ? "%Y-%m-%dT%H:%M:%SZ" : "%Y-%m-%dT%H:%M:%S%z";
217                         else if (ifmt == 3)
218                                 date_fmt = utc ? "%Y-%m-%dT%H:%MZ" : "%Y-%m-%dT%H:%M%z";
219                         else if (ifmt == 2) 
220                                 date_fmt = utc ? "%Y-%m-%dT%HZ" : "%Y-%m-%dT%H%z";
221                         else if (ifmt == 1)
222                                 date_fmt = "%Y-%m-%d";
223                 }
224         }
225         
226         if (*date_fmt == '\0') {
227
228                 /* With no format string, just print a blank line */
229                 
230                 *bb_common_bufsiz1=0;
231         } else {
232
233                 /* Handle special conversions */
234
235                 if (strncmp(date_fmt, "%f", 2) == 0) {
236                         date_fmt = "%Y.%m.%d-%H:%M:%S";
237                 }
238
239                 /* Generate output string */
240                 strftime(bb_common_bufsiz1, 200, date_fmt, &tm_time);
241         }
242         puts(bb_common_bufsiz1);
243
244         return EXIT_SUCCESS;
245 }