aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/exess/src/datetime.c
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/exess/src/datetime.c')
-rw-r--r--subprojects/exess/src/datetime.c265
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);
+}