diff options
author | David Robillard <d@drobilla.net> | 2021-02-25 10:27:59 -0500 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2021-03-08 23:23:05 -0500 |
commit | c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b (patch) | |
tree | a62995534f5f606ac2f8bae22d525532b824cb5e /subprojects/exess/src/datetime.c | |
parent | 6bcd18ae60482790b645a345f718e7099250f261 (diff) | |
download | serd-c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b.tar.gz serd-c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b.tar.bz2 serd-c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b.zip |
Add exess from git@gitlab.com:drobilla/exess.git 4638b1f
Diffstat (limited to 'subprojects/exess/src/datetime.c')
-rw-r--r-- | subprojects/exess/src/datetime.c | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/subprojects/exess/src/datetime.c b/subprojects/exess/src/datetime.c new file mode 100644 index 00000000..0fe56afe --- /dev/null +++ b/subprojects/exess/src/datetime.c @@ -0,0 +1,265 @@ +/* + Copyright 2019-2021 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "date_utils.h" +#include "read_utils.h" +#include "string_utils.h" +#include "time_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +static inline ExessDateTime +infinite_future(const bool is_utc) +{ + const ExessDateTime result = {INT16_MAX, + UINT8_MAX, + UINT8_MAX, + is_utc, + UINT8_MAX, + UINT8_MAX, + UINT8_MAX, + UINT32_MAX}; + + return result; +} + +static inline ExessDateTime +infinite_past(const bool is_utc) +{ + const ExessDateTime result = {INT16_MIN, 0, 0, is_utc, 0, 0, 0, 0}; + + return result; +} + +static int32_t +modulo(const int32_t a, const int32_t low, const int32_t high) +{ + return ((a - low) % (high - low)) + low; +} + +static int32_t +quotient(const int32_t a, const int32_t low, const int32_t high) +{ + return (a - low) / (high - low); +} + +ExessDateTime +exess_add_datetime_duration(const ExessDateTime s, const ExessDuration d) +{ + /* + See <https://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes>. + This algorithm is modified to support subtraction when d is negative. + */ + + const int32_t d_year = d.months / 12; + const int32_t d_month = d.months % 12; + const int32_t d_day = d.seconds / (24 * 60 * 60); + const int32_t d_hour = d.seconds / 60 / 60 % 24; + const int32_t d_minute = d.seconds / 60 % 60; + const int32_t d_second = d.seconds % 60; + const int32_t d_nanosecond = d.nanoseconds; + + ExessDateTime e = {0, 0u, 0u, s.is_utc, 0u, 0u, 0u, 0u}; + int32_t temp = 0; + int32_t carry = 0; + + // Months (may be modified additionally below) + temp = s.month + d_month; + if (temp <= 0) { + e.month = (uint8_t)(12 + modulo(temp, 1, 13)); + carry = quotient(temp, 1, 13) - 1; + } else { + e.month = (uint8_t)modulo(temp, 1, 13); + carry = quotient(temp, 1, 13); + } + + // Years (may be modified additionally below) + temp = s.year + d_year + carry; + if (temp > INT16_MAX) { + return infinite_future(s.is_utc); + } + + if (temp < INT16_MIN) { + return infinite_past(s.is_utc); + } + + e.year = (int16_t)temp; + + // Nanoseconds + temp = (int32_t)s.nanosecond + d_nanosecond; + if (temp < 0) { + e.nanosecond = (uint32_t)(1000000000 + (temp % 1000000000)); + carry = temp / 1000000000 - 1; + } else { + e.nanosecond = (uint32_t)(temp % 1000000000); + carry = temp / 1000000000; + } + + // Seconds + temp = s.second + d_second + carry; + if (temp < 0) { + e.second = (uint8_t)(60 + (temp % 60)); + carry = temp / 60 - 1; + } else { + e.second = (uint8_t)(temp % 60); + carry = temp / 60; + } + + // Minutes + temp = s.minute + d_minute + carry; + if (temp < 0) { + e.minute = (uint8_t)(60 + (temp % 60)); + carry = temp / 60 - 1; + } else { + e.minute = (uint8_t)(temp % 60); + carry = temp / 60; + } + + // Hours + temp = s.hour + d_hour + carry; + if (temp < 0) { + e.hour = (uint8_t)(24 + (temp % 24)); + carry = temp / 24 - 1; + } else { + e.hour = (uint8_t)(temp % 24); + carry = temp / 24; + } + + /* + Carry days into months and years as necessary. Note that the algorithm in + the spec first clamps here, but we don't because no such datetime should + exist (exess_read_datetime refuses to read them) + */ + int32_t day = s.day + d_day + carry; + while (day < 1 || day > days_in_month(e.year, e.month)) { + if (day < 1) { + if (e.month == 1) { + if (e.year == INT16_MIN) { + return infinite_past(s.is_utc); + } + + --e.year; + e.month = 12; + day += days_in_month(e.year, e.month); + } else { + --e.month; + day += days_in_month(e.year, e.month); + } + } else { + day -= days_in_month(e.year, e.month); + if (++e.month > 12) { + if (e.year == INT16_MAX) { + return infinite_future(s.is_utc); + } + + ++e.year; + e.month = (uint8_t)modulo(e.month, 1, 13); + } + } + } + + e.day = (uint8_t)day; + + return e; +} + +ExessResult +exess_read_datetime(ExessDateTime* const out, const char* const str) +{ + out->year = 0; + out->month = 0; + out->day = 0; + + // Read date + ExessDate date = {0, 0u, 0u, {EXESS_LOCAL}}; + const ExessResult dr = read_date_numbers(&date, str); + if (dr.status) { + return dr; + } + + size_t i = dr.count; + if (str[i] != 'T') { + return result(EXESS_EXPECTED_TIME_SEP, i); + } + + ++i; + + // Read time + ExessTime time = {{INT8_MAX}, 0u, 0u, 0u, 0u}; + const ExessResult tr = exess_read_time(&time, str + i); + if (tr.status) { + return result(tr.status, i + tr.count); + } + + i += tr.count; + + const ExessDateTime datetime = {date.year, + date.month, + date.day, + time.zone.quarter_hours != EXESS_LOCAL, + time.hour, + time.minute, + time.second, + time.nanosecond}; + + if (datetime.is_utc) { + const ExessDuration tz_duration = { + 0u, -time.zone.quarter_hours * 15 * 60, 0}; + + *out = exess_add_datetime_duration(datetime, tz_duration); + } else { + *out = datetime; + } + + return result(EXESS_SUCCESS, i); +} + +ExessResult +exess_write_datetime(const ExessDateTime value, + const size_t buf_size, + char* const buf) +{ + const ExessTimezone local = {EXESS_LOCAL}; + const ExessDate date = {value.year, value.month, value.day, local}; + const ExessTimezone zone = {value.is_utc ? 0 : EXESS_LOCAL}; + const ExessTime time = { + zone, value.hour, value.minute, value.second, value.nanosecond}; + + if (!in_range(value.month, 1, 12) || !in_range(value.day, 1, 31) || + !in_range(value.hour, 0, 24) || !in_range(value.minute, 0, 59) || + !in_range(value.second, 0, 59) || value.nanosecond > 999999999) { + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); + } + + // Write date + ExessResult dr = exess_write_date(date, buf_size, buf); + if (dr.status) { + return end_write(dr.status, buf_size, buf, dr.count); + } + + // Write time delimiter + size_t o = dr.count + write_char('T', buf_size, buf, dr.count); + + // Write time with timezone + const ExessResult tr = write_time(time, buf_size, buf, o); + + return end_write(tr.status, buf_size, buf, o + tr.count); +} |