Branch data Line data Source code
1 : : // Copyright 2019-2026 David Robillard <d@drobilla.net>
2 : : // SPDX-License-Identifier: ISC
3 : :
4 : : #include "duration.h"
5 : : #include "char_utils.h"
6 : : #include "date_time.h"
7 : : #include "number_utils.h"
8 : : #include "read_utils.h"
9 : : #include "result.h"
10 : : #include "time_utils.h"
11 : : #include "write_utils.h"
12 : :
13 : : #include <exess/exess.h>
14 : :
15 : : #include <stdbool.h>
16 : : #include <stdint.h>
17 : : #include <stdlib.h>
18 : : #include <string.h>
19 : :
20 : : typedef enum { NONE, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND } Field;
21 : :
22 : : static EXESS_NONBLOCKING ExessStatus
23 : 6252 : set_field(ExessDuration* const out,
24 : : const Field last_field,
25 : : const Field field,
26 : : const uint64_t value)
27 : : {
28 [ + + ]: 6252 : if (value >= INT32_MAX) {
29 : 7 : return EXESS_OUT_OF_RANGE;
30 : : }
31 : :
32 [ + + ]: 6245 : if (field <= last_field) {
33 : 6 : return EXESS_BAD_ORDER;
34 : : }
35 : :
36 : 6239 : EXESS_CONSTEXPR uint64_t factors[7] = {
37 : : 0UL, 12UL, 1UL, 24UL * 60UL * 60UL, 60UL * 60UL, 60UL, 1UL};
38 : :
39 : 6239 : const uint64_t factor = factors[field];
40 [ + + ]: 6239 : int32_t* const target = (field < DAY) ? &out->months : &out->seconds;
41 : :
42 : 6239 : const int64_t new_value = (int64_t)*target + (int64_t)(factor * value);
43 [ + + ]: 6239 : if (new_value >= INT32_MAX) {
44 : 4 : return EXESS_OUT_OF_RANGE;
45 : : }
46 : :
47 : 6235 : *target = (int32_t)new_value;
48 : 6235 : return EXESS_SUCCESS;
49 : : }
50 : :
51 : : static EXESS_NONBLOCKING ExessResult
52 : 1178 : read_duration_date(ExessDuration* const out, const char* const str)
53 : : {
54 : 1178 : size_t i = 0U;
55 : 1178 : ExessStatus st = EXESS_SUCCESS;
56 : 1178 : unsigned last_field = 0U;
57 : :
58 [ + + + + : 4335 : while (!st && last_field < DAY && str[i] != 'T') {
+ + ]
59 : : // Read the unsigned integer value
60 : 3236 : uint64_t value = 0U;
61 : 3236 : const ExessResult r = read_digits(&value, str + i);
62 : 3236 : i += r.count;
63 [ + + ]: 3236 : if (r.status) {
64 [ + + ]: 74 : st = r.count ? r.status : EXESS_SUCCESS;
65 : 74 : break;
66 : : }
67 : :
68 : : // Read Y, M, or D field tag
69 : 6324 : const Field field = (str[i] == 'Y') ? YEAR
70 [ + + + + ]: 4253 : : (str[i] == 'M') ? MONTH
71 [ + + ]: 1091 : : (str[i] == 'D') ? DAY
72 : : : NONE;
73 [ + + ]: 3162 : if (!field) {
74 : 5 : return RESULT(EXESS_EXPECTED_DATE_TAG, i);
75 : : }
76 : :
77 : : // Set the field value and advance
78 : 3157 : st = set_field(out, (Field)last_field, field, value);
79 : 3157 : last_field = field;
80 : 3157 : ++i;
81 : : }
82 : :
83 [ + + ]: 1173 : return i > 0 ? RESULT(st, i) : RESULT(EXESS_EXPECTED_DIGIT, i);
84 : : }
85 : :
86 : : static ExessResult
87 : 1113 : read_duration_time(ExessDuration* const out, const char* const str)
88 : : {
89 : 1113 : size_t i = 0U;
90 : 1113 : ExessStatus st = EXESS_SUCCESS;
91 : 1113 : unsigned last_field = 0U;
92 : :
93 [ + + + + ]: 4208 : while (!st && last_field < SECOND) {
94 : : // Read the unsigned integer value
95 : 3132 : uint64_t value = 0U;
96 : 3132 : const ExessResult r = read_digits(&value, str + i);
97 : 3132 : i += r.count;
98 [ + + ]: 3132 : if (r.status) {
99 [ + + ]: 31 : st = r.count ? r.status : EXESS_SUCCESS;
100 : 31 : break;
101 : : }
102 : :
103 : 3101 : Field field = YEAR;
104 [ + + ]: 3101 : if (str[i] == '.') {
105 : 1049 : uint32_t nanoseconds = 0U;
106 [ + + ]: 1049 : if (!is_digit(str[++i])) {
107 : 1 : return RESULT(EXESS_EXPECTED_DIGIT, i);
108 : : }
109 : :
110 : 1048 : const ExessResult s = read_nanoseconds(&nanoseconds, str + i);
111 : 1048 : i += s.count;
112 [ + + ]: 1048 : if (str[i] != 'S') {
113 : 4 : return RESULT(EXESS_EXPECTED_SECOND_TAG, i);
114 : : }
115 : :
116 : 1044 : field = SECOND;
117 : 1044 : out->nanoseconds = (int32_t)nanoseconds;
118 [ + + ]: 2052 : } else if (str[i] == 'H') {
119 : 996 : field = HOUR;
120 [ + + ]: 1056 : } else if (str[i] == 'M') {
121 : 1030 : field = MINUTE;
122 [ + + ]: 26 : } else if (str[i] == 'S') {
123 : 25 : field = SECOND;
124 : : } else {
125 : 1 : return RESULT(EXESS_EXPECTED_TIME_TAG, i);
126 : : }
127 : :
128 : : // Set the field value and advance
129 : 3095 : st = set_field(out, (Field)last_field, field, value);
130 : 3095 : last_field = (unsigned)field;
131 : 3095 : ++i;
132 : : }
133 : :
134 [ + + ]: 1107 : return i > 0 ? RESULT(st, i) : RESULT(EXESS_EXPECTED_DIGIT, i);
135 : : }
136 : :
137 : : static EXESS_NONBLOCKING ExessOrder
138 : 50 : compare_duration_partial(const ExessDuration lhs, const ExessDuration rhs)
139 : : {
140 : : // See https://www.w3.org/TR/xmlschema11-2/#duration
141 : :
142 : : const ExessDateTime a = {1696, 9, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
143 : : const ExessDateTime b = {1697, 2, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
144 : : const ExessDateTime c = {1903, 3, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
145 : : const ExessDateTime d = {1903, 7, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
146 : :
147 : 50 : const ExessOrder cmp = exess_compare_date_time(
148 : : exess_add_date_time_duration(a, lhs), exess_add_date_time_duration(a, rhs));
149 : :
150 [ + + ]: 50 : if (cmp == exess_compare_date_time(exess_add_date_time_duration(b, lhs),
151 : 40 : exess_add_date_time_duration(b, rhs)) &&
152 [ + + ]: 40 : cmp == exess_compare_date_time(exess_add_date_time_duration(c, lhs),
153 : 28 : exess_add_date_time_duration(c, rhs)) &&
154 [ + + ]: 28 : cmp == exess_compare_date_time(exess_add_date_time_duration(d, lhs),
155 : : exess_add_date_time_duration(d, rhs))) {
156 : 26 : return cmp;
157 : : }
158 : :
159 : 24 : return (lhs.months < rhs.months) ? EXESS_ORDER_MAYBE_LESS
160 [ + + + + ]: 32 : : (rhs.months < lhs.months) ? EXESS_ORDER_MAYBE_GREATER
161 [ + + ]: 8 : : (lhs.seconds < rhs.seconds) ? EXESS_ORDER_MAYBE_LESS
162 : : : EXESS_ORDER_MAYBE_GREATER;
163 : : }
164 : :
165 : : EXESS_NONBLOCKING ExessOrder
166 : 104 : exess_compare_duration(const ExessDuration lhs, const ExessDuration rhs)
167 : : {
168 : : // Exactly equal
169 [ + + + + ]: 104 : if (lhs.months == rhs.months && lhs.seconds == rhs.seconds &&
170 [ + + ]: 26 : lhs.nanoseconds == rhs.nanoseconds) {
171 : 18 : return EXESS_ORDER_EQUAL;
172 : : }
173 : :
174 : : // Totally ordered yearMonthDuration
175 [ + + + + : 86 : if (!lhs.seconds && !lhs.nanoseconds && !rhs.seconds && !rhs.nanoseconds) {
+ + + + ]
176 : 12 : return (lhs.months < rhs.months) ? EXESS_ORDER_STRICTLY_LESS
177 [ + + ]: 12 : : EXESS_ORDER_STRICTLY_GREATER;
178 : : }
179 : :
180 : : // Totally ordered dayTimeDuration
181 [ + + + + ]: 74 : if (!lhs.months && !rhs.months) {
182 : 24 : return (lhs.seconds < rhs.seconds) ? EXESS_ORDER_STRICTLY_LESS
183 [ + + + + ]: 28 : : (rhs.seconds < lhs.seconds) ? EXESS_ORDER_STRICTLY_GREATER
184 [ + + ]: 4 : : (lhs.nanoseconds < rhs.nanoseconds) ? EXESS_ORDER_STRICTLY_LESS
185 : : : EXESS_ORDER_STRICTLY_GREATER;
186 : : }
187 : :
188 : 50 : return compare_duration_partial(lhs, rhs);
189 : : }
190 : :
191 : : EXESS_NONBLOCKING ExessResult
192 : 1261 : exess_read_duration(const char* const str, ExessDuration* const out)
193 : : {
194 : 1261 : memset(out, 0, sizeof(*out));
195 : :
196 : 1261 : size_t i = skip_whitespace(str);
197 : 1261 : bool is_negative = false;
198 [ + + ]: 1261 : if (str[i] == '-') {
199 : 554 : is_negative = true;
200 : 554 : ++i;
201 : : }
202 : :
203 [ + + ]: 1261 : if (str[i] != 'P') {
204 : 7 : return RESULT(EXESS_EXPECTED_DURATION, i);
205 : : }
206 : :
207 : 1254 : ++i;
208 [ + + ]: 1254 : if (str[i] != 'T') {
209 : 1178 : ExessResult r = read_duration_date(out, str + i);
210 [ + + ]: 1178 : if (r.status) {
211 : 20 : return RESULT(r.status, i + r.count);
212 : : }
213 : :
214 : 1158 : i += r.count;
215 : : }
216 : :
217 [ + + ]: 1234 : if (str[i] == 'T') {
218 : 1113 : ++i;
219 : :
220 : 1113 : ExessResult r = read_duration_time(out, str + i);
221 [ + + ]: 1113 : if (r.status) {
222 : 19 : return RESULT(r.status, i + r.count);
223 : : }
224 : :
225 : 1094 : i += r.count;
226 : : }
227 : :
228 [ + + ]: 1215 : if (is_negative) {
229 : 554 : out->months = -out->months;
230 : 554 : out->seconds = -out->seconds;
231 : 554 : out->nanoseconds = -out->nanoseconds;
232 : : }
233 : :
234 : 1215 : return RESULT(EXESS_SUCCESS, i);
235 : : }
236 : :
237 : : static size_t
238 : 10548 : write_int_field(const uint32_t value,
239 : : const char tag,
240 : : const size_t buf_size,
241 : : char* const buf,
242 : : const size_t i)
243 : : {
244 [ + + ]: 10548 : if (!value) {
245 : 421 : return 0;
246 : : }
247 : :
248 : 10127 : const ExessResult r = write_digits(value, buf_size, buf, i);
249 : 10127 : return r.count + write_char(tag, buf_size, buf, i + r.count);
250 : : }
251 : :
252 : : static size_t
253 : 2087 : write_seconds(const uint8_t abs_seconds,
254 : : const uint32_t abs_nanoseconds,
255 : : const size_t buf_size,
256 : : char* const buf,
257 : : const size_t i)
258 : : {
259 : 2087 : const ExessResult r = write_digits(abs_seconds, buf_size, buf, i);
260 : :
261 : 2087 : size_t len = r.count;
262 [ + + ]: 2087 : if (abs_nanoseconds) {
263 : 2067 : len += write_nanoseconds(abs_nanoseconds, buf_size, buf, i + len);
264 : : }
265 : :
266 : 2087 : return len + write_char('S', buf_size, buf, i + len);
267 : : }
268 : :
269 : : EXESS_NONBLOCKING ExessResult
270 : 2131 : exess_write_duration(const ExessDuration value,
271 : : const size_t buf_size,
272 : : char* const buf)
273 : : {
274 : : // Write zero as a special case
275 : 2131 : size_t i = 0;
276 [ + + + + : 2131 : if (value.months == 0 && value.seconds == 0 && value.nanoseconds == 0) {
+ + ]
277 : 4 : i += write_string(3, "P0Y", buf_size, buf, i);
278 : 4 : return end_write(EXESS_SUCCESS, buf_size, buf, i);
279 : : }
280 : :
281 [ + + + + ]: 2127 : if (value.months == INT32_MIN || value.seconds == INT32_MIN) {
282 : 3 : return end_write(EXESS_OUT_OF_RANGE, buf_size, buf, 0);
283 : : }
284 : :
285 : 2124 : const bool is_negative =
286 [ + + + + : 2124 : (value.months < 0 || value.seconds < 0 || value.nanoseconds < 0);
+ + ]
287 : :
288 [ + + ]: 2124 : if (is_negative &&
289 [ + + + + : 1099 : (value.months > 0 || value.seconds > 0 || value.nanoseconds > 0)) {
+ + ]
290 : 6 : return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
291 : : }
292 : :
293 : : // Write duration prefix
294 [ + + ]: 2118 : i += is_negative ? write_char('-', buf_size, buf, i) : 0U;
295 : 2118 : i += write_char('P', buf_size, buf, i);
296 : :
297 : 2118 : const uint32_t abs_years = (uint32_t)(abs(value.months) / 12);
298 : 2118 : const uint32_t abs_months = (uint32_t)(abs(value.months) % 12);
299 : 2118 : const uint32_t abs_days = (uint32_t)(abs(value.seconds) / (24 * 60 * 60));
300 : 2118 : const uint32_t abs_hours = (uint32_t)(abs(value.seconds) / 60 / 60 % 24);
301 : 2118 : const uint32_t abs_minutes = (uint32_t)(abs(value.seconds) / 60 % 60);
302 : 2118 : const uint8_t abs_seconds = (uint8_t)(abs(value.seconds) % 60);
303 : 2118 : const uint32_t abs_nanoseconds = (uint32_t)abs(value.nanoseconds);
304 : :
305 : : // Write date segments if present
306 : 2118 : i += write_int_field(abs_years, 'Y', buf_size, buf, i);
307 : 2118 : i += write_int_field(abs_months, 'M', buf_size, buf, i);
308 : 2118 : i += write_int_field(abs_days, 'D', buf_size, buf, i);
309 : :
310 : : // Write time segments if present
311 : 2118 : const bool has_time = abs_hours + abs_minutes + abs_seconds + abs_nanoseconds;
312 [ + + ]: 2118 : if (has_time) {
313 : 2097 : i += write_char('T', buf_size, buf, i);
314 : 2097 : i += write_int_field(abs_hours, 'H', buf_size, buf, i);
315 : 2097 : i += write_int_field(abs_minutes, 'M', buf_size, buf, i);
316 [ + + ]: 2097 : if (abs_seconds + abs_nanoseconds) {
317 : 2087 : i += write_seconds(abs_seconds, abs_nanoseconds, buf_size, buf, i);
318 : : }
319 : : }
320 : :
321 : 2118 : return end_write(EXESS_SUCCESS, buf_size, buf, i);
322 : : }
|