Branch data Line data Source code
1 : : // Copyright 2011-2025 David Robillard <d@drobilla.net>
2 : : // SPDX-License-Identifier: ISC
3 : :
4 : : #undef NDEBUG
5 : :
6 : : #include "write_test_utils.h"
7 : :
8 : : #include <exess/exess.h>
9 : :
10 : : #include <assert.h>
11 : : #include <stdint.h>
12 : : #include <string.h>
13 : :
14 : : static const ExessDateTime local = {2001, 2, 3, 56, 4, 5, 6, 0};
15 : : static const ExessDateTime utc = {2001, 2, 3, EXESS_TIMEZONE_UTC, 4, 5, 6, 0};
16 : : static const ExessDateTime lowest = {INT16_MIN, 1, 1, -56, 0, 0, 0, 0};
17 : : static const ExessDateTime highest = {INT16_MAX, 12, 31, 56, 24, 0, 0, 0};
18 : :
19 : : static const ExessDateTime longest =
20 : : {INT16_MIN, 12, 31, 56, 23, 59, 59, 999999999};
21 : :
22 : : static const ExessDateTime utc_min =
23 : : {INT16_MIN, 1, 1, EXESS_TIMEZONE_UTC, 0, 0, 0, 0};
24 : :
25 : : static const ExessDateTime utc_max =
26 : : {INT16_MAX, 12, 31, EXESS_TIMEZONE_UTC, 24, 0, 0, 0};
27 : :
28 : : static const ExessDateTime nano = {2001, 1, 1, 56, 0, 0, 0, 1};
29 : : static const ExessDateTime garbage1 = {2004, 0, 1, 56, 12, 0, 0, 0};
30 : : static const ExessDateTime garbage2 = {2005, 13, 1, 56, 12, 0, 0, 0};
31 : : static const ExessDateTime garbage3 = {2006, 1, 0, 56, 12, 0, 0, 0};
32 : : static const ExessDateTime garbage4 = {2006, 1, 32, 56, 12, 0, 0, 0};
33 : : static const ExessDateTime garbage5 = {2001, 2, 3, 56, 0, 0, 0, 1000000000};
34 : : static const ExessDateTime garbage6 = {2001, 2, 3, 56, 0, 0, 60, 0};
35 : : static const ExessDateTime garbage7 = {2001, 2, 3, 56, 0, 60, 0, 0};
36 : : static const ExessDateTime garbage8 = {2001, 2, 3, 56, 24, 0, 0, 1};
37 : : static const ExessDateTime garbage9 = {2001, 2, 3, 56, 24, 0, 1, 0};
38 : : static const ExessDateTime garbage10 = {2001, 2, 3, 56, 24, 1, 0, 0};
39 : : static const ExessDateTime garbage11 = {2001, 2, 3, 56, 25, 0, 0, 0};
40 : :
41 : : static void
42 : 42 : check_add(const char* const datetime_string,
43 : : const char* const duration_string,
44 : : const char* const result_string)
45 : : {
46 : 42 : ExessDateTime datetime = {0, 0U, 0U, EXESS_TIMEZONE_LOCAL, 0U, 0U, 0U, 0U};
47 : 42 : ExessDuration duration = {0U, 0U, 0U};
48 : :
49 : 42 : ExessResult r = exess_read_date_time(datetime_string, &datetime);
50 [ - + ]: 42 : assert(!r.status);
51 : :
52 : 42 : r = exess_read_duration(duration_string, &duration);
53 [ - + ]: 42 : assert(!r.status);
54 : :
55 : 42 : const ExessDateTime result = exess_add_date_time_duration(datetime, duration);
56 : :
57 : 42 : char buf[EXESS_MAX_DATE_TIME_LENGTH + 1] = {42};
58 : 42 : init_out_buf(sizeof(buf), buf);
59 : :
60 : 42 : r = exess_write_date_time(result, sizeof(buf), buf);
61 [ - + ]: 42 : assert(!r.status);
62 [ - + ]: 42 : assert(!strcmp(buf, result_string));
63 : 42 : }
64 : :
65 : : static void
66 : 6 : check_is_underflow(const ExessDateTime datetime, const ExessTimezone zone)
67 : : {
68 [ - + ]: 6 : assert(datetime.year == INT16_MIN);
69 [ - + ]: 6 : assert(datetime.month == 0);
70 [ - + ]: 6 : assert(datetime.day == 0);
71 [ - + ]: 6 : assert(datetime.zone == zone);
72 [ - + ]: 6 : assert(datetime.hour == 0);
73 [ - + ]: 6 : assert(datetime.minute == 0);
74 [ - + ]: 6 : assert(datetime.second == 0);
75 [ - + ]: 6 : assert(datetime.nanosecond == 0);
76 : 6 : }
77 : :
78 : : static void
79 : 6 : check_is_overflow(const ExessDateTime datetime, const ExessTimezone zone)
80 : : {
81 [ - + ]: 6 : assert(datetime.year == INT16_MAX);
82 [ - + ]: 6 : assert(datetime.month == UINT8_MAX);
83 [ - + ]: 6 : assert(datetime.day == UINT8_MAX);
84 [ - + ]: 6 : assert(datetime.zone == zone);
85 [ - + ]: 6 : assert(datetime.hour == UINT8_MAX);
86 [ - + ]: 6 : assert(datetime.minute == UINT8_MAX);
87 [ - + ]: 6 : assert(datetime.second == UINT8_MAX);
88 [ - + ]: 6 : assert(datetime.nanosecond == UINT32_MAX);
89 : 6 : }
90 : :
91 : : static void
92 : 1 : test_add(void)
93 : : {
94 : : // Simple cases
95 : 1 : check_add("2001-01-01T00:00:00", "PT1.5S", "2001-01-01T00:00:01.5");
96 : 1 : check_add("2001-01-01T00:00:00", "PT1M", "2001-01-01T00:01:00");
97 : 1 : check_add("2001-01-01T00:00:00", "PT1H", "2001-01-01T01:00:00");
98 : 1 : check_add("2001-01-01T00:00:00", "P1D", "2001-01-02T00:00:00");
99 : 1 : check_add("2001-01-01T00:00:00", "P1M", "2001-02-01T00:00:00");
100 : 1 : check_add("2001-01-01T00:00:00", "P1Y", "2002-01-01T00:00:00");
101 : 1 : check_add("2001-02-02T02:02:02", "-PT1.5S", "2001-02-02T02:02:00.5");
102 : 1 : check_add("2001-02-02T02:02:02", "-PT1M", "2001-02-02T02:01:02");
103 : 1 : check_add("2001-02-02T02:02:02", "-PT1H", "2001-02-02T01:02:02");
104 : 1 : check_add("2001-02-02T02:02:02", "-P1D", "2001-02-01T02:02:02");
105 : 1 : check_add("2001-02-02T02:02:02", "-P1M", "2001-01-02T02:02:02");
106 : 1 : check_add("2001-02-02T02:02:02", "-P1Y", "2000-02-02T02:02:02");
107 : :
108 : : // Positive carrying
109 : 1 : check_add("2001-01-01T00:00:59", "PT1S", "2001-01-01T00:01:00");
110 : 1 : check_add("2001-01-01T00:59:00", "PT1M", "2001-01-01T01:00:00");
111 : 1 : check_add("2001-01-01T23:00:00", "PT1H", "2001-01-02T00:00:00");
112 : 1 : check_add("2001-01-31T00:00:00", "P1D", "2001-02-01T00:00:00");
113 : 1 : check_add("2001-12-01T00:00:00", "P1M", "2002-01-01T00:00:00");
114 : :
115 : : // Negative carrying
116 : 1 : check_add("2001-01-01T00:01:00", "-PT1S", "2001-01-01T00:00:59");
117 : 1 : check_add("2001-02-01T01:00:00", "-PT1M", "2001-02-01T00:59:00");
118 : 1 : check_add("2001-02-02T00:00:00", "-PT1H", "2001-02-01T23:00:00");
119 : 1 : check_add("2001-02-01T00:00:00", "-P1D", "2001-01-31T00:00:00");
120 : 1 : check_add("2001-01-01T00:00:00", "-P1M", "2000-12-01T00:00:00");
121 : :
122 : : // Underflow and overflow
123 : :
124 : : static const ExessDuration minus_month = {-1, 0, 0};
125 : : static const ExessDuration minus_second = {0, -1, 0};
126 : : static const ExessDuration minus_nanosecond = {0, 0, -1};
127 : : static const ExessDuration plus_month = {1, 0, 0};
128 : : static const ExessDuration plus_second = {0, 1, 0};
129 : : static const ExessDuration plus_nanosecond = {0, 0, 1};
130 : :
131 : 1 : check_is_underflow(exess_add_date_time_duration(lowest, minus_month),
132 : 1 : lowest.zone);
133 : 1 : check_is_underflow(exess_add_date_time_duration(lowest, minus_second),
134 : 1 : lowest.zone);
135 : 1 : check_is_underflow(exess_add_date_time_duration(lowest, minus_nanosecond),
136 : 1 : lowest.zone);
137 : :
138 : 1 : check_is_underflow(exess_add_date_time_duration(utc_min, minus_month),
139 : 1 : utc_min.zone);
140 : 1 : check_is_underflow(exess_add_date_time_duration(utc_min, minus_second),
141 : 1 : utc_min.zone);
142 : 1 : check_is_underflow(exess_add_date_time_duration(utc_min, minus_nanosecond),
143 : 1 : utc_min.zone);
144 : :
145 : 1 : check_is_overflow(exess_add_date_time_duration(highest, plus_month),
146 : 1 : highest.zone);
147 : 1 : check_is_overflow(exess_add_date_time_duration(highest, plus_second),
148 : 1 : highest.zone);
149 : 1 : check_is_overflow(exess_add_date_time_duration(highest, plus_nanosecond),
150 : 1 : highest.zone);
151 : :
152 : 1 : check_is_overflow(exess_add_date_time_duration(utc_max, plus_month),
153 : 1 : utc_max.zone);
154 : 1 : check_is_overflow(exess_add_date_time_duration(utc_max, plus_second),
155 : 1 : utc_max.zone);
156 : 1 : check_is_overflow(exess_add_date_time_duration(utc_max, plus_nanosecond),
157 : 1 : utc_max.zone);
158 : 1 : }
159 : :
160 : : static void
161 : 1 : test_calendar(void)
162 : : {
163 : : // February has 28 days in non-leap years
164 : 1 : check_add("-1400-02-28T12:00:00", "P1D", "-1400-03-01T12:00:00");
165 : 1 : check_add("-0399-02-28T12:00:00", "P1D", "-0399-03-01T12:00:00");
166 : 1 : check_add("-0300-02-28T12:00:00", "P1D", "-0300-03-01T12:00:00");
167 : 1 : check_add("-0200-02-28T12:00:00", "P1D", "-0200-03-01T12:00:00");
168 : 1 : check_add("-0100-02-28T12:00:00", "P1D", "-0100-03-01T12:00:00");
169 : 1 : check_add("-0001-02-28T12:00:00", "P1D", "-0001-03-01T12:00:00");
170 : 1 : check_add("0001-02-28T12:00:00", "P1D", "0001-03-01T12:00:00");
171 : 1 : check_add("0100-02-28T12:00:00", "P1D", "0100-03-01T12:00:00");
172 : 1 : check_add("0200-02-28T12:00:00", "P1D", "0200-03-01T12:00:00");
173 : 1 : check_add("0300-02-28T12:00:00", "P1D", "0300-03-01T12:00:00");
174 : 1 : check_add("0399-02-28T12:00:00", "P1D", "0399-03-01T12:00:00");
175 : 1 : check_add("1400-02-28T12:00:00", "P1D", "1400-03-01T12:00:00");
176 : :
177 : : // February has 29 days in leap years
178 : 1 : check_add("-0400-02-28T12:00:00", "P1D", "-0400-02-29T12:00:00");
179 : 1 : check_add("-0096-02-28T12:00:00", "P1D", "-0096-02-29T12:00:00");
180 : 1 : check_add("-0004-02-28T12:00:00", "P1D", "-0004-02-29T12:00:00");
181 : 1 : check_add("0000-02-28T12:00:00", "P1D", "0000-02-29T12:00:00");
182 : 1 : check_add("0004-02-28T12:00:00", "P1D", "0004-02-29T12:00:00");
183 : 1 : check_add("0096-02-28T12:00:00", "P1D", "0096-02-29T12:00:00");
184 : 1 : check_add("0400-02-28T12:00:00", "P1D", "0400-02-29T12:00:00");
185 : 1 : check_add("1600-02-28T12:00:00", "P1D", "1600-02-29T12:00:00");
186 : 1 : }
187 : :
188 : : static void
189 : 10 : check_to_utc(const char* const datetime_string, const char* const result_string)
190 : : {
191 : 10 : ExessDateTime datetime = {0, 0U, 0U, EXESS_TIMEZONE_LOCAL, 0U, 0U, 0U, 0U};
192 : :
193 : 10 : ExessResult r = exess_read_date_time(datetime_string, &datetime);
194 [ - + ]: 10 : assert(!r.status);
195 : :
196 : 10 : const ExessDateTime result = exess_date_time_to_utc(datetime);
197 : :
198 : 10 : char buf[EXESS_MAX_DATE_TIME_LENGTH + 1] = {42};
199 : 10 : init_out_buf(sizeof(buf), buf);
200 : :
201 : 10 : r = exess_write_date_time(result, sizeof(buf), buf);
202 [ - + ]: 10 : assert(!r.status);
203 [ - + ]: 10 : assert(!strcmp(buf, result_string));
204 : 10 : }
205 : :
206 : : static void
207 : 1 : test_to_utc(void)
208 : : {
209 : : // Zoned
210 : 1 : check_to_utc("2001-02-03T04:46:59-00:15", "2001-02-03T05:01:59Z");
211 : :
212 : : // Positive carry: minute => hour => day
213 : 1 : check_to_utc("2001-02-03T23:46:59-00:15", "2001-02-04T00:01:59Z");
214 : :
215 : : // Positive carry: minute => hour => day => month (common year)
216 : 1 : check_to_utc("2001-02-28T23:46:59-00:15", "2001-03-01T00:01:59Z");
217 : :
218 : : // Positive carry: minute => hour => day => month (leap year)
219 : 1 : check_to_utc("2000-02-29T23:46:59-00:15", "2000-03-01T00:01:59Z");
220 : :
221 : : // Positive carry: minute => hour => day => month => year
222 : 1 : check_to_utc("2001-12-31T23:46:59-00:15", "2002-01-01T00:01:59Z");
223 : :
224 : : // Negative carry: minute => hour
225 : 1 : check_to_utc("2001-02-03T04:14:59+00:15", "2001-02-03T03:59:59Z");
226 : :
227 : : // Negative carry: minute => hour => day
228 : 1 : check_to_utc("2001-02-02T00:14:59+00:15", "2001-02-01T23:59:59Z");
229 : :
230 : : // Negative carry: minute => hour => day => month (common year)
231 : 1 : check_to_utc("2001-03-01T00:14:59+00:15", "2001-02-28T23:59:59Z");
232 : :
233 : : // Negative carry: minute => hour => day => month (leap year)
234 : 1 : check_to_utc("2000-03-01T00:14:59+00:15", "2000-02-29T23:59:59Z");
235 : :
236 : : // Negative carry: minute => hour => day => month => year
237 : 1 : check_to_utc("2001-01-01T00:14:59+00:15", "2000-12-31T23:59:59Z");
238 : 1 : }
239 : :
240 : : static void
241 : 12 : check_read(const char* const string,
242 : : const ExessStatus expected_status,
243 : : const size_t expected_count,
244 : : const int64_t expected_year,
245 : : const uint8_t expected_month,
246 : : const uint8_t expected_day,
247 : : const uint8_t expected_hour,
248 : : const uint8_t expected_minute,
249 : : const uint8_t expected_second,
250 : : const uint32_t expected_nanosecond,
251 : : const ExessTimezone expected_zone)
252 : : {
253 : 12 : ExessDateTime value = {0, 0, 0, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
254 : :
255 : 12 : const ExessResult r = exess_read_date_time(string, &value);
256 [ - + ]: 12 : assert(r.status == expected_status);
257 [ - + ]: 12 : assert(r.count == expected_count);
258 [ - + ]: 12 : assert(value.year == expected_year);
259 [ - + ]: 12 : assert(value.month == expected_month);
260 [ - + ]: 12 : assert(value.day == expected_day);
261 [ - + ]: 12 : assert(value.hour == expected_hour);
262 [ - + ]: 12 : assert(value.minute == expected_minute);
263 [ - + ]: 12 : assert(value.second == expected_second);
264 [ - + ]: 12 : assert(value.nanosecond == expected_nanosecond);
265 [ - + ]: 12 : assert(value.zone == expected_zone);
266 : 12 : }
267 : :
268 : : static void
269 : 1 : test_read_date_time(void)
270 : : {
271 : : // Simple values
272 : :
273 : 1 : check_read("2001-02-03T04:05:06",
274 : : EXESS_SUCCESS,
275 : : 19,
276 : : 2001,
277 : : 2,
278 : : 3,
279 : : 4,
280 : : 5,
281 : : 6,
282 : : 0,
283 : : EXESS_TIMEZONE_LOCAL);
284 : :
285 : 1 : check_read("2001-02-03T04:05:06Z",
286 : : EXESS_SUCCESS,
287 : : 20,
288 : : 2001,
289 : : 2,
290 : : 3,
291 : : 4,
292 : : 5,
293 : : 6,
294 : : 0,
295 : : EXESS_TIMEZONE_UTC);
296 : :
297 : 1 : check_read("2004-04-12T13:20:15.5",
298 : : EXESS_SUCCESS,
299 : : 21,
300 : : 2004,
301 : : 4,
302 : : 12,
303 : : 13,
304 : : 20,
305 : : 15,
306 : : 500000000,
307 : : EXESS_TIMEZONE_LOCAL);
308 : :
309 : : // Time zone offsets
310 : :
311 : 1 : check_read("-32768-01-01T00:00:00.000000001+14:00",
312 : : EXESS_SUCCESS,
313 : : EXESS_MAX_DATE_TIME_LENGTH,
314 : : -32768,
315 : : 1,
316 : : 1,
317 : : 0,
318 : : 0,
319 : : 0,
320 : : 1,
321 : 1 : exess_timezone(14, 0));
322 : :
323 : 1 : check_read("2001-02-03T04:05:06.7-08:00",
324 : : EXESS_SUCCESS,
325 : : 27,
326 : : 2001,
327 : : 2,
328 : : 3,
329 : : 4,
330 : : 5,
331 : : 6,
332 : : 700000000,
333 : 1 : exess_timezone(-8, 0));
334 : :
335 : : // Midnight
336 : 1 : check_read("2001-02-03T24:00:00",
337 : : EXESS_SUCCESS,
338 : : 19,
339 : : 2001,
340 : : 2,
341 : : 3,
342 : : 24,
343 : : 0,
344 : : 0,
345 : : 0,
346 : : EXESS_TIMEZONE_LOCAL);
347 : :
348 : : // Too many nanosecond digits (expecting a timezone after 9 digits)
349 : 1 : check_read("2001-02-03T04:05:06.123456789123",
350 : : EXESS_OUT_OF_RANGE,
351 : : 29,
352 : : 2001,
353 : : 2,
354 : : 3,
355 : : 4,
356 : : 5,
357 : : 6,
358 : : 123456789,
359 : : EXESS_TIMEZONE_LOCAL);
360 : :
361 : : // Garbage
362 : :
363 : 1 : check_read("2004-04-12T13:00",
364 : : EXESS_EXPECTED_COLON,
365 : : 16,
366 : : 2004,
367 : : 4,
368 : : 12,
369 : : 13,
370 : : 0,
371 : : 0,
372 : : 0,
373 : : EXESS_TIMEZONE_LOCAL);
374 : :
375 : 1 : check_read(
376 : : "2004-04-121", EXESS_EXPECTED_TIME_SEP, 10, 2004, 4, 12, 0, 0, 0, 0, 0);
377 : :
378 : 1 : check_read(
379 : : "99-04-12T13:00", EXESS_EXPECTED_DIGIT, 2, 99, 0, 0, 0, 0, 0, 0, 0);
380 : :
381 : 1 : check_read(
382 : : "2004-04-12", EXESS_EXPECTED_TIME_SEP, 10, 2004, 4, 12, 0, 0, 0, 0, 0);
383 : :
384 : 1 : check_read("2004-04-12-05:00",
385 : : EXESS_EXPECTED_TIME_SEP,
386 : : 10,
387 : : 2004,
388 : : 4,
389 : : 12,
390 : : 0,
391 : : 0,
392 : : 0,
393 : : 0,
394 : : 0);
395 : 1 : }
396 : :
397 : : static void
398 : 21 : check_write(const ExessDateTime value,
399 : : const ExessStatus expected_status,
400 : : const size_t buf_size,
401 : : const char* const expected_string)
402 : : {
403 : 21 : char buf[EXESS_MAX_DATE_TIME_LENGTH + 1] = {42};
404 : 21 : init_out_buf(sizeof(buf), buf);
405 : :
406 [ - + ]: 21 : assert(buf_size <= sizeof(buf));
407 : :
408 : 21 : const ExessResult r = exess_write_date_time(value, buf_size, buf);
409 [ - + ]: 21 : assert(r.status == expected_status);
410 [ + + ]: 21 : if (!r.status) {
411 [ - + ]: 6 : assert(!strcmp(buf, expected_string));
412 [ - + ]: 6 : assert(r.count == strlen(buf));
413 [ - + ]: 6 : assert(exess_write_date_time(value, 0, NULL).count == r.count);
414 : : }
415 : 21 : }
416 : :
417 : : static void
418 : 1 : test_write_date_time(void)
419 : : {
420 : 1 : check_write(local, EXESS_SUCCESS, 26, "2001-02-03T04:05:06+14:00");
421 : 1 : check_write(utc, EXESS_SUCCESS, 21, "2001-02-03T04:05:06Z");
422 : 1 : check_write(lowest, EXESS_SUCCESS, 28, "-32768-01-01T00:00:00-14:00");
423 : 1 : check_write(highest, EXESS_SUCCESS, 27, "32767-12-31T24:00:00+14:00");
424 : 1 : check_write(
425 : : longest, EXESS_SUCCESS, 38, "-32768-12-31T23:59:59.999999999+14:00");
426 : 1 : check_write(nano, EXESS_SUCCESS, 36, "2001-01-01T00:00:00.000000001+14:00");
427 : :
428 : 1 : check_write(garbage1, EXESS_BAD_VALUE, 38, "");
429 : 1 : check_write(garbage2, EXESS_BAD_VALUE, 38, "");
430 : 1 : check_write(garbage3, EXESS_BAD_VALUE, 38, "");
431 : 1 : check_write(garbage4, EXESS_BAD_VALUE, 38, "");
432 : 1 : check_write(garbage5, EXESS_BAD_VALUE, 38, "");
433 : 1 : check_write(garbage6, EXESS_BAD_VALUE, 38, "");
434 : 1 : check_write(garbage7, EXESS_BAD_VALUE, 38, "");
435 : 1 : check_write(garbage8, EXESS_BAD_VALUE, 38, "");
436 : 1 : check_write(garbage9, EXESS_BAD_VALUE, 38, "");
437 : 1 : check_write(garbage10, EXESS_BAD_VALUE, 38, "");
438 : 1 : check_write(garbage11, EXESS_BAD_VALUE, 38, "");
439 : :
440 : 1 : check_write(lowest, EXESS_NO_SPACE, 12, "");
441 : 1 : check_write(lowest, EXESS_NO_SPACE, 17, "");
442 : 1 : check_write(lowest, EXESS_NO_SPACE, 18, "");
443 : 1 : check_write(lowest, EXESS_NO_SPACE, 21, "");
444 : :
445 : : // Check that nothing is written when there isn't enough space
446 : 1 : char c = 42;
447 : 1 : const ExessResult r = exess_write_date_time(highest, 0, &c);
448 [ - + ]: 1 : assert(c == 42);
449 [ - + ]: 1 : assert(r.status == EXESS_NO_SPACE);
450 [ - + ]: 1 : assert(r.count == 26);
451 : 1 : }
452 : :
453 : : int
454 : 1 : main(void)
455 : : {
456 : 1 : test_add();
457 : 1 : test_calendar();
458 : 1 : test_to_utc();
459 : 1 : test_read_date_time();
460 : 1 : test_write_date_time();
461 : :
462 : 1 : return 0;
463 : : }
|