Patch from Denis Vlasenko to constify things and fix a few typos.
[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                 if(stat(filename,&statbuf))
169                         bb_perror_msg_and_die("File '%s' not found.", filename);
170                 tm=statbuf.st_mtime;
171         } else time(&tm);
172         memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
173         /* Zero out fields - take her back to midnight! */
174         if (date_str != NULL) {
175                 tm_time.tm_sec = 0;
176                 tm_time.tm_min = 0;
177                 tm_time.tm_hour = 0;
178
179                 /* Process any date input to UNIX time since 1 Jan 1970 */
180                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) {
181                         strptime(date_str, hintfmt_arg, &tm_time);
182                 } else if (strchr(date_str, ':') != NULL) {
183                         date_conv_ftime(&tm_time, date_str);
184                 } else {
185                         date_conv_time(&tm_time, date_str);
186                 }
187
188                 /* Correct any day of week and day of year etc. fields */
189                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst. */
190                 tm = mktime(&tm_time);
191                 if (tm < 0) {
192                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
193                 }
194                 if (utc && putenv("TZ=UTC0") != 0) {
195                         bb_error_msg_and_die(bb_msg_memory_exhausted);
196                 }
197
198                 /* if setting time, set it */
199                 if (set_time && stime(&tm) < 0) {
200                         bb_perror_msg("cannot set date");
201                 }
202         }
203
204         /* Display output */
205
206         /* Deal with format string */
207         if (date_fmt == NULL) {
208                 /* Start with the default case */
209                 
210                 date_fmt = (opt & DATE_OPT_RFC2822 ?
211                                         (utc ? "%a, %d %b %Y %H:%M:%S GMT" :
212                                         "%a, %d %b %Y %H:%M:%S %z") :
213                                         "%a %b %e %H:%M:%S %Z %Y");
214
215                 if (ENABLE_FEATURE_DATE_ISOFMT) {
216                         if (ifmt == 4)
217                                 date_fmt = utc ? "%Y-%m-%dT%H:%M:%SZ" : "%Y-%m-%dT%H:%M:%S%z";
218                         else if (ifmt == 3)
219                                 date_fmt = utc ? "%Y-%m-%dT%H:%MZ" : "%Y-%m-%dT%H:%M%z";
220                         else if (ifmt == 2) 
221                                 date_fmt = utc ? "%Y-%m-%dT%HZ" : "%Y-%m-%dT%H%z";
222                         else if (ifmt == 1)
223                                 date_fmt = "%Y-%m-%d";
224                 }
225         }
226         
227         if (*date_fmt == '\0') {
228
229                 /* With no format string, just print a blank line */
230                 
231                 *bb_common_bufsiz1=0;
232         } else {
233
234                 /* Handle special conversions */
235
236                 if (strncmp(date_fmt, "%f", 2) == 0) {
237                         date_fmt = "%Y.%m.%d-%H:%M:%S";
238                 }
239
240                 /* Generate output string */
241                 strftime(bb_common_bufsiz1, 200, date_fmt, &tm_time);
242         }
243         puts(bb_common_bufsiz1);
244
245         return EXIT_SUCCESS;
246 }