Branch data Line data Source code
1 : : // Copyright 2019-2026 David Robillard <d@drobilla.net>
2 : : // SPDX-License-Identifier: ISC
3 : :
4 : : #include "char_utils.h"
5 : : #include "date_utils.h"
6 : : #include "result.h"
7 : : #include "time_utils.h"
8 : : #include "write_utils.h"
9 : :
10 : : #include <exess/exess.h>
11 : :
12 : : #include <stdint.h>
13 : : #include <string.h>
14 : :
15 : : static inline ExessDateTime
16 : 6 : infinite_future(const ExessTimezone zone)
17 : : {
18 : 6 : const ExessDateTime r = {INT16_MAX,
19 : : UINT8_MAX,
20 : : UINT8_MAX,
21 : : zone,
22 : : UINT8_MAX,
23 : : UINT8_MAX,
24 : : UINT8_MAX,
25 : : UINT32_MAX};
26 : :
27 : 6 : return r;
28 : : }
29 : :
30 : : static inline ExessDateTime
31 : 6 : infinite_past(const ExessTimezone zone)
32 : : {
33 : 6 : const ExessDateTime r = {INT16_MIN, 0, 0, zone, 0, 0, 0, 0};
34 : :
35 : 6 : return r;
36 : : }
37 : :
38 : : static int32_t
39 : 1533 : modulo(const int32_t a, const int32_t low, const int32_t high)
40 : : {
41 : 1533 : return ((a - low) % (high - low)) + low;
42 : : }
43 : :
44 : : static int32_t
45 : 1470 : quotient(const int32_t a, const int32_t low, const int32_t high)
46 : : {
47 : 1470 : return (a - low) / (high - low);
48 : : }
49 : :
50 : : static ExessOrder
51 : 1785 : compare_field(const unsigned lhs, const unsigned rhs)
52 : : {
53 : : return lhs < rhs ? EXESS_ORDER_STRICTLY_LESS
54 [ + + + + ]: 1785 : : lhs == rhs ? EXESS_ORDER_EQUAL
55 : : : EXESS_ORDER_STRICTLY_GREATER;
56 : : }
57 : :
58 : : static ExessOrder
59 : 506 : compare_date_time_total(const ExessDateTime lhs, const ExessDateTime rhs)
60 : : {
61 [ + + ]: 506 : if (lhs.year != rhs.year) {
62 : 37 : return lhs.year < rhs.year ? EXESS_ORDER_STRICTLY_LESS
63 [ + + ]: 37 : : EXESS_ORDER_STRICTLY_GREATER;
64 : : }
65 : :
66 : 469 : const ExessDateTime lhz = exess_date_time_to_utc(lhs);
67 : 469 : const ExessDateTime rhz = exess_date_time_to_utc(rhs);
68 : 469 : ExessOrder cmp = compare_field(lhz.month, rhz.month);
69 : :
70 [ + + ]: 469 : cmp = cmp ? cmp : compare_field(lhz.day, rhz.day);
71 [ + + ]: 469 : cmp = cmp ? cmp : compare_field(lhz.hour, rhz.hour);
72 [ + + ]: 469 : cmp = cmp ? cmp : compare_field(lhz.minute, rhz.minute);
73 [ + + ]: 469 : cmp = cmp ? cmp : compare_field(lhz.second, rhz.second);
74 [ + + ]: 469 : cmp = cmp ? cmp : compare_field(lhz.nanosecond, rhz.nanosecond);
75 : 469 : return cmp;
76 : : }
77 : :
78 : : static ExessDateTime
79 : 1070 : to_utc(const ExessDateTime s, const ExessDuration offset)
80 : : {
81 : 1070 : ExessDateTime r = exess_add_date_time_duration(s, offset);
82 : 1070 : r.zone = EXESS_TIMEZONE_UTC;
83 : 1070 : return r;
84 : : }
85 : :
86 : : EXESS_NONBLOCKING ExessDateTime
87 : 948 : exess_date_time_to_utc(const ExessDateTime datetime)
88 : : {
89 : 948 : const ExessDuration offset = {0U, -datetime.zone * 15 * 60, 0};
90 : 948 : return to_utc(datetime, offset);
91 : : }
92 : :
93 : : static EXESS_NONBLOCKING ExessOrder
94 : 70 : compare_date_time_partial(const ExessDateTime lhs, const ExessDateTime rhs)
95 : : {
96 : : // See https://www.w3.org/TR/xmlschema-2/#dateTime-order
97 : : // and https://www.w3.org/TR/xmlschema11-2/#theSevenPropertyModel
98 : :
99 : 70 : EXESS_CONSTEXPR ExessDuration plus_14h = {0U, 14 * 60 * 60, 0};
100 : 70 : EXESS_CONSTEXPR ExessDuration minus_14h = {0U, -14 * 60 * 60, 0};
101 : :
102 : 70 : ExessOrder order = EXESS_ORDER_EQUAL;
103 : :
104 [ + + ]: 70 : if (lhs.zone != EXESS_TIMEZONE_LOCAL) {
105 : 35 : order = compare_date_time_total(lhs, to_utc(rhs, minus_14h));
106 [ + + ]: 35 : if (order < 0) {
107 : 12 : return order;
108 : : }
109 : :
110 : 23 : order = compare_date_time_total(lhs, to_utc(rhs, plus_14h));
111 [ + + ]: 23 : if (order > 0) {
112 : 6 : return order;
113 : : }
114 : :
115 : : // Incomparable, arbitrarily put local time first
116 : 17 : return EXESS_ORDER_MAYBE_GREATER;
117 : : }
118 : :
119 : 35 : order = compare_date_time_total(to_utc(lhs, plus_14h), rhs);
120 [ + + ]: 35 : if (order < 0) {
121 : 6 : return order;
122 : : }
123 : :
124 : 29 : order = compare_date_time_total(to_utc(lhs, minus_14h), rhs);
125 [ + + ]: 29 : if (order > 0) {
126 : 12 : return order;
127 : : }
128 : :
129 : : // Incomparable, arbitrarily put local time first
130 : 17 : return EXESS_ORDER_MAYBE_LESS;
131 : : }
132 : :
133 : : EXESS_NONBLOCKING ExessOrder
134 : 454 : exess_compare_date_time(const ExessDateTime lhs, const ExessDateTime rhs)
135 : : {
136 [ + + ]: 88 : return ((lhs.zone == rhs.zone) || (lhs.zone != EXESS_TIMEZONE_LOCAL &&
137 [ + + ]: 53 : rhs.zone != EXESS_TIMEZONE_LOCAL))
138 : 384 : ? compare_date_time_total(lhs, rhs)
139 [ + + ]: 908 : : compare_date_time_partial(lhs, rhs);
140 : : }
141 : :
142 : : static int32_t
143 : 5864 : add_field(const int32_t lhs,
144 : : const int32_t rhs,
145 : : const int32_t max,
146 : : int32_t* const carry)
147 : : {
148 : 5864 : const int32_t temp = lhs + rhs + *carry;
149 : :
150 [ + + ]: 5864 : if (temp < 0) {
151 : 953 : *carry = (temp / max) - 1;
152 : 953 : return max + (temp % max);
153 : : }
154 : :
155 : 4911 : *carry = temp / max;
156 : 4911 : return temp % max;
157 : : }
158 : :
159 : : /**
160 : : Set the day, carrying into into months and years as necessary.
161 : :
162 : : Note that the algorithm in the spec first clamps here, but we don't because
163 : : no such dateTime should exist (exess_read_date_time refuses to read them).
164 : : This might return the infinite past or future.
165 : : */
166 : : EXESS_CONST_FUNC static ExessDateTime
167 : 1466 : carry_set_day(ExessDateTime e, int day)
168 : : {
169 [ + + + + ]: 2262 : while (day < 1 || day > days_in_month(e.year, e.month)) {
170 [ + + ]: 804 : if (day < 1) {
171 [ + + ]: 276 : if (--e.month == 0) {
172 [ + + ]: 41 : if (e.year == INT16_MIN) {
173 : 4 : return infinite_past(e.zone);
174 : : }
175 : :
176 : 37 : --e.year;
177 : 37 : e.month = 12;
178 : : }
179 : 272 : day += days_in_month(e.year, e.month);
180 : : } else {
181 : 528 : day -= days_in_month(e.year, e.month);
182 [ + + ]: 528 : if (++e.month > 12) {
183 [ + + ]: 67 : if (e.year == INT16_MAX) {
184 : 4 : return infinite_future(e.zone);
185 : : }
186 : :
187 : 63 : ++e.year;
188 : 63 : e.month = (uint8_t)modulo(e.month, 1, 13);
189 : : }
190 : : }
191 : : }
192 : :
193 : 1458 : e.day = (uint8_t)day;
194 : 1458 : return e;
195 : : }
196 : :
197 : : EXESS_NONBLOCKING ExessDateTime
198 : 1470 : exess_add_date_time_duration(const ExessDateTime s, const ExessDuration d)
199 : : {
200 : : // See https://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
201 : : // and https://www.w3.org/TR/xmlschema11-2/#sec-dt-arith
202 : : // This algorithm is modified here to support subtraction when d is negative
203 : :
204 : 1470 : EXESS_CONSTEXPR int32_t giga = 1000000000;
205 : :
206 : 1470 : const int32_t d_year = d.months / 12;
207 : 1470 : const int32_t d_month = d.months % 12;
208 : 1470 : const int32_t d_day = d.seconds / (24 * 60 * 60);
209 : 1470 : const int32_t d_hour = d.seconds / 60 / 60 % 24;
210 : 1470 : const int32_t d_minute = d.seconds / 60 % 60;
211 : 1470 : const int32_t d_second = d.seconds % 60;
212 : :
213 : 1470 : ExessDateTime e = {0, 0U, 0U, s.zone, 0U, 0U, 0U, 0U};
214 : 1470 : int32_t temp = 0;
215 : 1470 : int32_t carry = 0;
216 : :
217 : : // Months (may be modified additionally below)
218 : 1470 : temp = s.month + d_month;
219 [ + + ]: 1470 : if (temp <= 0) {
220 : 3 : e.month = (uint8_t)(12 + modulo(temp, 1, 13));
221 : 3 : carry = quotient(temp, 1, 13) - 1;
222 : : } else {
223 : 1467 : e.month = (uint8_t)modulo(temp, 1, 13);
224 : 1467 : carry = quotient(temp, 1, 13);
225 : : }
226 : :
227 : : // Years (may be modified additionally below)
228 : 1470 : temp = s.year + d_year + carry;
229 : 1470 : carry = 0;
230 [ + + ]: 1470 : if (temp > INT16_MAX) {
231 : 2 : return infinite_future(s.zone);
232 : : }
233 : :
234 [ + + ]: 1468 : if (temp < INT16_MIN) {
235 : 2 : return infinite_past(s.zone);
236 : : }
237 : :
238 : 1466 : e.year = (int16_t)temp;
239 : :
240 : : // Day time
241 : :
242 : 1466 : e.nanosecond =
243 : 1466 : (uint32_t)add_field((int32_t)s.nanosecond, d.nanoseconds, giga, &carry);
244 : :
245 : 1466 : e.second = (uint8_t)add_field(s.second, d_second, 60, &carry);
246 : 1466 : e.minute = (uint8_t)add_field(s.minute, d_minute, 60, &carry);
247 : 1466 : e.hour = (uint8_t)add_field(s.hour, d_hour, 24, &carry);
248 : :
249 : 1466 : return carry_set_day(e, s.day + d_day + carry);
250 : : }
251 : :
252 : : EXESS_NONBLOCKING ExessResult
253 : 227 : exess_read_date_time(const char* const str, ExessDateTime* const out)
254 : : {
255 : 227 : memset(out, 0, sizeof(*out));
256 : :
257 : : // Read date
258 : 227 : ExessDate date = {0, 0U, 0U, EXESS_TIMEZONE_LOCAL};
259 : 227 : const ExessResult dr = read_date_numbers(&date, str);
260 : :
261 : 227 : out->year = date.year;
262 : 227 : out->month = date.month;
263 : 227 : out->day = date.day;
264 [ + + ]: 227 : if (dr.status) {
265 : 4 : return dr;
266 : : }
267 : :
268 : 223 : size_t i = dr.count;
269 [ + + ]: 223 : if (str[i] != 'T') {
270 : 3 : return RESULT(EXESS_EXPECTED_TIME_SEP, i);
271 : : }
272 : :
273 : 220 : ++i;
274 : :
275 : : // Read time
276 : 220 : ExessTime time = {EXESS_TIMEZONE_LOCAL, 0U, 0U, 0U, 0U};
277 : 220 : const ExessResult tr = read_time(&time, str + i);
278 : :
279 : 220 : out->zone = time.zone;
280 : 220 : out->hour = time.hour;
281 : 220 : out->minute = time.minute;
282 : 220 : out->second = time.second;
283 : 220 : out->nanosecond = time.nanosecond;
284 : 220 : i += tr.count;
285 [ + + ]: 220 : if (tr.status) {
286 : 2 : return RESULT(tr.status, i);
287 : : }
288 : :
289 : 218 : return RESULT(EXESS_SUCCESS, i);
290 : : }
291 : :
292 : : EXESS_NONBLOCKING ExessResult
293 : 120 : exess_write_date_time(const ExessDateTime value,
294 : : const size_t buf_size,
295 : : char* const buf)
296 : : {
297 : 120 : const ExessDate date = {
298 : 120 : value.year, value.month, value.day, EXESS_TIMEZONE_LOCAL};
299 : :
300 : 120 : const ExessTime time = {
301 : 120 : value.zone, value.hour, value.minute, value.second, value.nanosecond};
302 : :
303 [ + + + + ]: 120 : if (!in_range(value.month, 1, 12) || !in_range(value.day, 1, 31) ||
304 [ + + + + ]: 116 : !in_range(value.hour, 0, 24) || !in_range(value.minute, 0, 59) ||
305 [ + + + + ]: 114 : !in_range(value.second, 0, 59) || value.nanosecond > 999999999 ||
306 [ + + ]: 112 : (value.hour == 24 &&
307 [ + + + + : 6 : (value.minute || value.second || value.nanosecond))) {
+ + ]
308 : 11 : return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
309 : : }
310 : :
311 : 109 : const ExessResult dr = exess_write_date(date, buf_size, buf);
312 : 109 : const size_t o = dr.count + write_char('T', buf_size, buf, dr.count);
313 : 109 : const ExessResult tr = write_time(time, buf_size, buf, o);
314 : 109 : return end_write(
315 [ + + ]: 109 : dr.status ? dr.status : tr.status, buf_size, buf, o + tr.count);
316 : : }
|