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