Coverage Report

Created: 2025-04-03 08:46

/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
 */