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