blob: fa5ab433e411f2f88f21ae676d0b8274da1346e5
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Utility routines. |
4 | * |
5 | * Copyright (C) 2007 Denys Vlasenko |
6 | * |
7 | * Licensed under GPLv2, see file LICENSE in this source tree. |
8 | */ |
9 | #include "libbb.h" |
10 | |
11 | void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) |
12 | { |
13 | char end = '\0'; |
14 | const char *last_colon = strrchr(date_str, ':'); |
15 | |
16 | if (last_colon != NULL) { |
17 | /* Parse input and assign appropriately to ptm */ |
18 | #if ENABLE_DESKTOP |
19 | const char *endp; |
20 | #endif |
21 | |
22 | /* HH:MM */ |
23 | if (sscanf(date_str, "%u:%u%c", |
24 | &ptm->tm_hour, |
25 | &ptm->tm_min, |
26 | &end) >= 2 |
27 | ) { |
28 | /* no adjustments needed */ |
29 | } else |
30 | /* mm.dd-HH:MM */ |
31 | if (sscanf(date_str, "%u.%u-%u:%u%c", |
32 | &ptm->tm_mon, &ptm->tm_mday, |
33 | &ptm->tm_hour, &ptm->tm_min, |
34 | &end) >= 4 |
35 | ) { |
36 | /* Adjust month from 1-12 to 0-11 */ |
37 | ptm->tm_mon -= 1; |
38 | } else |
39 | /* yyyy.mm.dd-HH:MM */ |
40 | if (sscanf(date_str, "%u.%u.%u-%u:%u%c", &ptm->tm_year, |
41 | &ptm->tm_mon, &ptm->tm_mday, |
42 | &ptm->tm_hour, &ptm->tm_min, |
43 | &end) >= 5 |
44 | /* yyyy-mm-dd HH:MM */ |
45 | || sscanf(date_str, "%u-%u-%u %u:%u%c", &ptm->tm_year, |
46 | &ptm->tm_mon, &ptm->tm_mday, |
47 | &ptm->tm_hour, &ptm->tm_min, |
48 | &end) >= 5 |
49 | ) { |
50 | ptm->tm_year -= 1900; /* Adjust years */ |
51 | ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */ |
52 | } else |
53 | #if ENABLE_DESKTOP /* strptime is BIG: ~1k in uclibc, ~10k in glibc */ |
54 | /* month_name d HH:MM:SS YYYY. Supported by GNU date */ |
55 | if ((endp = strptime(date_str, "%b %d %T %Y", ptm)) != NULL |
56 | && *endp == '\0' |
57 | ) { |
58 | return; /* don't fall through to end == ":" check */ |
59 | } else |
60 | #endif |
61 | { |
62 | bb_error_msg_and_die(bb_msg_invalid_date, date_str); |
63 | } |
64 | if (end == ':') { |
65 | /* xxx:SS */ |
66 | if (sscanf(last_colon + 1, "%u%c", &ptm->tm_sec, &end) == 1) |
67 | end = '\0'; |
68 | /* else end != NUL and we error out */ |
69 | } |
70 | } else |
71 | if (strchr(date_str, '-') |
72 | /* Why strchr('-') check? |
73 | * sscanf below will trash ptm->tm_year, this breaks |
74 | * if parse_str is "10101010" (iow, "MMddhhmm" form) |
75 | * because we destroy year. Do these sscanf |
76 | * only if we saw a dash in parse_str. |
77 | */ |
78 | /* yyyy-mm-dd HH */ |
79 | && (sscanf(date_str, "%u-%u-%u %u%c", &ptm->tm_year, |
80 | &ptm->tm_mon, &ptm->tm_mday, |
81 | &ptm->tm_hour, |
82 | &end) >= 4 |
83 | /* yyyy-mm-dd */ |
84 | || sscanf(date_str, "%u-%u-%u%c", &ptm->tm_year, |
85 | &ptm->tm_mon, &ptm->tm_mday, |
86 | &end) >= 3 |
87 | ) |
88 | ) { |
89 | ptm->tm_year -= 1900; /* Adjust years */ |
90 | ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */ |
91 | } else |
92 | if (date_str[0] == '@') { |
93 | time_t t = bb_strtol(date_str + 1, NULL, 10); |
94 | if (!errno) { |
95 | struct tm *lt = localtime(&t); |
96 | if (lt) { |
97 | *ptm = *lt; |
98 | return; |
99 | } |
100 | } |
101 | end = '1'; |
102 | } else { |
103 | /* Googled the following on an old date manpage: |
104 | * |
105 | * The canonical representation for setting the date/time is: |
106 | * cc Century (either 19 or 20) |
107 | * yy Year in abbreviated form (e.g. 89, 06) |
108 | * mm Numeric month, a number from 1 to 12 |
109 | * dd Day, a number from 1 to 31 |
110 | * HH Hour, a number from 0 to 23 |
111 | * MM Minutes, a number from 0 to 59 |
112 | * .SS Seconds, a number from 0 to 61 (with leap seconds) |
113 | * Everything but the minutes is optional |
114 | * |
115 | * "touch -t DATETIME" format: [[[[[YY]YY]MM]DD]hh]mm[.ss] |
116 | * Some, but not all, Unix "date DATETIME" commands |
117 | * move [[YY]YY] past minutes mm field (!). |
118 | * Coreutils date does it, and SUS mandates it. |
119 | * (date -s DATETIME does not support this format. lovely!) |
120 | * In bbox, this format is special-cased in date applet |
121 | * (IOW: this function assumes "touch -t" format). |
122 | */ |
123 | unsigned cur_year = ptm->tm_year; |
124 | int len = strchrnul(date_str, '.') - date_str; |
125 | |
126 | /* MM[.SS] */ |
127 | if (len == 2 && sscanf(date_str, "%2u%2u%2u%2u""%2u%c" + 12, |
128 | &ptm->tm_min, |
129 | &end) >= 1) { |
130 | } else |
131 | /* HHMM[.SS] */ |
132 | if (len == 4 && sscanf(date_str, "%2u%2u%2u""%2u%2u%c" + 9, |
133 | &ptm->tm_hour, |
134 | &ptm->tm_min, |
135 | &end) >= 2) { |
136 | } else |
137 | /* ddHHMM[.SS] */ |
138 | if (len == 6 && sscanf(date_str, "%2u%2u""%2u%2u%2u%c" + 6, |
139 | &ptm->tm_mday, |
140 | &ptm->tm_hour, |
141 | &ptm->tm_min, |
142 | &end) >= 3) { |
143 | } else |
144 | /* mmddHHMM[.SS] */ |
145 | if (len == 8 && sscanf(date_str, "%2u""%2u%2u%2u%2u%c" + 3, |
146 | &ptm->tm_mon, |
147 | &ptm->tm_mday, |
148 | &ptm->tm_hour, |
149 | &ptm->tm_min, |
150 | &end) >= 4) { |
151 | /* Adjust month from 1-12 to 0-11 */ |
152 | ptm->tm_mon -= 1; |
153 | } else |
154 | /* yymmddHHMM[.SS] */ |
155 | if (len == 10 && sscanf(date_str, "%2u%2u%2u%2u%2u%c", |
156 | &ptm->tm_year, |
157 | &ptm->tm_mon, |
158 | &ptm->tm_mday, |
159 | &ptm->tm_hour, |
160 | &ptm->tm_min, |
161 | &end) >= 5) { |
162 | /* Adjust month from 1-12 to 0-11 */ |
163 | ptm->tm_mon -= 1; |
164 | if (cur_year >= 50) { /* >= 1950 */ |
165 | /* Adjust year: */ |
166 | /* 1. Put it in the current century */ |
167 | ptm->tm_year += (cur_year / 100) * 100; |
168 | /* 2. If too far in the past, +100 years */ |
169 | if (ptm->tm_year < (int) cur_year - 50) |
170 | ptm->tm_year += 100; |
171 | /* 3. If too far in the future, -100 years */ |
172 | if (ptm->tm_year > (int) cur_year + 50) |
173 | ptm->tm_year -= 100; |
174 | } |
175 | } else |
176 | /* ccyymmddHHMM[.SS] */ |
177 | if (len == 12 && sscanf(date_str, "%4u%2u%2u%2u%2u%c", |
178 | &ptm->tm_year, |
179 | &ptm->tm_mon, |
180 | &ptm->tm_mday, |
181 | &ptm->tm_hour, |
182 | &ptm->tm_min, |
183 | &end) >= 5) { |
184 | ptm->tm_year -= 1900; /* Adjust years */ |
185 | ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */ |
186 | } else { |
187 | bb_error_msg_and_die(bb_msg_invalid_date, date_str); |
188 | } |
189 | ptm->tm_sec = 0; /* assume zero if [.SS] is not given */ |
190 | if (end == '.') { |
191 | /* xxx.SS */ |
192 | if (sscanf(strchr(date_str, '.') + 1, "%u%c", |
193 | &ptm->tm_sec, &end) == 1) |
194 | end = '\0'; |
195 | /* else end != NUL and we error out */ |
196 | } |
197 | } |
198 | if (end != '\0') { |
199 | bb_error_msg_and_die(bb_msg_invalid_date, date_str); |
200 | } |
201 | } |
202 | |
203 | time_t FAST_FUNC validate_tm_time(const char *date_str, struct tm *ptm) |
204 | { |
205 | time_t t = mktime(ptm); |
206 | if (t == (time_t) -1L) { |
207 | bb_error_msg_and_die(bb_msg_invalid_date, date_str); |
208 | } |
209 | return t; |
210 | } |
211 | |
212 | static char* strftime_fmt(char *buf, unsigned len, time_t *tp, const char *fmt) |
213 | { |
214 | time_t t; |
215 | if (!tp) { |
216 | tp = &t; |
217 | time(tp); |
218 | } |
219 | /* Returns pointer to NUL */ |
220 | return buf + strftime(buf, len, fmt, localtime(tp)); |
221 | } |
222 | |
223 | char* FAST_FUNC strftime_HHMMSS(char *buf, unsigned len, time_t *tp) |
224 | { |
225 | return strftime_fmt(buf, len, tp, "%H:%M:%S"); |
226 | } |
227 | |
228 | char* FAST_FUNC strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp) |
229 | { |
230 | return strftime_fmt(buf, len, tp, "%Y-%m-%d %H:%M:%S"); |
231 | } |
232 | |
233 | #if ENABLE_MONOTONIC_SYSCALL |
234 | |
235 | #include <sys/syscall.h> |
236 | /* Old glibc (< 2.3.4) does not provide this constant. We use syscall |
237 | * directly so this definition is safe. */ |
238 | #ifndef CLOCK_MONOTONIC |
239 | #define CLOCK_MONOTONIC 1 |
240 | #endif |
241 | |
242 | /* libc has incredibly messy way of doing this, |
243 | * typically requiring -lrt. We just skip all this mess */ |
244 | static void get_mono(struct timespec *ts) |
245 | { |
246 | if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, ts)) |
247 | bb_error_msg_and_die("clock_gettime(MONOTONIC) failed"); |
248 | } |
249 | unsigned long long FAST_FUNC monotonic_ns(void) |
250 | { |
251 | struct timespec ts; |
252 | get_mono(&ts); |
253 | return ts.tv_sec * 1000000000ULL + ts.tv_nsec; |
254 | } |
255 | unsigned long long FAST_FUNC monotonic_us(void) |
256 | { |
257 | struct timespec ts; |
258 | get_mono(&ts); |
259 | return ts.tv_sec * 1000000ULL + ts.tv_nsec/1000; |
260 | } |
261 | unsigned long long FAST_FUNC monotonic_ms(void) |
262 | { |
263 | struct timespec ts; |
264 | get_mono(&ts); |
265 | return ts.tv_sec * 1000ULL + ts.tv_nsec/1000000; |
266 | } |
267 | unsigned FAST_FUNC monotonic_sec(void) |
268 | { |
269 | struct timespec ts; |
270 | get_mono(&ts); |
271 | return ts.tv_sec; |
272 | } |
273 | |
274 | #else |
275 | |
276 | unsigned long long FAST_FUNC monotonic_ns(void) |
277 | { |
278 | struct timeval tv; |
279 | gettimeofday(&tv, NULL); |
280 | return tv.tv_sec * 1000000000ULL + tv.tv_usec * 1000; |
281 | } |
282 | unsigned long long FAST_FUNC monotonic_us(void) |
283 | { |
284 | struct timeval tv; |
285 | gettimeofday(&tv, NULL); |
286 | return tv.tv_sec * 1000000ULL + tv.tv_usec; |
287 | } |
288 | unsigned long long FAST_FUNC monotonic_ms(void) |
289 | { |
290 | struct timeval tv; |
291 | gettimeofday(&tv, NULL); |
292 | return tv.tv_sec * 1000ULL + tv.tv_usec / 1000; |
293 | } |
294 | unsigned FAST_FUNC monotonic_sec(void) |
295 | { |
296 | return time(NULL); |
297 | } |
298 | |
299 | #endif |
300 |