Branch data Line data Source code
1 : : // Copyright 2019-2026 David Robillard <d@drobilla.net>
2 : : // SPDX-License-Identifier: ISC
3 : :
4 : : #include "time_utils.h"
5 : : #include "char_utils.h"
6 : : #include "number_utils.h"
7 : : #include "result.h"
8 : : #include "timezone.h"
9 : : #include "write_utils.h"
10 : :
11 : : #include <exess/exess.h>
12 : :
13 : : #include <assert.h>
14 : : #include <stdbool.h>
15 : : #include <stddef.h>
16 : : #include <stdint.h>
17 : :
18 : : EXESS_NONBLOCKING ExessResult
19 : 6851 : read_nanoseconds(uint32_t* const out, const char* const str)
20 : : {
21 : : // Read available digits
22 : 6851 : size_t i = 0U;
23 [ + + + + ]: 68021 : for (; i < 9U && is_digit(str[i]); ++i) {
24 : 61170 : *out = (*out * 10U) + (uint32_t)(str[i] - '0');
25 : : }
26 : :
27 [ + + + + ]: 6851 : if (i == 9U && is_digit(str[i])) {
28 : 3 : return RESULT(EXESS_OUT_OF_RANGE, i);
29 : : }
30 : :
31 : : // "Read" imagined trailing zeros to reach 9 digit magnitude
32 [ + + ]: 7337 : for (size_t j = i; j < 9U; ++j) {
33 : 489 : *out *= 10U;
34 : : }
35 : :
36 [ + + ]: 6848 : return RESULT(i > 0 ? EXESS_SUCCESS : EXESS_EXPECTED_DIGIT, i);
37 : : }
38 : :
39 : : EXESS_NONBLOCKING size_t
40 : 8097 : write_nanoseconds(const uint32_t nanosecond,
41 : : const size_t buf_size,
42 : : char* const buf,
43 : : const size_t i)
44 : : {
45 [ - + ]: 8097 : assert(nanosecond <= 999999999);
46 : :
47 [ + + ]: 8097 : if (nanosecond == 0) {
48 : 252 : return 0;
49 : : }
50 : :
51 : 7845 : char frac_digits[10] = {'0', '0', '0', '0', '0', '0', '0', '0', '0', 0};
52 : :
53 : : // Write digits right to left, but replace trailing zeros with null
54 : 7845 : uint32_t remaining = nanosecond;
55 : 7845 : uint32_t n_trailing = 0;
56 : 7845 : bool wrote_nonzero = false;
57 [ + + ]: 77346 : for (uint32_t j = 0; remaining > 0; ++j) {
58 : 69501 : const char digit = (char)('0' + (remaining % 10));
59 [ + + + + ]: 69501 : if (!wrote_nonzero && digit == '0') {
60 : 287 : frac_digits[8 - j] = '\0';
61 : 287 : ++n_trailing;
62 : : } else {
63 : 69214 : frac_digits[8 - j] = digit;
64 : : }
65 : :
66 [ + + + + ]: 69501 : wrote_nonzero = wrote_nonzero || digit != '0';
67 : 69501 : remaining /= 10;
68 : : }
69 : :
70 : 7845 : size_t n = write_char('.', buf_size, buf, i);
71 : :
72 : 7845 : n += write_string(9 - n_trailing, frac_digits, buf_size, buf, i + n);
73 : :
74 : 7845 : return n;
75 : : }
76 : :
77 : : EXESS_NONBLOCKING ExessResult
78 : 6272 : read_time(ExessTime* const out, const char* const str)
79 : : {
80 : : // Read hour
81 : 6272 : size_t i = 0;
82 : 6272 : ExessResult r = read_two_digit_number(&out->hour, 0, 24, str + i);
83 [ + + ]: 6272 : if (r.status) {
84 : 7 : return RESULT(r.status, i + r.count);
85 : : }
86 : :
87 : 6265 : const bool midnight = out->hour == 24;
88 : :
89 : : // Read hour-minute delimiter
90 : 6265 : i += r.count;
91 [ + + ]: 6265 : if (str[i] != ':') {
92 : 1 : return RESULT(EXESS_EXPECTED_COLON, i);
93 : : }
94 : :
95 : : // Read minute
96 : 6264 : ++i;
97 [ + + ]: 6264 : r = read_two_digit_number(&out->minute, 0, midnight ? 0 : 59, str + i);
98 [ + + ]: 6264 : if (r.status) {
99 : 2 : return RESULT(r.status, i + r.count);
100 : : }
101 : :
102 : : // Read minute-second delimiter
103 : 6262 : i += r.count;
104 [ + + ]: 6262 : if (str[i] != ':') {
105 : 4 : return RESULT(EXESS_EXPECTED_COLON, i);
106 : : }
107 : :
108 : : // Read second
109 : 6258 : ++i;
110 [ + + ]: 6258 : r = read_two_digit_number(&out->second, 0, midnight ? 0 : 59, str + i);
111 : 6258 : i += r.count;
112 [ + + ]: 6258 : if (r.status) {
113 : 2 : return RESULT(r.status, i);
114 : : }
115 : :
116 : : // Read nanoseconds if present
117 [ + + ]: 6256 : if (str[i] == '.') {
118 : 5803 : ++i;
119 : 5803 : r = read_nanoseconds(&out->nanosecond, str + i);
120 : 5803 : i += r.count;
121 : : }
122 [ + + ]: 6256 : if (r.status) {
123 : 3 : return RESULT(r.status, i);
124 : : }
125 : :
126 : : // Ensure midnight has zero nanoseconds
127 [ + + + + ]: 6253 : if (midnight && out->nanosecond) {
128 : 2 : return RESULT(EXESS_OUT_OF_RANGE, i);
129 : : }
130 : :
131 : : // Read timezone offset if present
132 : 6251 : r = read_optional_timezone(&out->zone, str + i);
133 : 6251 : i += r.count;
134 : :
135 : 6251 : return RESULT(r.status, i);
136 : : }
137 : :
138 : : EXESS_NONBLOCKING ExessResult
139 : 6034 : write_time(const ExessTime value,
140 : : const size_t buf_size,
141 : : char* const buf,
142 : : const size_t offset)
143 : : {
144 [ + + + + : 6034 : if (value.hour > 24 || value.minute > 59 || value.second > 59 ||
+ + ]
145 [ + + ]: 6031 : value.nanosecond > 999999999) {
146 : 4 : return RESULT(EXESS_BAD_VALUE, 0);
147 : : }
148 : :
149 : 6030 : size_t o = offset;
150 : :
151 : : // Write integral hour, minute, and second
152 : 6030 : o += write_two_digit_number(value.hour, buf_size, buf, o);
153 : 6030 : o += write_char(':', buf_size, buf, o);
154 : 6030 : o += write_two_digit_number(value.minute, buf_size, buf, o);
155 : 6030 : o += write_char(':', buf_size, buf, o);
156 : 6030 : o += write_two_digit_number(value.second, buf_size, buf, o);
157 : 6030 : o += write_nanoseconds(value.nanosecond, buf_size, buf, o);
158 : :
159 : 6030 : const ExessResult r = write_timezone(value.zone, buf_size, buf, o);
160 : :
161 : 6030 : return RESULT(r.status, o - offset + r.count);
162 : : }
|