Coverage Report

Created: 2023-12-08 06:56

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