/src/freeradius-server/src/lib/util/time.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 314410eea7c04a41f5ba5894ab8ca2a35d08f4ea $ |
19 | | * |
20 | | * @brief Platform independent time functions |
21 | | * @file lib/util/time.c |
22 | | * |
23 | | * @copyright 2016-2019 Alan DeKok (aland@freeradius.org) |
24 | | * @copyright 2019-2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
25 | | */ |
26 | | RCSID("$Id: 314410eea7c04a41f5ba5894ab8ca2a35d08f4ea $") |
27 | | |
28 | | #include <freeradius-devel/autoconf.h> |
29 | | #include <freeradius-devel/util/time.h> |
30 | | #include <freeradius-devel/util/misc.h> |
31 | | |
32 | | int64_t const fr_time_multiplier_by_res[] = { |
33 | | [FR_TIME_RES_NSEC] = 1, |
34 | | [FR_TIME_RES_USEC] = NSEC / USEC, |
35 | | [FR_TIME_RES_MSEC] = NSEC / MSEC, |
36 | | [FR_TIME_RES_CSEC] = NSEC / CSEC, |
37 | | [FR_TIME_RES_SEC] = NSEC, |
38 | | [FR_TIME_RES_MIN] = (int64_t)NSEC * 60, |
39 | | [FR_TIME_RES_HOUR] = (int64_t)NSEC * 3600, |
40 | | [FR_TIME_RES_DAY] = (int64_t)NSEC * 86400, |
41 | | [FR_TIME_RES_WEEK] = (int64_t)NSEC * 86400 * 7, |
42 | | [FR_TIME_RES_MONTH] = FR_TIME_DUR_MONTH, |
43 | | [FR_TIME_RES_YEAR] = FR_TIME_DUR_YEAR, |
44 | | }; |
45 | | |
46 | | fr_table_num_ordered_t const fr_time_precision_table[] = { |
47 | | { L("microseconds"), FR_TIME_RES_USEC }, |
48 | | { L("us"), FR_TIME_RES_USEC }, |
49 | | |
50 | | { L("nanoseconds"), FR_TIME_RES_NSEC }, |
51 | | { L("ns"), FR_TIME_RES_NSEC }, |
52 | | |
53 | | { L("milliseconds"), FR_TIME_RES_MSEC }, |
54 | | { L("ms"), FR_TIME_RES_MSEC }, |
55 | | |
56 | | { L("centiseconds"), FR_TIME_RES_CSEC }, |
57 | | { L("cs"), FR_TIME_RES_CSEC }, |
58 | | |
59 | | { L("seconds"), FR_TIME_RES_SEC }, |
60 | | { L("s"), FR_TIME_RES_SEC }, |
61 | | |
62 | | { L("minutes"), FR_TIME_RES_MIN }, |
63 | | { L("m"), FR_TIME_RES_MIN }, |
64 | | |
65 | | { L("hours"), FR_TIME_RES_HOUR }, |
66 | | { L("h"), FR_TIME_RES_HOUR }, |
67 | | |
68 | | { L("days"), FR_TIME_RES_DAY }, |
69 | | { L("d"), FR_TIME_RES_DAY }, |
70 | | |
71 | | { L("weeks"), FR_TIME_RES_WEEK }, |
72 | | { L("w"), FR_TIME_RES_WEEK }, |
73 | | |
74 | | /* |
75 | | * These use special values FR_TIME_DUR_MONTH and FR_TIME_DUR_YEAR |
76 | | */ |
77 | | { L("months"), FR_TIME_RES_MONTH }, |
78 | | { L("M"), FR_TIME_RES_MONTH }, |
79 | | |
80 | | { L("years"), FR_TIME_RES_YEAR }, |
81 | | { L("y"), FR_TIME_RES_YEAR }, |
82 | | |
83 | | }; |
84 | | size_t fr_time_precision_table_len = NUM_ELEMENTS(fr_time_precision_table); |
85 | | |
86 | | int64_t fr_time_epoch; //!< monotonic clock at boot, i.e. our epoch |
87 | | _Atomic int64_t fr_time_monotonic_to_realtime; //!< difference between the two clocks |
88 | | |
89 | | static char const *tz_names[2] = { NULL, NULL }; //!< normal, DST, from localtime_r(), tm_zone |
90 | | static long gmtoff[2] = {0, 0}; //!< from localtime_r(), tm_gmtoff |
91 | | static bool isdst = false; //!< from localtime_r(), tm_is_dst |
92 | | |
93 | | |
94 | | /** Get a new fr_time_monotonic_to_realtime value |
95 | | * |
96 | | * Should be done regularly to adjust for changes in system time. |
97 | | * |
98 | | * @return |
99 | | * - 0 on success. |
100 | | * - -1 on failure. |
101 | | */ |
102 | | int fr_time_sync(void) |
103 | 0 | { |
104 | 0 | struct tm tm; |
105 | 0 | time_t now; |
106 | | |
107 | | /* |
108 | | * fr_time_monotonic_to_realtime is the difference in nano |
109 | | * |
110 | | * So to convert a realtime timeval to fr_time we just subtract fr_time_monotonic_to_realtime from the timeval, |
111 | | * which leaves the number of nanoseconds elapsed since our epoch. |
112 | | */ |
113 | 0 | struct timespec ts_realtime, ts_monotime; |
114 | | |
115 | | /* |
116 | | * Call these consecutively to minimise drift... |
117 | | */ |
118 | 0 | if (clock_gettime(CLOCK_REALTIME, &ts_realtime) < 0) return -1; |
119 | 0 | if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts_monotime) < 0) return -1; |
120 | | |
121 | 0 | atomic_store_explicit(&fr_time_monotonic_to_realtime, |
122 | 0 | fr_time_delta_unwrap(fr_time_delta_from_timespec(&ts_realtime)) - |
123 | 0 | (fr_time_delta_unwrap(fr_time_delta_from_timespec(&ts_monotime)) - fr_time_epoch), |
124 | 0 | memory_order_release); |
125 | |
|
126 | 0 | now = ts_realtime.tv_sec; |
127 | | |
128 | | /* |
129 | | * Get local time zone name, daylight savings, and GMT |
130 | | * offsets. |
131 | | */ |
132 | 0 | (void) localtime_r(&now, &tm); |
133 | |
|
134 | 0 | isdst = (tm.tm_isdst != 0); |
135 | 0 | tz_names[isdst] = tm.tm_zone; |
136 | 0 | gmtoff[isdst] = tm.tm_gmtoff * NSEC; /* they store seconds, we store nanoseconds */ |
137 | |
|
138 | 0 | return 0; |
139 | 0 | } |
140 | | |
141 | | /** Initialize the local time. |
142 | | * |
143 | | * MUST be called when the program starts. MUST NOT be called after |
144 | | * that. |
145 | | * |
146 | | * @return |
147 | | * - <0 on error |
148 | | * - 0 on success |
149 | | */ |
150 | | int fr_time_start(void) |
151 | 0 | { |
152 | 0 | struct timespec ts; |
153 | |
|
154 | 0 | tzset(); /* Populate timezone, daylight and tzname globals */ |
155 | |
|
156 | 0 | if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts) < 0) return -1; |
157 | 0 | fr_time_epoch = fr_time_delta_unwrap(fr_time_delta_from_timespec(&ts)); |
158 | |
|
159 | 0 | return fr_time_sync(); |
160 | 0 | } |
161 | | |
162 | | /** Return time delta from the time zone. |
163 | | * |
164 | | * Returns the delta between UTC and the timezone specified by tz |
165 | | * |
166 | | * @param[in] tz time zone name |
167 | | * @param[out] delta the time delta |
168 | | * @return |
169 | | * - 0 converted OK |
170 | | * - <0 on error |
171 | | * |
172 | | * @note This function ONLY handles a limited number of time |
173 | | * zones: local and gmt. It is impossible in general to parse |
174 | | * arbitrary time zone strings, as there are duplicates. |
175 | | */ |
176 | | int fr_time_delta_from_time_zone(char const *tz, fr_time_delta_t *delta) |
177 | 1.09k | { |
178 | 1.09k | *delta = fr_time_delta_wrap(0); |
179 | | |
180 | 1.09k | if ((strcmp(tz, "UTC") == 0) || |
181 | 1.09k | (strcmp(tz, "GMT") == 0)) { |
182 | 118 | return 0; |
183 | 118 | } |
184 | | |
185 | | /* |
186 | | * Our local time zone OR time zone with daylight savings. |
187 | | */ |
188 | 978 | if (tz_names[0] && (strcmp(tz, tz_names[0]) == 0)) { |
189 | 0 | *delta = fr_time_delta_wrap(gmtoff[0]); |
190 | 0 | return 0; |
191 | 0 | } |
192 | | |
193 | 978 | if (tz_names[1] && (strcmp(tz, tz_names[1]) == 0)) { |
194 | 0 | *delta = fr_time_delta_wrap(gmtoff[1]); |
195 | 0 | return 0; |
196 | 0 | } |
197 | | |
198 | 978 | return -1; |
199 | 978 | } |
200 | | |
201 | | /** Create fr_time_delta_t from a string |
202 | | * |
203 | | * @param[out] out Where to write fr_time_delta_t |
204 | | * @param[in] in String to parse. |
205 | | * @param[in] hint scale for the parsing. Default is "seconds". |
206 | | * @param[in] no_trailing asserts that there should be a terminal sequence |
207 | | * after the time delta. Allows us to produce |
208 | | * better errors. |
209 | | * @param[in] tt terminal sequences. |
210 | | * @return |
211 | | * - >= 0 on success. |
212 | | * - <0 on failure. |
213 | | */ |
214 | | fr_slen_t fr_time_delta_from_substr(fr_time_delta_t *out, fr_sbuff_t *in, fr_time_res_t hint, |
215 | | bool no_trailing, fr_sbuff_term_t const *tt) |
216 | 7.74k | { |
217 | 7.74k | fr_sbuff_t our_in = FR_SBUFF(in); |
218 | 7.74k | int64_t integer; /* Whole units */ |
219 | 7.74k | fr_time_res_t res; |
220 | 7.74k | bool negative; |
221 | 7.74k | fr_sbuff_parse_error_t sberr; |
222 | 7.74k | bool overflow; |
223 | 7.74k | fr_time_delta_t delta; /* The delta we're building */ |
224 | 7.74k | size_t match_len; |
225 | | |
226 | 7.74k | negative = fr_sbuff_is_char(&our_in, '-'); |
227 | | |
228 | 7.74k | if (fr_sbuff_out(&sberr, &integer, &our_in) < 0) { |
229 | 1.60k | num_error: |
230 | 1.60k | fr_strerror_printf("Failed parsing time_delta: %s", |
231 | 1.60k | fr_table_str_by_value(sbuff_parse_error_table, sberr, "<INVALID>")); |
232 | 1.60k | FR_SBUFF_ERROR_RETURN(&our_in); |
233 | 428 | } |
234 | 7.31k | fr_sbuff_out_by_longest_prefix(&match_len, &res, fr_time_precision_table, &our_in, FR_TIME_RES_INVALID); |
235 | | |
236 | | /* |
237 | | * We now determine which one of the three formats |
238 | | * we accept the string is in. |
239 | | * |
240 | | * Either: |
241 | | * - <integer>[<scale>] |
242 | | * - <integer>.<fraction>[<scale>] |
243 | | * - [hours:]minutes:seconds |
244 | | */ |
245 | | |
246 | | /* |
247 | | * We have a fractional component |
248 | | * |
249 | | * <integer>.<fraction>[<scale>] |
250 | | */ |
251 | 7.31k | if (fr_sbuff_next_if_char(&our_in, '.')) { |
252 | 1.48k | fr_sbuff_marker_t m_f; |
253 | 1.48k | size_t f_len; |
254 | 1.48k | uint64_t f = 0; /* Fractional units */ |
255 | | |
256 | | /* |
257 | | * Normalise as a positive integer |
258 | | */ |
259 | 1.48k | if (negative) integer = -(integer); |
260 | | |
261 | | /* |
262 | | * Mark the start of the fractional component |
263 | | */ |
264 | 1.48k | fr_sbuff_marker(&m_f, &our_in); |
265 | | |
266 | | /* |
267 | | * Leading zeros appear to mess up integer parsing |
268 | | */ |
269 | 1.48k | fr_sbuff_adv_past_zeros(&our_in, SIZE_MAX, tt); |
270 | | |
271 | 1.48k | if (fr_sbuff_out(&sberr, &f, &our_in) < 0) { |
272 | | /* |
273 | | * Crappy workaround for <num>.0 |
274 | | * |
275 | | * Advancing past the leading zeros screws |
276 | | * up the fractional parsing when the |
277 | | * fraction is all zeros... |
278 | | */ |
279 | 691 | if ((sberr != FR_SBUFF_PARSE_ERROR_NOT_FOUND) || !fr_sbuff_is_char(&m_f, '0')) goto num_error; |
280 | 691 | } |
281 | | |
282 | 1.13k | f_len = fr_sbuff_behind(&m_f); |
283 | 1.13k | if (f_len > 9) { |
284 | 43 | fr_strerror_const("Too much precision for time_delta"); |
285 | 43 | fr_sbuff_set(&our_in, fr_sbuff_current(&m_f) + 10); |
286 | 43 | FR_SBUFF_ERROR_RETURN(&our_in); |
287 | 43 | } |
288 | | |
289 | | /* |
290 | | * Convert to nanoseconds |
291 | | * |
292 | | * This can't overflow. |
293 | | */ |
294 | 9.01k | while (f_len < 9) { |
295 | 7.92k | f *= 10; |
296 | 7.92k | f_len++; |
297 | 7.92k | } |
298 | | |
299 | | /* |
300 | | * Look for a scale suffix |
301 | | */ |
302 | 1.08k | fr_sbuff_out_by_longest_prefix(&match_len, &res, fr_time_precision_table, &our_in, FR_TIME_RES_INVALID); |
303 | | |
304 | 1.08k | if (no_trailing && !fr_sbuff_is_terminal(&our_in, tt)) { |
305 | 0 | trailing_data: |
306 | | /* Got a qualifier but there's stuff after */ |
307 | 0 | if (res != FR_TIME_RES_INVALID) { |
308 | 0 | fr_strerror_const("Trailing data after time_delta"); |
309 | 0 | FR_SBUFF_ERROR_RETURN(&our_in); |
310 | 0 | } |
311 | | |
312 | 0 | fr_strerror_const("Invalid precision qualifier for time_delta"); |
313 | 0 | FR_SBUFF_ERROR_RETURN(&our_in); |
314 | 0 | } |
315 | | |
316 | | /* Scale defaults to hint */ |
317 | 1.08k | if (res == FR_TIME_RES_INVALID) res = hint; |
318 | | |
319 | | /* |
320 | | * Subseconds was parsed as if it was nanoseconds. |
321 | | * But instead it may be something else, so it should |
322 | | * be truncated. |
323 | | * |
324 | | * Note that this operation can't overflow. |
325 | | */ |
326 | 1.08k | f *= fr_time_multiplier_by_res[res]; |
327 | 1.08k | f /= NSEC; |
328 | | |
329 | 1.08k | delta = fr_time_delta_from_integer(&overflow, integer, res); |
330 | 1.08k | if (overflow) { |
331 | 693 | overflow: |
332 | 693 | fr_strerror_printf("time_delta would %s", negative ? "underflow" : "overflow"); |
333 | 693 | fr_sbuff_set_to_start(&our_in); |
334 | 693 | FR_SBUFF_ERROR_RETURN(&our_in); |
335 | 391 | } |
336 | | |
337 | 696 | { |
338 | 696 | int64_t tmp; |
339 | | |
340 | | /* |
341 | | * Add fractional and integral parts checking for overflow |
342 | | */ |
343 | 696 | if (!fr_add(&tmp, fr_time_delta_unwrap(delta), f)) goto overflow; |
344 | | |
345 | | /* |
346 | | * Flip the sign back to negative |
347 | | */ |
348 | 695 | if (negative) tmp = -(tmp); |
349 | | |
350 | 695 | *out = fr_time_delta_wrap(tmp); |
351 | 695 | } |
352 | | |
353 | 695 | FR_SBUFF_SET_RETURN(in, &our_in); |
354 | | /* |
355 | | * It's timestamp format |
356 | | * |
357 | | * [hours:]minutes:seconds |
358 | | */ |
359 | 5.83k | } else if (fr_sbuff_next_if_char(&our_in, ':')) { |
360 | 4.74k | uint64_t hours, minutes, seconds; |
361 | 4.74k | fr_sbuff_marker_t m1; |
362 | | |
363 | 4.74k | res = FR_TIME_RES_SEC; |
364 | | |
365 | 4.74k | fr_sbuff_marker(&m1, &our_in); |
366 | | |
367 | 4.74k | if (fr_sbuff_out(&sberr, &seconds, &our_in) < 0) goto num_error; |
368 | | |
369 | | /* |
370 | | * minutes:seconds |
371 | | */ |
372 | 4.19k | if (!fr_sbuff_next_if_char(&our_in, ':')) { |
373 | 1.88k | hours = 0; |
374 | 1.88k | minutes = negative ? -(integer) : integer; |
375 | | |
376 | 1.88k | if (minutes > UINT16_MAX) { |
377 | 198 | fr_strerror_printf("minutes component of time_delta is too large"); |
378 | 198 | fr_sbuff_set_to_start(&our_in); |
379 | 198 | FR_SBUFF_ERROR_RETURN(&our_in); |
380 | 198 | } |
381 | | /* |
382 | | * hours:minutes:seconds |
383 | | */ |
384 | 2.31k | } else { |
385 | 2.31k | hours = negative ? -(integer) : integer; |
386 | 2.31k | minutes = seconds; |
387 | | |
388 | 2.31k | if (fr_sbuff_out(&sberr, &seconds, &our_in) < 0) goto num_error; |
389 | | |
390 | 2.03k | if (hours > UINT16_MAX) { |
391 | 403 | fr_strerror_printf("hours component of time_delta is too large"); |
392 | 403 | fr_sbuff_set_to_start(&our_in); |
393 | 403 | FR_SBUFF_ERROR_RETURN(&our_in); |
394 | 403 | } |
395 | | |
396 | 1.63k | if (minutes > UINT16_MAX) { |
397 | 583 | fr_strerror_printf("minutes component of time_delta is too large"); |
398 | 583 | FR_SBUFF_ERROR_RETURN(&m1); |
399 | 583 | } |
400 | 1.63k | } |
401 | | |
402 | 2.73k | if (no_trailing && !fr_sbuff_is_terminal(&our_in, tt)) goto trailing_data; |
403 | | |
404 | | /* |
405 | | * Add all the components together... |
406 | | */ |
407 | 2.73k | if (!fr_add(&integer, ((hours * 60) * 60) + (minutes * 60), seconds)) goto overflow; |
408 | | |
409 | | /* |
410 | | * Flip the sign back to negative |
411 | | */ |
412 | 2.68k | if (negative) integer = -(integer); |
413 | | |
414 | 2.68k | *out = fr_time_delta_from_sec(integer); |
415 | 2.68k | FR_SBUFF_SET_RETURN(in, &our_in); |
416 | | /* |
417 | | * Nothing fancy here it's just a time delta as an integer |
418 | | * |
419 | | * <integer>[<scale>] |
420 | | */ |
421 | 2.73k | } else { |
422 | 1.09k | if (no_trailing && !fr_sbuff_is_terminal(&our_in, tt)) goto trailing_data; |
423 | | |
424 | | /* Scale defaults to hint */ |
425 | 1.09k | if (res == FR_TIME_RES_INVALID) res = hint; |
426 | | |
427 | | /* Do the scale conversion */ |
428 | 1.09k | *out = fr_time_delta_from_integer(&overflow, integer, res); |
429 | 1.09k | if (overflow) goto overflow; |
430 | | |
431 | 1.09k | FR_SBUFF_SET_RETURN(in, &our_in); |
432 | 1.09k | } |
433 | 7.31k | } |
434 | | |
435 | | /** Create fr_time_delta_t from a string |
436 | | * |
437 | | * @param[out] out Where to write fr_time_delta_t |
438 | | * @param[in] in String to parse. |
439 | | * @param[in] inlen Length of string. |
440 | | * @param[in] hint scale for the parsing. Default is "seconds" |
441 | | * @return |
442 | | * - >0 on success. |
443 | | * - <0 on failure. |
444 | | */ |
445 | | fr_slen_t fr_time_delta_from_str(fr_time_delta_t *out, char const *in, size_t inlen, fr_time_res_t hint) |
446 | 0 | { |
447 | 0 | fr_slen_t slen; |
448 | |
|
449 | 0 | slen = fr_time_delta_from_substr(out, &FR_SBUFF_IN(in, inlen), hint, true, NULL); |
450 | 0 | if (slen < 0) return slen; |
451 | 0 | if (slen != (fr_slen_t)inlen) { |
452 | 0 | fr_strerror_const("trailing data after time_delta"); /* Shouldn't happen with no_trailing */ |
453 | 0 | return -(inlen + 1); |
454 | 0 | } |
455 | 0 | return slen; |
456 | 0 | } |
457 | | |
458 | | /** Print fr_time_delta_t to a string with an appropriate suffix |
459 | | * |
460 | | * @param[out] out Where to write the string version of the time delta. |
461 | | * @param[in] delta to print. |
462 | | * @param[in] res to print resolution with. |
463 | | * @param[in] is_unsigned whether the value should be printed unsigned. |
464 | | * @return |
465 | | * - >0 the number of bytes written to out. |
466 | | * - <0 how many additional bytes would have been required. |
467 | | */ |
468 | | fr_slen_t fr_time_delta_to_str(fr_sbuff_t *out, fr_time_delta_t delta, fr_time_res_t res, bool is_unsigned) |
469 | 0 | { |
470 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
471 | 0 | char *q; |
472 | 0 | int64_t lhs = 0; |
473 | 0 | uint64_t rhs = 0; |
474 | | |
475 | | /* |
476 | | * The % operator can return a _signed_ value. This macro is |
477 | | * correct for both positive and negative inputs. |
478 | | */ |
479 | 0 | #define MOD(a,b) (((a<0) ? (-a) : (a))%(b)) |
480 | |
|
481 | 0 | lhs = fr_time_delta_to_integer(delta, res); |
482 | 0 | rhs = MOD(fr_time_delta_unwrap(delta), fr_time_multiplier_by_res[res]); |
483 | |
|
484 | 0 | if (!is_unsigned) { |
485 | | /* |
486 | | * 0 is unsigned, but we want to print |
487 | | * "-0.1" if necessary. |
488 | | */ |
489 | 0 | if ((lhs == 0) && fr_time_delta_isneg(delta)) { |
490 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, '-'); |
491 | 0 | } |
492 | | |
493 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%" PRIi64 ".%09" PRIu64, lhs, rhs); |
494 | 0 | } else { |
495 | 0 | if (fr_time_delta_isneg(delta)) lhs = rhs = 0; |
496 | |
|
497 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%" PRIu64 ".%09" PRIu64, lhs, rhs); |
498 | 0 | } |
499 | 0 | q = fr_sbuff_current(&our_out) - 1; |
500 | | |
501 | | /* |
502 | | * Truncate trailing zeros. |
503 | | */ |
504 | 0 | while (*q == '0') *(q--) = '\0'; |
505 | | |
506 | | /* |
507 | | * If there's nothing after the decimal point, |
508 | | * trunctate the decimal point. i.e. Don't print |
509 | | * "5." |
510 | | */ |
511 | 0 | if (*q == '.') { |
512 | 0 | *q = '\0'; |
513 | 0 | } else { |
514 | 0 | q++; /* to account for q-- above */ |
515 | 0 | } |
516 | |
|
517 | 0 | FR_SBUFF_SET_RETURN(out, q); |
518 | 0 | } |
519 | | |
520 | | DIAG_OFF(format-nonliteral) |
521 | | /** Copy a time string (local timezone) to an sbuff |
522 | | * |
523 | | * @note This function will attempt to extend the sbuff by double the length of |
524 | | * the fmt string. It is recommended to either pre-extend the sbuff before |
525 | | * calling this function, or avoid using format specifiers that expand to |
526 | | * character strings longer than 4 bytes. |
527 | | * |
528 | | * @param[in] out Where to write the formatted time string. |
529 | | * @param[in] time Internal server time to convert to wallclock |
530 | | * time and copy out as formatted string. |
531 | | * @param[in] fmt Time format string. |
532 | | * @return |
533 | | * - >0 the number of bytes written to the sbuff. |
534 | | * - 0 if there's insufficient space in the sbuff. |
535 | | */ |
536 | | size_t fr_time_strftime_local(fr_sbuff_t *out, fr_time_t time, char const *fmt) |
537 | 0 | { |
538 | 0 | struct tm tm; |
539 | 0 | time_t utime = fr_time_to_sec(time); |
540 | 0 | size_t len; |
541 | |
|
542 | 0 | localtime_r(&utime, &tm); |
543 | |
|
544 | 0 | len = strftime(fr_sbuff_current(out), fr_sbuff_extend_lowat(NULL, out, strlen(fmt) * 2), fmt, &tm); |
545 | 0 | if (len == 0) return 0; |
546 | | |
547 | 0 | return fr_sbuff_advance(out, len); |
548 | 0 | } |
549 | | |
550 | | /** Copy a time string (UTC) to an sbuff |
551 | | * |
552 | | * @note This function will attempt to extend the sbuff by double the length of |
553 | | * the fmt string. It is recommended to either pre-extend the sbuff before |
554 | | * calling this function, or avoid using format specifiers that expand to |
555 | | * character strings longer than 4 bytes. |
556 | | * |
557 | | * @param[in] out Where to write the formatted time string. |
558 | | * @param[in] time Internal server time to convert to wallclock |
559 | | * time and copy out as formatted string. |
560 | | * @param[in] fmt Time format string. |
561 | | * @return |
562 | | * - >0 the number of bytes written to the sbuff. |
563 | | * - 0 if there's insufficient space in the sbuff. |
564 | | */ |
565 | | size_t fr_time_strftime_utc(fr_sbuff_t *out, fr_time_t time, char const *fmt) |
566 | 0 | { |
567 | 0 | struct tm tm; |
568 | 0 | time_t utime = fr_time_to_sec(time); |
569 | 0 | size_t len; |
570 | |
|
571 | 0 | gmtime_r(&utime, &tm); |
572 | |
|
573 | 0 | len = strftime(fr_sbuff_current(out), fr_sbuff_extend_lowat(NULL, out, strlen(fmt) * 2), fmt, &tm); |
574 | 0 | if (len == 0) return 0; |
575 | | |
576 | 0 | return fr_sbuff_advance(out, len); |
577 | 0 | } |
578 | | DIAG_ON(format-nonliteral) |
579 | | |
580 | | void fr_time_elapsed_update(fr_time_elapsed_t *elapsed, fr_time_t start, fr_time_t end) |
581 | 0 | { |
582 | 0 | fr_time_delta_t delay; |
583 | |
|
584 | 0 | if (fr_time_gteq(start, end)) { |
585 | 0 | delay = fr_time_delta_wrap(0); |
586 | 0 | } else { |
587 | 0 | delay = fr_time_sub(end, start); |
588 | 0 | } |
589 | |
|
590 | 0 | if (fr_time_delta_lt(delay, fr_time_delta_wrap(1000))) { /* microseconds */ |
591 | 0 | elapsed->array[0]++; |
592 | |
|
593 | 0 | } else if (fr_time_delta_lt(delay, fr_time_delta_wrap(10000))) { |
594 | 0 | elapsed->array[1]++; |
595 | |
|
596 | 0 | } else if (fr_time_delta_lt(delay, fr_time_delta_wrap(100000))) { |
597 | 0 | elapsed->array[2]++; |
598 | |
|
599 | 0 | } else if (fr_time_delta_lt(delay, fr_time_delta_wrap(1000000))) { /* milliseconds */ |
600 | 0 | elapsed->array[3]++; |
601 | |
|
602 | 0 | } else if (fr_time_delta_lt(delay, fr_time_delta_wrap(10000000))) { |
603 | 0 | elapsed->array[4]++; |
604 | |
|
605 | 0 | } else if (fr_time_delta_lt(delay, fr_time_delta_wrap(100000000))) { |
606 | 0 | elapsed->array[5]++; |
607 | |
|
608 | 0 | } else if (fr_time_delta_lt(delay, fr_time_delta_wrap(1000000000))) { /* seconds */ |
609 | 0 | elapsed->array[6]++; |
610 | |
|
611 | 0 | } else { /* tens of seconds or more */ |
612 | 0 | elapsed->array[7]++; |
613 | |
|
614 | 0 | } |
615 | 0 | } |
616 | | |
617 | | static const char *names[8] = { |
618 | | "1us", "10us", "100us", |
619 | | "1ms", "10ms", "100ms", |
620 | | "1s", "10s" |
621 | | }; |
622 | | |
623 | | static char const *tab_string = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; |
624 | | |
625 | | void fr_time_elapsed_fprint(FILE *fp, fr_time_elapsed_t const *elapsed, char const *prefix, int tab_offset) |
626 | 0 | { |
627 | 0 | int i; |
628 | 0 | size_t prefix_len; |
629 | |
|
630 | 0 | if (!prefix) prefix = "elapsed"; |
631 | |
|
632 | 0 | prefix_len = strlen(prefix); |
633 | |
|
634 | 0 | for (i = 0; i < 8; i++) { |
635 | 0 | size_t len; |
636 | |
|
637 | 0 | if (!elapsed->array[i]) continue; |
638 | | |
639 | 0 | len = prefix_len + 1 + strlen(names[i]); |
640 | |
|
641 | 0 | if (len >= (size_t) (tab_offset * 8)) { |
642 | 0 | fprintf(fp, "%s.%s %" PRIu64 "\n", |
643 | 0 | prefix, names[i], elapsed->array[i]); |
644 | |
|
645 | 0 | } else { |
646 | 0 | int tabs; |
647 | |
|
648 | 0 | tabs = ((tab_offset * 8) - len); |
649 | 0 | if ((tabs & 0x07) != 0) tabs += 7; |
650 | 0 | tabs >>= 3; |
651 | |
|
652 | 0 | fprintf(fp, "%s.%s%.*s%" PRIu64 "\n", |
653 | 0 | prefix, names[i], tabs, tab_string, elapsed->array[i]); |
654 | 0 | } |
655 | 0 | } |
656 | 0 | } |
657 | | |
658 | | /* |
659 | | * Based on https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html |
660 | | */ |
661 | | fr_unix_time_t fr_unix_time_from_tm(struct tm *tm) |
662 | 1.14k | { |
663 | 1.14k | static const uint16_t month_yday[12] = {0, 31, 59, 90, 120, 151, |
664 | 1.14k | 181, 212, 243, 273, 304, 334}; |
665 | | |
666 | 1.14k | uint32_t year_adj; |
667 | 1.14k | uint32_t febs; |
668 | 1.14k | uint32_t leap_days; |
669 | 1.14k | uint32_t days; |
670 | | |
671 | | /* Prevent crash if tm->tm_mon is invalid - seen in clusterfuzz */ |
672 | 1.14k | if (unlikely(tm->tm_mon >= (__typeof__(tm->tm_mon))NUM_ELEMENTS(month_yday))) return fr_unix_time_min(); |
673 | | |
674 | 1.14k | if (unlikely(tm->tm_year > 10000)) return fr_unix_time_min(); |
675 | | |
676 | 1.14k | year_adj = tm->tm_year + 4800 + 1900; /* Ensure positive year, multiple of 400. */ |
677 | 1.14k | febs = year_adj - (tm->tm_mon < 2 ? 1 : 0); /* Februaries since base. tm_mon is 0 - 11 */ |
678 | 1.14k | leap_days = 1 + (febs / 4) - (febs / 100) + (febs / 400); |
679 | | |
680 | 1.14k | days = 365 * year_adj + leap_days + month_yday[tm->tm_mon] + tm->tm_mday - 1; |
681 | | |
682 | 10.2k | #define CHECK(_x, _max) if ((tm->tm_ ## _x < 0) || (tm->tm_ ## _x >= _max)) tm->tm_ ## _x = _max - 1 |
683 | | |
684 | 1.14k | CHECK(sec, 60); |
685 | 1.14k | CHECK(min, 60); |
686 | 1.14k | CHECK(hour, 24); |
687 | 1.14k | CHECK(mday, 32); |
688 | 1.14k | CHECK(mon, 12); |
689 | 1.14k | CHECK(year, 3000); |
690 | 1.14k | CHECK(wday, 7); |
691 | 1.14k | CHECK(mon, 12); |
692 | 1.14k | CHECK(yday, 366); |
693 | | /* don't check gmtoff, it can be negative */ |
694 | | |
695 | | /* |
696 | | * 2472692 adjusts the days for Unix epoch. It is calculated as |
697 | | * (365.2425 * (4800 + 1970)) |
698 | | * |
699 | | * We REMOVE the time zone offset in order to get internal unix times in UTC. |
700 | | */ |
701 | 1.14k | return fr_unix_time_from_sec((((days - 2472692) * 86400) + (tm->tm_hour * 3600) + |
702 | 1.14k | (tm->tm_min * 60) + tm->tm_sec) - tm->tm_gmtoff); |
703 | 1.14k | } |
704 | | |
705 | | /** Scale an input time to NSEC, clamping it at max / min. |
706 | | * |
707 | | * @param t input time / time delta |
708 | | * @param hint time resolution hint |
709 | | * @return |
710 | | * - INT64_MIN on underflow |
711 | | * - 0 on invalid hint |
712 | | * - INT64_MAX on overflow |
713 | | * - otherwise a valid number, multiplied by the relevant scale, |
714 | | * so that the result is in nanoseconds. |
715 | | */ |
716 | | int64_t fr_time_scale(int64_t t, fr_time_res_t hint) |
717 | 19.6k | { |
718 | 19.6k | int64_t scale; |
719 | | |
720 | 19.6k | switch (hint) { |
721 | 13.7k | case FR_TIME_RES_SEC: |
722 | 13.7k | scale = NSEC; |
723 | 13.7k | break; |
724 | | |
725 | 0 | case FR_TIME_RES_MSEC: |
726 | 0 | scale = 1000000; |
727 | 0 | break; |
728 | | |
729 | 5.89k | case FR_TIME_RES_USEC: |
730 | 5.89k | scale = 1000; |
731 | 5.89k | break; |
732 | | |
733 | 0 | case FR_TIME_RES_NSEC: |
734 | 0 | return t; |
735 | | |
736 | 0 | default: |
737 | 0 | return 0; |
738 | 19.6k | } |
739 | | |
740 | 19.6k | if (t < 0) { |
741 | 5.59k | if (t < (INT64_MIN / scale)) { |
742 | 197 | return INT64_MIN; |
743 | 197 | } |
744 | 14.0k | } else if (t > 0) { |
745 | 12.0k | if (t > (INT64_MAX / scale)) { |
746 | 136 | return INT64_MAX; |
747 | 136 | } |
748 | 12.0k | } |
749 | | |
750 | 19.3k | return t * scale; |
751 | 19.6k | } |
752 | | |
753 | | |
754 | | /* |
755 | | * Sort of strtok/strsep function. |
756 | | */ |
757 | | static char *mystrtok(char **ptr, char const *sep) |
758 | 16.1k | { |
759 | 16.1k | char *res; |
760 | | |
761 | 16.1k | if (**ptr == '\0') return NULL; |
762 | | |
763 | 13.1k | while (**ptr && strchr(sep, **ptr)) (*ptr)++; |
764 | | |
765 | 12.5k | if (**ptr == '\0') return NULL; |
766 | | |
767 | 12.5k | res = *ptr; |
768 | 76.2k | while (**ptr && strchr(sep, **ptr) == NULL) (*ptr)++; |
769 | | |
770 | 12.5k | if (**ptr != '\0') *(*ptr)++ = '\0'; |
771 | | |
772 | 12.5k | return res; |
773 | 12.5k | } |
774 | | |
775 | | /* |
776 | | * Helper function to get a 2-digit date. With a maximum value, |
777 | | * and a terminating character. |
778 | | */ |
779 | | static int get_part(char **str, int *date, int min, int max, char term, char const *name) |
780 | 4.97k | { |
781 | 4.97k | char *p = *str; |
782 | | |
783 | 4.97k | if (!isdigit((uint8_t) *p) || !isdigit((uint8_t) p[1])) return -1; |
784 | 4.64k | *date = (p[0] - '0') * 10 + (p[1] - '0'); |
785 | | |
786 | 4.64k | if (*date < min) { |
787 | 123 | fr_strerror_printf("Invalid %s (too small)", name); |
788 | 123 | return -1; |
789 | 123 | } |
790 | | |
791 | 4.52k | if (*date > max) { |
792 | 59 | fr_strerror_printf("Invalid %s (too large)", name); |
793 | 59 | return -1; |
794 | 59 | } |
795 | | |
796 | 4.46k | p += 2; |
797 | 4.46k | if (!term) { |
798 | 893 | *str = p; |
799 | 893 | return 0; |
800 | 893 | } |
801 | | |
802 | 3.57k | if (*p != term) { |
803 | 96 | fr_strerror_printf("Expected '%c' after %s, got '%c'", |
804 | 96 | term, name, *p); |
805 | 96 | return -1; |
806 | 96 | } |
807 | 3.47k | p++; |
808 | | |
809 | 3.47k | *str = p; |
810 | 3.47k | return 0; |
811 | 3.57k | } |
812 | | |
813 | | static char const *months[] = { |
814 | | "jan", "feb", "mar", "apr", "may", "jun", |
815 | | "jul", "aug", "sep", "oct", "nov", "dec" }; |
816 | | |
817 | | |
818 | | /** Convert string in various formats to a fr_unix_time_t |
819 | | * |
820 | | * @param date_str input date string. |
821 | | * @param date time_t to write result to. |
822 | | * @param[in] hint scale for the parsing. Default is "seconds" |
823 | | * @return |
824 | | * - 0 on success. |
825 | | * - -1 on failure. |
826 | | */ |
827 | | int fr_unix_time_from_str(fr_unix_time_t *date, char const *date_str, fr_time_res_t hint) |
828 | 6.24k | { |
829 | 6.24k | int i; |
830 | 6.24k | int64_t tmp; |
831 | 6.24k | struct tm *tm, s_tm; |
832 | 6.24k | char buf[64]; |
833 | 6.24k | char *p; |
834 | 6.24k | char *f[4]; |
835 | 6.24k | char *tail = NULL; |
836 | 6.24k | unsigned long l; |
837 | 6.24k | fr_time_delta_t gmt_delta = fr_time_delta_wrap(0); |
838 | | |
839 | | /* |
840 | | * Test for unix timestamp, which is just a number and |
841 | | * nothing else. |
842 | | */ |
843 | 6.24k | tmp = strtoul(date_str, &tail, 10); |
844 | 6.24k | if (*tail == '\0') { |
845 | 851 | *date = fr_unix_time_from_nsec(fr_time_scale(tmp, hint)); |
846 | 851 | return 0; |
847 | 851 | } |
848 | | |
849 | 5.39k | tm = &s_tm; |
850 | 5.39k | memset(tm, 0, sizeof(*tm)); |
851 | 5.39k | tm->tm_isdst = -1; /* don't know, and don't care about DST */ |
852 | | |
853 | | /* |
854 | | * Check for RFC 3339 dates. Note that we only support |
855 | | * dates in a ~1000 year period. If the server is being |
856 | | * used after 3000AD, someone can patch it then. |
857 | | * |
858 | | * %Y-%m-%dT%H:%M:%S |
859 | | * [.%d] sub-seconds |
860 | | * Z | (+/-)%H:%M time zone offset |
861 | | * |
862 | | */ |
863 | 5.39k | if ((tmp > 1900) && (tmp < 3000) && *tail == '-') { |
864 | 1.21k | unsigned long subseconds; |
865 | 1.21k | int tz, tz_hour, tz_min; |
866 | | |
867 | 1.21k | p = tail + 1; |
868 | 1.21k | s_tm.tm_year = tmp - 1900; /* 'struct tm' starts years in 1900 */ |
869 | | |
870 | 1.21k | if (get_part(&p, &s_tm.tm_mon, 1, 12, '-', "month") < 0) return -1; |
871 | 873 | s_tm.tm_mon--; /* ISO is 1..12, where 'struct tm' is 0..11 */ |
872 | | |
873 | 873 | if (get_part(&p, &s_tm.tm_mday, 1, 31, 'T', "day") < 0) return -1; |
874 | 820 | if (get_part(&p, &s_tm.tm_hour, 0, 23, ':', "hour") < 0) return -1; |
875 | 786 | if (get_part(&p, &s_tm.tm_min, 0, 59, ':', "minute") < 0) return -1; |
876 | 746 | if (get_part(&p, &s_tm.tm_sec, 0, 60, '\0', "seconds") < 0) return -1; |
877 | | |
878 | 696 | if (*p == '.') { |
879 | 367 | p++; |
880 | 367 | subseconds = strtoul(p, &tail, 10); |
881 | 367 | if (subseconds > NSEC) { |
882 | 149 | fr_strerror_const("Invalid nanosecond specifier"); |
883 | 149 | return -1; |
884 | 149 | } |
885 | | |
886 | | /* |
887 | | * Scale subseconds to nanoseconds by how |
888 | | * many digits were parsed/ |
889 | | */ |
890 | 218 | if ((tail - p) < 9) { |
891 | 1.05k | for (i = 0; i < 9 - (tail -p); i++) { |
892 | 914 | subseconds *= 10; |
893 | 914 | } |
894 | 140 | } |
895 | | |
896 | 218 | p = tail; |
897 | 329 | } else { |
898 | 329 | subseconds = 0; |
899 | 329 | } |
900 | | |
901 | | /* |
902 | | * Time zone is GMT. Leave well enough |
903 | | * alone. |
904 | | */ |
905 | 547 | if (*p == 'Z') { |
906 | 46 | if (p[1] != '\0') { |
907 | 26 | fr_strerror_printf("Unexpected text '%c' after time zone", p[1]); |
908 | 26 | return -1; |
909 | 26 | } |
910 | 20 | tz = 0; |
911 | 20 | goto done; |
912 | 46 | } |
913 | | |
914 | 501 | if ((*p != '+') && (*p != '-')) { |
915 | 215 | fr_strerror_printf("Invalid time zone specifier '%c'", *p); |
916 | 215 | return -1; |
917 | 215 | } |
918 | 286 | tail = p; /* remember sign for later */ |
919 | 286 | p++; |
920 | | |
921 | 286 | if (get_part(&p, &tz_hour, 0, 23, ':', "hour in time zone") < 0) return -1; |
922 | 253 | if (get_part(&p, &tz_min, 0, 59, '\0', "minute in time zone") < 0) return -1; |
923 | | |
924 | 197 | if (*p != '\0') { |
925 | 67 | fr_strerror_printf("Unexpected text '%c' after time zone", *p); |
926 | 67 | return -1; |
927 | 67 | } |
928 | | |
929 | | /* |
930 | | * We set the time zone, but the timegm() |
931 | | * function ignores it. Note also that mktime() |
932 | | * ignores it too, and treats the time zone as |
933 | | * local. |
934 | | * |
935 | | * We can't store this value in s_tm.gtmoff, |
936 | | * because the timegm() function helpfully zeros |
937 | | * it out. |
938 | | * |
939 | | * So insyead of using stupid C library |
940 | | * functions, we just roll our own. |
941 | | */ |
942 | 130 | tz = tz_hour * 3600 + tz_min; |
943 | 130 | if (*tail == '-') tz *= -1; |
944 | | |
945 | 150 | done: |
946 | | /* |
947 | | * We REMOVE the time zone offset in order to get internal unix times in UTC. |
948 | | */ |
949 | 150 | tm->tm_gmtoff = -tz; |
950 | 150 | *date = fr_unix_time_add(fr_unix_time_from_tm(tm), fr_time_delta_wrap(subseconds)); |
951 | 150 | return 0; |
952 | 130 | } |
953 | | |
954 | | /* |
955 | | * Try to parse dates via locale-specific names, |
956 | | * using the same format string as strftime(). |
957 | | * |
958 | | * If that fails, then we fall back to our parsing |
959 | | * routine, which is much more forgiving. |
960 | | */ |
961 | | |
962 | | #ifdef __APPLE__ |
963 | | /* |
964 | | * OSX "man strptime" says it only accepts the local time zone, and GMT. |
965 | | * |
966 | | * However, when printing dates via strftime(), it prints |
967 | | * "UTC" instead of "GMT". So... we have to fix it up |
968 | | * for stupid nonsense. |
969 | | */ |
970 | | { |
971 | | char const *tz = strstr(date_str, "UTC"); |
972 | | if (tz) { |
973 | | char *my_str; |
974 | | |
975 | | my_str = talloc_strdup(NULL, date_str); |
976 | | if (my_str) { |
977 | | p = my_str + (tz - date_str); |
978 | | memcpy(p, "GMT", 3); |
979 | | |
980 | | p = strptime(my_str, "%b %e %Y %H:%M:%S %Z", tm); |
981 | | if (p && (*p == '\0')) { |
982 | | talloc_free(my_str); |
983 | | *date = fr_unix_time_from_tm(tm); |
984 | | return 0; |
985 | | } |
986 | | talloc_free(my_str); |
987 | | } |
988 | | } |
989 | | } |
990 | | #endif |
991 | | |
992 | 4.17k | p = strptime(date_str, "%b %e %Y %H:%M:%S %Z", tm); |
993 | 4.17k | if (p && (*p == '\0')) { |
994 | 131 | *date = fr_unix_time_from_tm(tm); |
995 | 131 | return 0; |
996 | 131 | } |
997 | | |
998 | 4.04k | strlcpy(buf, date_str, sizeof(buf)); |
999 | | |
1000 | 4.04k | p = buf; |
1001 | 4.04k | f[0] = mystrtok(&p, " \t"); |
1002 | 4.04k | f[1] = mystrtok(&p, " \t"); |
1003 | 4.04k | f[2] = mystrtok(&p, " \t"); |
1004 | 4.04k | f[3] = mystrtok(&p, " \t"); /* may, or may not, be present */ |
1005 | 4.04k | if (!f[0] || !f[1] || !f[2]) { |
1006 | 794 | fr_strerror_const("Too few fields"); |
1007 | 794 | return -1; |
1008 | 794 | } |
1009 | | |
1010 | | /* |
1011 | | * Try to parse the time zone. If it's GMT / UTC or a |
1012 | | * local time zone we're OK. |
1013 | | * |
1014 | | * Otherwise, ignore errors and assume GMT. |
1015 | | */ |
1016 | 3.25k | if (*p != '\0') { |
1017 | 1.09k | fr_skip_whitespace(p); |
1018 | 1.09k | (void) fr_time_delta_from_time_zone(p, &gmt_delta); |
1019 | 1.09k | } |
1020 | | |
1021 | | /* |
1022 | | * The time has a colon, where nothing else does. |
1023 | | * So if we find it, bubble it to the back of the list. |
1024 | | */ |
1025 | 3.25k | if (f[3]) { |
1026 | 5.96k | for (i = 0; i < 3; i++) { |
1027 | 4.74k | if (strchr(f[i], ':')) { |
1028 | 603 | p = f[3]; |
1029 | 603 | f[3] = f[i]; |
1030 | 603 | f[i] = p; |
1031 | 603 | break; |
1032 | 603 | } |
1033 | 4.74k | } |
1034 | 1.82k | } |
1035 | | |
1036 | | /* |
1037 | | * The month is text, which allows us to find it easily. |
1038 | | */ |
1039 | 3.25k | tm->tm_mon = 12; |
1040 | 13.0k | for (i = 0; i < 3; i++) { |
1041 | 9.76k | if (isalpha((uint8_t) *f[i])) { |
1042 | 3.42k | int j; |
1043 | | |
1044 | | /* |
1045 | | * Bubble the month to the front of the list |
1046 | | */ |
1047 | 3.42k | p = f[0]; |
1048 | 3.42k | f[0] = f[i]; |
1049 | 3.42k | f[i] = p; |
1050 | | |
1051 | 21.9k | for (j = 0; j < 12; j++) { |
1052 | 21.1k | if (strncasecmp(months[j], f[0], 3) == 0) { |
1053 | 2.65k | tm->tm_mon = j; |
1054 | 2.65k | break; |
1055 | 2.65k | } |
1056 | 21.1k | } |
1057 | 3.42k | } |
1058 | 9.76k | } |
1059 | | |
1060 | | /* month not found? */ |
1061 | 3.25k | if (tm->tm_mon == 12) { |
1062 | 628 | fr_strerror_const("No month found"); |
1063 | 628 | return -1; |
1064 | 628 | } |
1065 | | |
1066 | | /* |
1067 | | * Check for invalid text, or invalid trailing text. |
1068 | | */ |
1069 | 2.62k | l = strtoul(f[1], &tail, 10); |
1070 | 2.62k | if ((l == ULONG_MAX) || (*tail != '\0')) { |
1071 | 262 | fr_strerror_const("Invalid year string"); |
1072 | 262 | return -1; |
1073 | 262 | } |
1074 | 2.36k | tm->tm_year = l; |
1075 | | |
1076 | 2.36k | l = strtoul(f[2], &tail, 10); |
1077 | 2.36k | if ((l == ULONG_MAX) || (*tail != '\0')) { |
1078 | 317 | fr_strerror_const("Invalid day of month string"); |
1079 | 317 | return -1; |
1080 | 317 | } |
1081 | 2.04k | tm->tm_mday = l; |
1082 | | |
1083 | 2.04k | if (tm->tm_year >= 1900) { |
1084 | 679 | tm->tm_year -= 1900; |
1085 | | |
1086 | 1.36k | } else { |
1087 | | /* |
1088 | | * We can't use 2-digit years any more, they make it |
1089 | | * impossible to tell what's the day, and what's the year. |
1090 | | */ |
1091 | 1.36k | if (tm->tm_mday < 1900) { |
1092 | 423 | fr_strerror_const("Invalid year < 1900"); |
1093 | 423 | return -1; |
1094 | 423 | } |
1095 | | |
1096 | | /* |
1097 | | * Swap the year and the day. |
1098 | | */ |
1099 | 945 | i = tm->tm_year; |
1100 | 945 | tm->tm_year = tm->tm_mday - 1900; |
1101 | 945 | tm->tm_mday = i; |
1102 | 945 | } |
1103 | | |
1104 | 1.62k | if (tm->tm_year > 10000) { |
1105 | 230 | fr_strerror_const("Invalid value for year"); |
1106 | 230 | return -1; |
1107 | 230 | } |
1108 | | |
1109 | | /* |
1110 | | * If the day is out of range, die. |
1111 | | */ |
1112 | 1.39k | if ((tm->tm_mday < 1) || (tm->tm_mday > 31)) { |
1113 | 303 | fr_strerror_const("Invalid value for day of month"); |
1114 | 303 | return -1; |
1115 | 303 | } |
1116 | | |
1117 | | /* |
1118 | | * There may be %H:%M:%S. Parse it in a hacky way. |
1119 | | */ |
1120 | 1.09k | if (f[3]) { |
1121 | 841 | f[0] = f[3]; /* HH */ |
1122 | 841 | f[1] = strchr(f[0], ':'); /* find : separator */ |
1123 | 841 | if (!f[1]) { |
1124 | 232 | fr_strerror_const("No ':' after hour"); |
1125 | 232 | return -1; |
1126 | 232 | } |
1127 | | |
1128 | 609 | *(f[1]++) = '\0'; /* nuke it, and point to MM:SS */ |
1129 | | |
1130 | 609 | f[2] = strchr(f[1], ':'); /* find : separator */ |
1131 | 609 | if (f[2]) { |
1132 | 247 | *(f[2]++) = '\0'; /* nuke it, and point to SS */ |
1133 | 247 | tm->tm_sec = atoi(f[2]); |
1134 | 247 | } /* else leave it as zero */ |
1135 | | |
1136 | 609 | tm->tm_hour = atoi(f[0]); |
1137 | 609 | tm->tm_min = atoi(f[1]); |
1138 | 609 | } |
1139 | | |
1140 | 859 | *date = fr_unix_time_add(fr_unix_time_from_tm(tm), gmt_delta); |
1141 | | |
1142 | 859 | return 0; |
1143 | 1.09k | } |
1144 | | |
1145 | | /** Convert unix time to string |
1146 | | * |
1147 | | * @param[out] out Where to write the string. |
1148 | | * @param[in] time to convert. |
1149 | | * @param[in] res What base resolution to print the time as. |
1150 | | * @return |
1151 | | * - 0 on success. |
1152 | | * - -1 on failure. |
1153 | | */ |
1154 | | fr_slen_t fr_unix_time_to_str(fr_sbuff_t *out, fr_unix_time_t time, fr_time_res_t res) |
1155 | 0 | { |
1156 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
1157 | 0 | int64_t subseconds; |
1158 | 0 | time_t t; |
1159 | 0 | struct tm s_tm; |
1160 | 0 | size_t len; |
1161 | 0 | char buf[128]; |
1162 | |
|
1163 | 0 | t = fr_unix_time_to_sec(time); |
1164 | 0 | (void) gmtime_r(&t, &s_tm); |
1165 | |
|
1166 | 0 | if (res == FR_TIME_RES_SEC) { |
1167 | 0 | len = strftime(buf, sizeof(buf), "%b %e %Y %H:%M:%S UTC", &s_tm); |
1168 | 0 | FR_SBUFF_IN_BSTRNCPY_RETURN(&our_out, buf, len); |
1169 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
1170 | 0 | } |
1171 | | |
1172 | 0 | len = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &s_tm); |
1173 | 0 | FR_SBUFF_IN_BSTRNCPY_RETURN(&our_out, buf, len); |
1174 | 0 | subseconds = fr_unix_time_unwrap(time) % NSEC; |
1175 | | |
1176 | | /* |
1177 | | * Use RFC 3339 format, which is a |
1178 | | * profile of ISO8601. The ISO standard |
1179 | | * allows a much more complex set of date |
1180 | | * formats. The RFC is much stricter. |
1181 | | */ |
1182 | 0 | switch (res) { |
1183 | 0 | case FR_TIME_RES_INVALID: |
1184 | 0 | case FR_TIME_RES_YEAR: |
1185 | 0 | case FR_TIME_RES_MONTH: |
1186 | 0 | case FR_TIME_RES_WEEK: |
1187 | 0 | case FR_TIME_RES_DAY: |
1188 | 0 | case FR_TIME_RES_HOUR: |
1189 | 0 | case FR_TIME_RES_MIN: |
1190 | 0 | case FR_TIME_RES_SEC: |
1191 | 0 | break; |
1192 | | |
1193 | 0 | case FR_TIME_RES_CSEC: |
1194 | 0 | subseconds /= (NSEC / CSEC); |
1195 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, ".%02" PRIi64, subseconds); |
1196 | 0 | break; |
1197 | | |
1198 | 0 | case FR_TIME_RES_MSEC: |
1199 | 0 | subseconds /= (NSEC / MSEC); |
1200 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, ".%03" PRIi64, subseconds); |
1201 | 0 | break; |
1202 | | |
1203 | 0 | case FR_TIME_RES_USEC: |
1204 | 0 | subseconds /= (NSEC / USEC); |
1205 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, ".%06" PRIi64, subseconds); |
1206 | 0 | break; |
1207 | | |
1208 | 0 | case FR_TIME_RES_NSEC: |
1209 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, ".%09" PRIi64, subseconds); |
1210 | 0 | break; |
1211 | 0 | } |
1212 | | |
1213 | | /* |
1214 | | * And time zone. |
1215 | | */ |
1216 | 0 | if (s_tm.tm_gmtoff != 0) { |
1217 | 0 | int hours, minutes; |
1218 | |
|
1219 | 0 | hours = s_tm.tm_gmtoff / 3600; |
1220 | 0 | minutes = (s_tm.tm_gmtoff / 60) % 60; |
1221 | |
|
1222 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "%+03d:%02u", hours, minutes); |
1223 | 0 | } else { |
1224 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, 'Z'); |
1225 | 0 | } |
1226 | | |
1227 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
1228 | 0 | } |
1229 | | |
1230 | | /** Get the offset to gmt. |
1231 | | * |
1232 | | */ |
1233 | | fr_time_delta_t fr_time_gmtoff(void) |
1234 | 0 | { |
1235 | 0 | return fr_time_delta_wrap(gmtoff[isdst]); |
1236 | 0 | } |
1237 | | |
1238 | | /** Whether or not we're daylight savings. |
1239 | | * |
1240 | | */ |
1241 | | bool fr_time_is_dst(void) |
1242 | 0 | { |
1243 | 0 | return isdst; |
1244 | 0 | } |