/src/wireshark/wsutil/nstime.c
Line | Count | Source |
1 | | /* nstime.c |
2 | | * Routines for manipulating nstime_t structures |
3 | | * |
4 | | * Copyright (c) 2005 MX Telecom Ltd. <richardv@mxtelecom.com> |
5 | | * |
6 | | * Wireshark - Network traffic analyzer |
7 | | * By Gerald Combs <gerald@wireshark.org> |
8 | | * Copyright 1998 Gerald Combs |
9 | | * |
10 | | * SPDX-License-Identifier: GPL-2.0-or-later |
11 | | */ |
12 | | |
13 | | #include "nstime.h" |
14 | | |
15 | | #include <stdio.h> |
16 | | #include <string.h> |
17 | | #include <errno.h> |
18 | | |
19 | | #include "epochs.h" |
20 | | #include "time_util.h" |
21 | | #include "to_str.h" |
22 | | #include "strtoi.h" |
23 | | #include "ws_assert.h" |
24 | | |
25 | | /* this is #defined so that we can clearly see that we have the right number of |
26 | | zeros, rather than as a guard against the number of nanoseconds in a second |
27 | | changing ;) */ |
28 | 6.77k | #define NS_PER_S 1000000000 |
29 | | |
30 | | /* set the given nstime_t to zero */ |
31 | | void nstime_set_zero(nstime_t *nstime) |
32 | 471k | { |
33 | 471k | nstime->secs = 0; |
34 | 471k | nstime->nsecs = 0; |
35 | 471k | } |
36 | | |
37 | | /* is the given nstime_t currently zero? */ |
38 | | bool nstime_is_zero(const nstime_t *nstime) |
39 | 20.4k | { |
40 | 20.4k | return nstime->secs == 0 && nstime->nsecs == 0; |
41 | 20.4k | } |
42 | | |
43 | | /* is the given nstime_t currently negative? */ |
44 | | bool nstime_is_negative(const nstime_t *nstime) |
45 | 0 | { |
46 | 0 | return nstime->secs < 0 || nstime->nsecs < 0; |
47 | 0 | } |
48 | | |
49 | | /* set the given nstime_t to (0,maxint) to mark it as "unset" |
50 | | * That way we can find the first frame even when a timestamp |
51 | | * is zero (fix for bug 1056) |
52 | | */ |
53 | | void nstime_set_unset(nstime_t *nstime) |
54 | 3.60k | { |
55 | 3.60k | nstime->secs = 0; |
56 | 3.60k | nstime->nsecs = INT_MAX; |
57 | 3.60k | } |
58 | | |
59 | | /* is the given nstime_t currently (0,maxint)? */ |
60 | | bool nstime_is_unset(const nstime_t *nstime) |
61 | 1.48M | { |
62 | 1.48M | if(nstime->secs == 0 && nstime->nsecs == INT_MAX) { |
63 | 4 | return true; |
64 | 1.48M | } else { |
65 | 1.48M | return false; |
66 | 1.48M | } |
67 | 1.48M | } |
68 | | |
69 | | |
70 | | /** function: nstime_copy |
71 | | * |
72 | | * a = b |
73 | | */ |
74 | | void nstime_copy(nstime_t *a, const nstime_t *b) |
75 | 410 | { |
76 | 410 | a->secs = b->secs; |
77 | 410 | a->nsecs = b->nsecs; |
78 | 410 | } |
79 | | |
80 | | /* |
81 | | * function: nstime_delta |
82 | | * delta = b - a |
83 | | */ |
84 | | |
85 | | void nstime_delta(nstime_t *delta, const nstime_t *b, const nstime_t *a ) |
86 | 740k | { |
87 | 740k | if (G_UNLIKELY(nstime_is_unset(a) || nstime_is_unset(b))) { |
88 | 0 | nstime_set_unset(delta); |
89 | 0 | return; |
90 | 0 | } |
91 | | /* If calculations with the nanoseconds overflow, the nanoseconds are |
92 | | * outside the valid range, probably because the struct member was set |
93 | | * directly from packet data without checking the value. */ |
94 | 740k | if (ckd_sub(&delta->nsecs, b->nsecs, a->nsecs)) { |
95 | 0 | ws_warn_badarg("Nanoseconds outside valid range."); |
96 | 0 | delta->nsecs = 0; |
97 | 0 | errno = EINVAL; |
98 | 0 | } |
99 | 740k | if (b->secs == a->secs) { |
100 | | /* The seconds part of b is the same as the seconds part of a, so if |
101 | | the nanoseconds part of the first time is less than the nanoseconds |
102 | | part of a, b is before a. The nanoseconds part of the delta should |
103 | | just be the difference between the nanoseconds part of b and the |
104 | | nanoseconds part of a; don't adjust the seconds part of the delta, |
105 | | as it's OK if the nanoseconds part is negative, and an overflow |
106 | | should never result. */ |
107 | 739k | delta->secs = 0; |
108 | 739k | } else if (b->secs < a->secs) { |
109 | | /* The seconds part of b is less than the seconds part of a, so b is |
110 | | before a. |
111 | | |
112 | | Both the "seconds" and "nanoseconds" value of the delta |
113 | | should have the same sign, so if the difference between the |
114 | | nanoseconds values would be *positive*, subtract 1,000,000,000 |
115 | | from it, and add one to the seconds value. */ |
116 | 667 | delta->secs = b->secs; |
117 | 667 | if(delta->nsecs > 0) { |
118 | 0 | delta->nsecs -= NS_PER_S; |
119 | | /* This can never overflow, because b is less than a. */ |
120 | 0 | delta->secs++; |
121 | 0 | } |
122 | 667 | if (ckd_sub(&delta->secs, delta->secs, a->secs)) { |
123 | | /* Because b is less than a, the correct answer is smaller than |
124 | | * representable. Clamp to the minimum valid value. */ |
125 | 0 | delta->secs = TIME_T_MIN; |
126 | 0 | delta->nsecs = 1 - NS_PER_S; |
127 | 0 | errno = ERANGE; |
128 | 0 | } |
129 | 667 | } else { |
130 | 457 | delta->secs = b->secs; |
131 | 457 | if(delta->nsecs < 0) { |
132 | 79 | delta->nsecs += NS_PER_S; |
133 | | /* This can never overflow, because b is greater than a. */ |
134 | 79 | delta->secs--; |
135 | 79 | } |
136 | 457 | if (ckd_sub(&delta->secs, delta->secs, a->secs)) { |
137 | | /* Because b is greater than a, the correct answer is greater |
138 | | * than representable. Clamp to the maximum valid value. */ |
139 | 1 | delta->secs = TIME_T_MAX; |
140 | 1 | delta->nsecs = NS_PER_S - 1; |
141 | 1 | errno = ERANGE; |
142 | 1 | } |
143 | 457 | } |
144 | 740k | } |
145 | | |
146 | | /* |
147 | | * function: nstime_sum |
148 | | * sum = a + b |
149 | | */ |
150 | | |
151 | | void nstime_sum(nstime_t *sum, const nstime_t *a, const nstime_t *b) |
152 | 48 | { |
153 | 48 | if (G_UNLIKELY(nstime_is_unset(a) || nstime_is_unset(b))) { |
154 | 0 | nstime_set_unset(sum); |
155 | 0 | return; |
156 | 0 | } |
157 | 48 | if (ckd_add(&sum->nsecs, a->nsecs, b->nsecs)) { |
158 | 0 | ws_warn_badarg("Nanoseconds outside valid range."); |
159 | 0 | sum->nsecs = 0; |
160 | 0 | errno = EINVAL; |
161 | 0 | } |
162 | 48 | if (ckd_add(&sum->secs, a->secs, b->secs)) { |
163 | 0 | if (a->secs > 0) { |
164 | 0 | sum->secs = TIME_T_MAX; |
165 | 0 | sum->nsecs = NS_PER_S - 1; |
166 | 0 | } else { |
167 | 0 | sum->secs = TIME_T_MIN; |
168 | 0 | sum->nsecs = 1 - NS_PER_S; |
169 | 0 | } |
170 | 0 | errno = ERANGE; |
171 | 0 | return; |
172 | 0 | } |
173 | 48 | if(sum->nsecs>=NS_PER_S || (sum->nsecs>0 && sum->secs<0)){ |
174 | 8 | sum->nsecs-=NS_PER_S; |
175 | 8 | if (ckd_add(&sum->secs, sum->secs, 1)) { |
176 | 0 | sum->secs = TIME_T_MAX; |
177 | 0 | sum->nsecs = NS_PER_S - 1; |
178 | 0 | errno = ERANGE; |
179 | 0 | return; |
180 | 0 | } |
181 | 40 | } else if(sum->nsecs<=-NS_PER_S || (sum->nsecs<0 && sum->secs>0)) { |
182 | 0 | sum->nsecs+=NS_PER_S; |
183 | 0 | if (ckd_sub(&sum->secs, sum->secs, 1)) { |
184 | 0 | sum->secs = TIME_T_MIN; |
185 | 0 | sum->nsecs = 1 - NS_PER_S; |
186 | 0 | errno = ERANGE; |
187 | 0 | return; |
188 | 0 | } |
189 | 0 | } |
190 | 48 | } |
191 | | |
192 | | /* |
193 | | * function: nstime_cmp |
194 | | * |
195 | | * a > b : > 0 |
196 | | * a = b : 0 |
197 | | * a < b : < 0 |
198 | | */ |
199 | | |
200 | | int nstime_cmp (const nstime_t *a, const nstime_t *b ) |
201 | 1.22k | { |
202 | 1.22k | if (G_UNLIKELY(nstime_is_unset(a))) { |
203 | 0 | if (G_UNLIKELY(nstime_is_unset(b))) { |
204 | 0 | return 0; /* "no time stamp" is "equal" to "no time stamp" */ |
205 | 0 | } else { |
206 | 0 | return -1; /* and is less than all time stamps */ |
207 | 0 | } |
208 | 1.22k | } else { |
209 | 1.22k | if (G_UNLIKELY(nstime_is_unset(b))) { |
210 | 0 | return 1; |
211 | 0 | } |
212 | 1.22k | } |
213 | 1.22k | if (a->secs == b->secs) { |
214 | 256 | if (a->nsecs == b->nsecs) { |
215 | 177 | return 0; |
216 | 177 | } else { |
217 | 79 | return a->nsecs > b->nsecs ? 1 : -1; |
218 | 79 | } |
219 | 972 | } else { |
220 | 972 | return a->secs > b->secs ? 1 : -1; |
221 | | #if 0 |
222 | | /* The old behavior, in the common case, returned the difference in |
223 | | * seconds, which could overflow. That was never promised, but if it |
224 | | * is desired, a way to handle it with C23 checked arithmetic could be |
225 | | * (ignoring hypothetical platforms with floating-point time_t): */ |
226 | | int ret; |
227 | | if (ckd_sub(&ret, a->secs, b->secs)) { |
228 | | /* We subtract b->secs, so if it's positive, there was underflow, |
229 | | * and vice versa. */ |
230 | | ret = b->secs > 0 ? INT_MIN : INT_MAX; |
231 | | } |
232 | | return ret; |
233 | | #endif |
234 | 972 | } |
235 | 1.22k | } |
236 | | |
237 | | unsigned nstime_hash(const nstime_t *nstime) |
238 | 0 | { |
239 | 0 | int64_t val1 = (int64_t)nstime->secs; |
240 | |
|
241 | 0 | return g_int64_hash(&val1) ^ g_int_hash(&nstime->nsecs); |
242 | 0 | } |
243 | | |
244 | | /* |
245 | | * function: nstime_to_msec |
246 | | * converts nstime to double, time base is milli seconds |
247 | | */ |
248 | | |
249 | | double nstime_to_msec(const nstime_t *nstime) |
250 | 0 | { |
251 | 0 | return ((double)nstime->secs*1000 + (double)nstime->nsecs/1000000); |
252 | 0 | } |
253 | | |
254 | | /* |
255 | | * function: nstime_to_sec |
256 | | * converts nstime to double, time base is seconds |
257 | | */ |
258 | | |
259 | | double nstime_to_sec(const nstime_t *nstime) |
260 | 6.51k | { |
261 | 6.51k | return ((double)nstime->secs + (double)nstime->nsecs/NS_PER_S); |
262 | 6.51k | } |
263 | | |
264 | | void nstime_rounded(nstime_t *a, const nstime_t *b, ws_tsprec_e prec) |
265 | 0 | { |
266 | 0 | nstime_t round = NSTIME_INIT_ZERO; |
267 | 0 | unsigned dv = NS_PER_S; |
268 | 0 | for (ws_tsprec_e i = WS_TSPREC_SEC; i < prec; i++) { |
269 | 0 | dv /= 10; |
270 | 0 | } |
271 | 0 | round.nsecs = 5 * (dv / 10); |
272 | 0 | nstime_sum(a, b, &round); |
273 | 0 | a->nsecs = (a->nsecs / dv) * dv; |
274 | 0 | } |
275 | | |
276 | | static bool |
277 | | common_filetime_to_nstime(nstime_t *nstime, uint64_t ftsecs, int nsecs) |
278 | 1.32k | { |
279 | 1.32k | int64_t secs; |
280 | | |
281 | | /* |
282 | | * Shift the seconds from the Windows epoch to the UN*X epoch. |
283 | | * ftsecs's value should fit in a 64-bit signed variable, as |
284 | | * ftsecs is derived from a 64-bit fractions-of-a-second value, |
285 | | * and is far from the maximum 64-bit signed value, and |
286 | | * EPOCH_DELTA_1601_01_01_00_00_00_UTC is also far from the |
287 | | * maximum 64-bit signed value, so the difference between them |
288 | | * should also fit in a 64-bit signed value. |
289 | | */ |
290 | 1.32k | secs = (int64_t)ftsecs - EPOCH_DELTA_1601_01_01_00_00_00_UTC; |
291 | | |
292 | 1.32k | if (!(TIME_T_MIN <= secs && secs <= TIME_T_MAX)) { |
293 | | /* The result won't fit in a time_t */ |
294 | 0 | return false; |
295 | 0 | } |
296 | | |
297 | | /* |
298 | | * Get the time as seconds and nanoseconds. |
299 | | */ |
300 | 1.32k | nstime->secs = (time_t) secs; |
301 | 1.32k | nstime->nsecs = nsecs; |
302 | 1.32k | return true; |
303 | 1.32k | } |
304 | | |
305 | | /* |
306 | | * function: filetime_to_nstime |
307 | | * converts a Windows FILETIME value to an nstime_t |
308 | | * returns true if the conversion succeeds, false if it doesn't |
309 | | * (for example, with a 32-bit time_t, the time overflows or |
310 | | * underflows time_t) |
311 | | */ |
312 | | bool |
313 | | filetime_to_nstime(nstime_t *nstime, uint64_t filetime) |
314 | 1.32k | { |
315 | 1.32k | uint64_t ftsecs; |
316 | 1.32k | int nsecs; |
317 | | |
318 | | /* |
319 | | * Split into seconds and tenths of microseconds, and |
320 | | * then convert tenths of microseconds to nanoseconds. |
321 | | */ |
322 | 1.32k | ftsecs = filetime / 10000000; |
323 | 1.32k | nsecs = (int)((filetime % 10000000)*100); |
324 | | |
325 | 1.32k | return common_filetime_to_nstime(nstime, ftsecs, nsecs); |
326 | 1.32k | } |
327 | | |
328 | | /* |
329 | | * function: filetime_ns_to_nstime |
330 | | * converts a Windows FILETIME-like value, but given in nanoseconds |
331 | | * rather than 10ths of microseconds, to an nstime_t |
332 | | * returns true if the conversion succeeds, false if it doesn't |
333 | | * (for example, with a 32-bit time_t, the time overflows or |
334 | | * underflows time_t) |
335 | | */ |
336 | | bool |
337 | | filetime_ns_to_nstime(nstime_t *nstime, uint64_t nsfiletime) |
338 | 0 | { |
339 | 0 | uint64_t ftsecs; |
340 | 0 | int nsecs; |
341 | | |
342 | | /* Split into seconds and nanoseconds. */ |
343 | 0 | ftsecs = nsfiletime / NS_PER_S; |
344 | 0 | nsecs = (int)(nsfiletime % NS_PER_S); |
345 | |
|
346 | 0 | return common_filetime_to_nstime(nstime, ftsecs, nsecs); |
347 | 0 | } |
348 | | |
349 | | /* |
350 | | * function: filetime_1sec_to_nstime |
351 | | * converts a Windows FILETIME-like value, but given in seconds |
352 | | * rather than 10ths of microseconds, to an nstime_t |
353 | | * returns true if the conversion succeeds, false if it doesn't |
354 | | * (for example, with a 32-bit time_t, the time overflows or |
355 | | * underflows time_t) |
356 | | */ |
357 | | bool |
358 | | filetime_1sec_to_nstime(nstime_t *nstime, uint64_t filetime_1sec) |
359 | 0 | { |
360 | | /* |
361 | | * Make sure filetime_1sec fits in a 64-bit signed integer. |
362 | | */ |
363 | 0 | if (filetime_1sec > INT64_MAX) |
364 | 0 | return false; /* No, it doesn't */ |
365 | 0 | return common_filetime_to_nstime(nstime, filetime_1sec, 0); |
366 | 0 | } |
367 | | |
368 | | /* |
369 | | * function: iso8601_to_nstime |
370 | | * parses a character string for a date and time given in |
371 | | * ISO 8601 date-time format (eg: 2014-04-07T05:41:56.782+00:00) |
372 | | * and converts to an nstime_t |
373 | | * returns pointer to the first character after the last character |
374 | | * parsed on success, or NULL on failure |
375 | | * |
376 | | * NB. ISO 8601 is actually a lot more flexible than the above format, |
377 | | * much to a developer's chagrin. The "basic format" is distinguished from |
378 | | * the "extended format" by lacking the - and : separators. This function |
379 | | * supports both the basic and extended format (as well as both simultaneously) |
380 | | * with several common options and extensions. Time resolution is supported |
381 | | * up to nanoseconds (9 fractional digits) or down to whole minutes (omitting |
382 | | * the seconds component in the latter case). The T separator can be replaced |
383 | | * by a space in either format (a common extension not in ISO 8601 but found |
384 | | * in, e.g., RFC 3339) or omitted entirely in the basic format. |
385 | | * |
386 | | * Many standards that use ISO 8601 implement profiles with additional |
387 | | * constraints, such as requiring that the seconds field be present, only |
388 | | * allowing "." as the decimal separator, or limiting the number of fractional |
389 | | * digits. Callers that wish to check constraints not yet enforced by a |
390 | | * profile supported by the function must do so themselves. |
391 | | * |
392 | | * Future improvements could parse other ISO 8601 formats, such as |
393 | | * YYYY-Www-D, YYYY-DDD, etc. For a relatively easy introduction to |
394 | | * these formats, see wikipedia: https://en.wikipedia.org/wiki/ISO_8601 |
395 | | */ |
396 | | const char * |
397 | | iso8601_to_nstime(nstime_t *nstime, const char *ptr, iso8601_fmt_e format) |
398 | 0 | { |
399 | 0 | struct tm tm; |
400 | 0 | int n_scanned = 0; |
401 | 0 | int n_chars = 0; |
402 | 0 | unsigned frac = 0; |
403 | 0 | int off_hr = 0; |
404 | 0 | int off_min = 0; |
405 | 0 | char sign = '\0'; |
406 | 0 | bool has_separator = false; |
407 | 0 | bool have_offset = false; |
408 | |
|
409 | 0 | memset(&tm, 0, sizeof(tm)); |
410 | 0 | tm.tm_isdst = -1; |
411 | 0 | nstime_set_unset(nstime); |
412 | | |
413 | | /* Verify that we start with a four digit year and then look for the |
414 | | * separator. */ |
415 | 0 | for (n_scanned = 0; n_scanned < 4; n_scanned++) { |
416 | 0 | if (!g_ascii_isdigit(*ptr)) { |
417 | 0 | return NULL; |
418 | 0 | } |
419 | 0 | tm.tm_year *= 10; |
420 | 0 | tm.tm_year += *ptr++ - '0'; |
421 | 0 | } |
422 | 0 | if (*ptr == '-') { |
423 | 0 | switch (format) { |
424 | 0 | case ISO8601_DATETIME_BASIC: |
425 | 0 | return NULL; |
426 | | |
427 | 0 | case ISO8601_DATETIME: |
428 | 0 | case ISO8601_DATETIME_AUTO: |
429 | 0 | default: |
430 | 0 | has_separator = true; |
431 | 0 | ptr++; |
432 | 0 | }; |
433 | 0 | } else if (g_ascii_isdigit(*ptr)) { |
434 | 0 | switch (format) { |
435 | 0 | case ISO8601_DATETIME: |
436 | 0 | return NULL; |
437 | | |
438 | 0 | case ISO8601_DATETIME_BASIC: |
439 | 0 | case ISO8601_DATETIME_AUTO: |
440 | 0 | default: |
441 | 0 | has_separator = false; |
442 | 0 | }; |
443 | 0 | } else { |
444 | 0 | return NULL; |
445 | 0 | } |
446 | | |
447 | 0 | tm.tm_year -= 1900; /* struct tm expects number of years since 1900 */ |
448 | | |
449 | | /* Note: sscanf is known to be inconsistent across platforms with respect |
450 | | to whether a %n is counted as a return value or not (XXX: Is this |
451 | | still true, despite the express comments of C99 §7.19.6.2 12?), so we |
452 | | use '<'/'>=' |
453 | | */ |
454 | | /* XXX: sscanf allows an optional sign indicator before each integer |
455 | | * converted (whether with %d or %u), so this will convert some bogus |
456 | | * strings. Either checking afterwards or doing the whole thing by hand |
457 | | * as with the year above is the only correct way. (strptime certainly |
458 | | * can't handle the basic format.) |
459 | | */ |
460 | 0 | n_scanned = sscanf(ptr, has_separator ? "%2u-%2u%n" : "%2u%2u%n", |
461 | 0 | &tm.tm_mon, |
462 | 0 | &tm.tm_mday, |
463 | 0 | &n_chars); |
464 | 0 | if (n_scanned >= 2) { |
465 | | /* Got year, month, and day */ |
466 | 0 | tm.tm_mon--; /* struct tm expects 0-based month */ |
467 | 0 | ptr += n_chars; |
468 | 0 | } |
469 | 0 | else { |
470 | 0 | return NULL; |
471 | 0 | } |
472 | | |
473 | 0 | if (*ptr == 'T' || *ptr == ' ') { |
474 | | /* The 'T' between date and time is optional if the meaning is |
475 | | unambiguous. We also allow for ' ' here per RFC 3339 to support |
476 | | formats such as editcap's -A/-B options. */ |
477 | 0 | ptr++; |
478 | 0 | } |
479 | 0 | else if (has_separator) { |
480 | | /* Allow no separator between date and time iff we have no |
481 | | separator between units. (Some extended formats may negotiate |
482 | | no separator here, so this could be changed.) */ |
483 | 0 | return NULL; |
484 | 0 | } |
485 | | |
486 | | /* Now we're on to the time part. We'll require a minimum of hours and |
487 | | minutes. */ |
488 | | |
489 | 0 | n_scanned = sscanf(ptr, has_separator ? "%2u:%2u%n" : "%2u%2u%n", |
490 | 0 | &tm.tm_hour, |
491 | 0 | &tm.tm_min, |
492 | 0 | &n_chars); |
493 | 0 | if (n_scanned >= 2) { |
494 | 0 | ptr += n_chars; |
495 | 0 | } |
496 | 0 | else { |
497 | | /* didn't get hours and minutes */ |
498 | 0 | return NULL; |
499 | 0 | } |
500 | | |
501 | | /* Test for (whole) seconds */ |
502 | 0 | if ((has_separator && *ptr == ':') || |
503 | 0 | (!has_separator && g_ascii_isdigit(*ptr))) { |
504 | | /* Looks like we should have them */ |
505 | 0 | if (1 > sscanf(ptr, has_separator ? ":%2u%n" : "%2u%n", |
506 | 0 | &tm.tm_sec, &n_chars)) { |
507 | | /* Couldn't get them */ |
508 | 0 | return NULL; |
509 | 0 | } |
510 | 0 | ptr += n_chars; |
511 | | |
512 | | /* Now let's test for fractional seconds */ |
513 | 0 | if (*ptr == '.' || *ptr == ',') { |
514 | | /* Get fractional seconds */ |
515 | 0 | ptr++; |
516 | 0 | if (1 <= sscanf(ptr, "%u%n", &frac, &n_chars)) { |
517 | | /* normalize frac to nanoseconds */ |
518 | 0 | if ((frac >= 1000000000) || (frac == 0)) { |
519 | 0 | frac = 0; |
520 | 0 | } else { |
521 | 0 | switch (n_chars) { /* including leading zeros */ |
522 | 0 | case 1: frac *= 100000000; break; |
523 | 0 | case 2: frac *= 10000000; break; |
524 | 0 | case 3: frac *= 1000000; break; |
525 | 0 | case 4: frac *= 100000; break; |
526 | 0 | case 5: frac *= 10000; break; |
527 | 0 | case 6: frac *= 1000; break; |
528 | 0 | case 7: frac *= 100; break; |
529 | 0 | case 8: frac *= 10; break; |
530 | 0 | default: break; |
531 | 0 | } |
532 | 0 | } |
533 | 0 | ptr += n_chars; |
534 | 0 | } |
535 | | /* If we didn't get frac, it's still its default of 0 */ |
536 | 0 | } |
537 | 0 | } |
538 | 0 | else { |
539 | | /* No seconds. ISO 8601 allows decimal fractions of a minute here, |
540 | | * but that's pretty rare in practice. Could be added later if needed. |
541 | | */ |
542 | 0 | tm.tm_sec = 0; |
543 | 0 | } |
544 | | |
545 | | /* Validate what we got so far. mktime() doesn't care about strange |
546 | | values but we should at least start with something valid */ |
547 | 0 | if (!tm_is_valid(&tm)) { |
548 | 0 | return NULL; |
549 | 0 | } |
550 | | |
551 | | /* Check for a time zone offset */ |
552 | 0 | if (*ptr == '-' || *ptr == '+' || *ptr == 'Z') { |
553 | | /* Just in case somewhere decides to observe a timezone of -00:30 or |
554 | | * some such. */ |
555 | 0 | sign = *ptr; |
556 | | /* We have a UTC-relative offset */ |
557 | 0 | if (*ptr == 'Z') { |
558 | 0 | off_hr = off_min = 0; |
559 | 0 | have_offset = true; |
560 | 0 | ptr++; |
561 | 0 | } |
562 | 0 | else { |
563 | 0 | off_hr = off_min = 0; |
564 | 0 | n_scanned = sscanf(ptr, "%3d%n", &off_hr, &n_chars); |
565 | 0 | if (n_scanned >= 1) { |
566 | | /* Definitely got hours */ |
567 | 0 | have_offset = true; |
568 | 0 | ptr += n_chars; |
569 | 0 | n_scanned = sscanf(ptr, *ptr == ':' ? ":%2d%n" : "%2d%n", &off_min, &n_chars); |
570 | 0 | if (n_scanned >= 1) { |
571 | | /* Got minutes too */ |
572 | 0 | ptr += n_chars; |
573 | 0 | } |
574 | 0 | } |
575 | 0 | else { |
576 | | /* Didn't get a valid offset, treat as if there's none at all */ |
577 | 0 | have_offset = false; |
578 | 0 | } |
579 | 0 | } |
580 | 0 | } |
581 | 0 | if (have_offset) { |
582 | 0 | nstime->secs = mktime_utc(&tm); |
583 | 0 | if (sign == '+') { |
584 | 0 | nstime->secs -= (off_hr * 3600) + (off_min * 60); |
585 | 0 | } else if (sign == '-') { |
586 | | /* -00:00 is illegal according to ISO 8601, but RFC 3339 allows |
587 | | * it under a convention where -00:00 means "time in UTC is known, |
588 | | * local timezone is unknown." This has the same value as an |
589 | | * offset of Z or +00:00, but semantically implies that UTC is |
590 | | * not the preferred time zone, which is immaterial to us. |
591 | | */ |
592 | | /* Add the time, but reverse the sign of off_hr, which includes |
593 | | * the negative sign. |
594 | | */ |
595 | 0 | nstime->secs += ((-off_hr) * 3600) + (off_min * 60); |
596 | 0 | } |
597 | 0 | } |
598 | 0 | else { |
599 | | /* No UTC offset given; ISO 8601 says this means local time */ |
600 | 0 | nstime->secs = mktime(&tm); |
601 | 0 | } |
602 | 0 | nstime->nsecs = frac; |
603 | 0 | return ptr; |
604 | 0 | } |
605 | | |
606 | | /* |
607 | | * function: unix_epoch_to_nstime |
608 | | * parses a character string for a date and time given in |
609 | | * a floating point number containing a Unix epoch date-time |
610 | | * format (e.g. 1600000000.000 for Sun Sep 13 05:26:40 AM PDT 2020) |
611 | | * and converts to an nstime_t |
612 | | * returns pointer to the first character after the last character |
613 | | * parsed on success, or NULL on failure |
614 | | * |
615 | | * Reference: https://en.wikipedia.org/wiki/Unix_time |
616 | | */ |
617 | | const char * |
618 | | unix_epoch_to_nstime(nstime_t *nstime, const char *ptr) |
619 | 0 | { |
620 | 0 | int64_t secs; |
621 | 0 | const char *ptr_new; |
622 | |
|
623 | 0 | int n_chars = 0; |
624 | 0 | unsigned frac = 0; |
625 | |
|
626 | 0 | nstime_set_unset(nstime); |
627 | | |
628 | | /* |
629 | | * Extract the seconds as a 64-bit signed number, as time_t |
630 | | * might be 64-bit. |
631 | | */ |
632 | 0 | if (!ws_strtoi64(ptr, &ptr_new, &secs)) { |
633 | 0 | return NULL; |
634 | 0 | } |
635 | | |
636 | | /* For now, reject times before the Epoch. */ |
637 | 0 | if (secs < 0) { |
638 | 0 | return NULL; |
639 | 0 | } |
640 | | |
641 | | /* Make sure it fits. */ |
642 | 0 | nstime->secs = (time_t) secs; |
643 | 0 | if (nstime->secs != secs) { |
644 | 0 | return NULL; |
645 | 0 | } |
646 | | |
647 | | /* Now let's test for fractional seconds */ |
648 | 0 | if (*ptr_new == '.' || *ptr_new == ',') { |
649 | | /* Get fractional seconds */ |
650 | 0 | ptr_new++; |
651 | 0 | if (1 <= sscanf(ptr_new, "%u%n", &frac, &n_chars)) { |
652 | | /* normalize frac to nanoseconds */ |
653 | 0 | if ((frac >= 1000000000) || (frac == 0)) { |
654 | 0 | frac = 0; |
655 | 0 | } else { |
656 | 0 | switch (n_chars) { /* including leading zeros */ |
657 | 0 | case 1: frac *= 100000000; break; |
658 | 0 | case 2: frac *= 10000000; break; |
659 | 0 | case 3: frac *= 1000000; break; |
660 | 0 | case 4: frac *= 100000; break; |
661 | 0 | case 5: frac *= 10000; break; |
662 | 0 | case 6: frac *= 1000; break; |
663 | 0 | case 7: frac *= 100; break; |
664 | 0 | case 8: frac *= 10; break; |
665 | 0 | default: break; |
666 | 0 | } |
667 | 0 | } |
668 | 0 | ptr_new += n_chars; |
669 | 0 | } |
670 | | /* If we didn't get frac, it's still its default of 0 */ |
671 | 0 | } |
672 | 0 | else { |
673 | 0 | frac = 0; |
674 | 0 | } |
675 | 0 | nstime->nsecs = frac; |
676 | 0 | return ptr_new; |
677 | 0 | } |
678 | | |
679 | | size_t nstime_to_iso8601(char *buf, size_t buf_size, const nstime_t *nstime) |
680 | 0 | { |
681 | 0 | struct tm *tm; |
682 | 0 | #ifndef _WIN32 |
683 | 0 | struct tm tm_time; |
684 | 0 | #endif |
685 | 0 | size_t len; |
686 | |
|
687 | | #ifdef _WIN32 |
688 | | /* |
689 | | * Do not use gmtime_s(), as it will call and |
690 | | * exception handler if the time we're providing |
691 | | * is < 0, and that will, by default, exit. |
692 | | * ("Programmers not bothering to check return |
693 | | * values? Try new Microsoft Visual Studio, |
694 | | * with Parameter Validation(R)! Kill insufficiently |
695 | | * careful programs - *and* the processes running them - |
696 | | * fast!") |
697 | | * |
698 | | * We just want to report this as an unrepresentable |
699 | | * time. It fills in a per-thread structure, which |
700 | | * is sufficiently thread-safe for our purposes. |
701 | | */ |
702 | | tm = gmtime(&nstime->secs); |
703 | | #else |
704 | | /* |
705 | | * Use gmtime_r(), because the Single UNIX Specification |
706 | | * does *not* guarantee that gmtime() is thread-safe. |
707 | | * Perhaps it is on all platforms on which we run, but |
708 | | * this way we don't have to check. |
709 | | */ |
710 | 0 | tm = gmtime_r(&nstime->secs, &tm_time); |
711 | 0 | #endif |
712 | 0 | if (tm == NULL) { |
713 | 0 | return 0; |
714 | 0 | } |
715 | | |
716 | | /* Some platforms (MinGW-w64) do not support %F or %T. */ |
717 | | /* Returns number of bytes, excluding terminating null, placed in |
718 | | * buf, or zero if there is not enough space for the whole string. */ |
719 | 0 | len = strftime(buf, buf_size, "%Y-%m-%dT%H:%M:%S", tm); |
720 | 0 | if (len == 0) { |
721 | 0 | return 0; |
722 | 0 | } |
723 | 0 | ws_assert(len < buf_size); |
724 | 0 | buf += len; |
725 | 0 | buf_size -= len; |
726 | 0 | len += snprintf(buf, buf_size, ".%09dZ", nstime->nsecs); |
727 | 0 | return len; |
728 | 0 | } |
729 | | |
730 | | void nstime_to_unix(char *buf, size_t buf_size, const nstime_t *nstime) |
731 | 0 | { |
732 | 0 | display_signed_time(buf, buf_size, nstime, WS_TSPREC_NSEC); |
733 | 0 | } |
734 | | |
735 | | /* |
736 | | * Editor modelines |
737 | | * |
738 | | * Local Variables: |
739 | | * c-basic-offset: 4 |
740 | | * tab-width: 8 |
741 | | * indent-tabs-mode: nil |
742 | | * End: |
743 | | * |
744 | | * ex: set shiftwidth=4 tabstop=8 expandtab: |
745 | | * :indentSize=4:tabSize=8:noTabs=true: |
746 | | */ |