Coverage Report

Created: 2025-12-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
 */