diff options
Diffstat (limited to 'subprojects/exess/src/duration.c')
-rw-r--r-- | subprojects/exess/src/duration.c | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/subprojects/exess/src/duration.c b/subprojects/exess/src/duration.c new file mode 100644 index 00000000..0a753fd3 --- /dev/null +++ b/subprojects/exess/src/duration.c @@ -0,0 +1,322 @@ +/* + 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 "read_utils.h" +#include "string_utils.h" +#include "time_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +typedef enum { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND } Field; + +static ExessStatus +set_field(ExessDuration* const out, + const Field current_field, + const Field field, + const uint32_t value) +{ + if (value >= INT32_MAX) { + return EXESS_OUT_OF_RANGE; + } + + if (field < current_field) { + return EXESS_BAD_ORDER; + } + + switch (field) { + case YEAR: + out->months = (int32_t)(12 * lrint(value)); + break; + case MONTH: + out->months = (int32_t)(out->months + lrint(value)); + break; + case DAY: + out->seconds = (int32_t)(24 * 60 * 60 * lrint(value)); + break; + case HOUR: + out->seconds = (int32_t)(out->seconds + 60 * 60 * lrint(value)); + break; + case MINUTE: + out->seconds = (int32_t)(out->seconds + 60 * lrint(value)); + break; + case SECOND: + out->seconds = (int32_t)(out->seconds + lrint(value)); + break; + } + + return EXESS_SUCCESS; +} + +static ExessResult +read_date(ExessDuration* const out, const Field field, const char* const str) +{ + uint32_t value = 0; + ExessResult r = exess_read_uint(&value, str); + if (r.status > EXESS_EXPECTED_END) { + return r; + } + + size_t i = r.count; + switch (str[i]) { + case 'Y': + if ((r.status = set_field(out, field, YEAR, value))) { + return r; + } + + ++i; + if (str[i] != 'T' && !is_end(str[i])) { + r = read_date(out, MONTH, str + i); + i += r.count; + } + break; + + case 'M': + if ((r.status = set_field(out, field, MONTH, value))) { + return r; + } + + ++i; + if (str[i] != 'T' && !is_end(str[i])) { + r = read_date(out, DAY, str + i); + i += r.count; + } + break; + + case 'D': + if ((r.status = set_field(out, field, DAY, value))) { + return r; + } + + ++i; + break; + + default: + return result(EXESS_EXPECTED_DATE_TAG, i); + } + + return result(r.status, i); +} + +static ExessResult +read_time(ExessDuration* const out, const Field field, const char* const str) +{ + uint32_t value = 0; + ExessResult r = exess_read_uint(&value, str); + if (r.status > EXESS_EXPECTED_END) { + return r; + } + + size_t i = r.count; + ExessResult next = {EXESS_SUCCESS, 0}; + switch (str[i]) { + case '.': { + if (!is_digit(str[++i])) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + uint32_t nanoseconds = 0; + + r = read_nanoseconds(&nanoseconds, str + i); + i += r.count; + + if (str[i] != 'S') { + return result(EXESS_EXPECTED_TIME_TAG, i); + } + + r.status = set_field(out, field, SECOND, value); + out->nanoseconds = (int32_t)nanoseconds; + + break; + } + + case 'H': + r.status = set_field(out, field, HOUR, value); + if (!is_end(str[i + 1])) { + next = read_time(out, MINUTE, str + i + 1); + } + break; + + case 'M': + r.status = set_field(out, field, MINUTE, value); + if (!is_end(str[i + 1])) { + next = read_time(out, SECOND, str + i + 1); + } + break; + + case 'S': + r.status = set_field(out, field, SECOND, value); + break; + + default: + return result(EXESS_EXPECTED_TIME_TAG, i); + } + + if (r.status) { + return r; + } + + return result(next.status, i + 1 + next.count); +} + +ExessResult +exess_read_duration(ExessDuration* const out, const char* const str) +{ + memset(out, 0, sizeof(*out)); + + size_t i = skip_whitespace(str); + bool is_negative = false; + if (str[i] == '-') { + is_negative = true; + ++i; + } + + if (str[i] != 'P') { + return result(EXESS_EXPECTED_DURATION, i); + } + + ++i; + + if (str[i] != 'T') { + ExessResult r = read_date(out, YEAR, str + i); + if (r.status) { + return result(r.status, i + r.count); + } + + i += r.count; + + if (!is_end(str[i]) && str[i] != 'T') { + return result(EXESS_EXPECTED_TIME_SEP, i); + } + } + + if (str[i] == 'T') { + ++i; + + ExessResult r = read_time(out, HOUR, str + i); + if (r.status) { + return result(r.status, i + r.count); + } + + i += r.count; + } + + if (is_negative) { + out->months = -out->months; + out->seconds = -out->seconds; + out->nanoseconds = -out->nanoseconds; + } + + return end_read(EXESS_SUCCESS, str, i); +} + +static size_t +write_int_field(ExessResult* r, + const uint32_t value, + const char tag, + const size_t buf_size, + char* const buf, + const size_t i) +{ + if (!r->status) { + if (value == 0) { + *r = result(EXESS_SUCCESS, 0); + } else if (!buf) { + *r = exess_write_uint(value, buf_size, buf); + ++r->count; + } else { + *r = exess_write_uint(value, buf_size - i, buf + i); + if (!r->status) { + buf[i + r->count++] = tag; + } + } + } + + return r->count; +} + +ExessResult +exess_write_duration(const ExessDuration value, + const size_t buf_size, + char* const buf) +{ + // Write zero as a special case + size_t i = 0; + if (value.months == 0 && value.seconds == 0 && value.nanoseconds == 0) { + i += write_string(3, "P0Y", buf_size, buf, i); + return end_write(EXESS_SUCCESS, buf_size, buf, i); + } + + if (value.months == INT32_MIN || value.seconds == INT32_MIN) { + return end_write(EXESS_OUT_OF_RANGE, buf_size, buf, 0); + } + + const bool is_negative = + (value.months < 0 || value.seconds < 0 || value.nanoseconds < 0); + + if (is_negative && + (value.months > 0 || value.seconds > 0 || value.nanoseconds > 0)) { + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); + } + + // Write duration prefix + if (value.months < 0 || value.seconds < 0 || value.nanoseconds < 0) { + i += write_string(2, "-P", buf_size, buf, i); + } else { + i += write_char('P', buf_size, buf, i); + } + + const uint32_t abs_years = (uint32_t)(abs(value.months) / 12); + const uint32_t abs_months = (uint32_t)(abs(value.months) % 12); + const uint32_t abs_days = (uint32_t)(abs(value.seconds) / (24 * 60 * 60)); + const uint32_t abs_hours = (uint32_t)(abs(value.seconds) / 60 / 60 % 24); + const uint32_t abs_minutes = (uint32_t)(abs(value.seconds) / 60 % 60); + const uint8_t abs_seconds = (uint8_t)(abs(value.seconds) % 60); + const uint32_t abs_nanoseconds = (uint32_t)abs(value.nanoseconds); + + // Write date segments if present + ExessResult r = result(EXESS_SUCCESS, 0); + i += write_int_field(&r, abs_years, 'Y', buf_size, buf, i); + i += write_int_field(&r, abs_months, 'M', buf_size, buf, i); + i += write_int_field(&r, abs_days, 'D', buf_size, buf, i); + + // Write time segments if present + const bool has_time = abs_hours + abs_minutes + abs_seconds + abs_nanoseconds; + if (has_time && !r.status) { + i += write_char('T', buf_size, buf, i); + i += write_int_field(&r, abs_hours, 'H', buf_size, buf, i); + i += write_int_field(&r, abs_minutes, 'M', buf_size, buf, i); + + if (abs_seconds != 0 || abs_nanoseconds != 0) { + r = write_digits(abs_seconds, buf_size, buf, i); + i += r.count; + + if (!r.status && abs_nanoseconds > 0) { + i += write_nanoseconds(abs_nanoseconds, buf_size, buf, i); + } + + i += write_char('S', buf_size, buf, i); + } + } + + return end_write(r.status, buf_size, buf, i); +} |