114 lines
4.2 KiB
C
114 lines
4.2 KiB
C
/*
|
|
* Trainlog timestamp implementation.
|
|
*
|
|
* Owns this module's bounded behavior and does not redefine persistence, synchronization, or UI contracts owned elsewhere.
|
|
*/
|
|
#include "timestamp.h"
|
|
|
|
#include <limits.h>
|
|
|
|
static bool parse_digits(const char *value, size_t count, int *output)
|
|
{
|
|
size_t index;
|
|
int result = 0;
|
|
for (index = 0U; index < count; ++index) {
|
|
if (value[index] < '0' || value[index] > '9') return false;
|
|
result = result * 10 + (value[index] - '0');
|
|
}
|
|
*output = result;
|
|
return true;
|
|
}
|
|
|
|
static bool leap_year(int year)
|
|
{
|
|
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
|
}
|
|
|
|
static int days_in_month(int year, int month)
|
|
{
|
|
static const int DAYS[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
|
return month == 2 && leap_year(year) ? 29 : DAYS[month - 1];
|
|
}
|
|
|
|
/* Proleptic Gregorian day number relative to 0001-01-01. */
|
|
static int64_t day_number(int year, int month, int day)
|
|
{
|
|
int prior_year = year - 1;
|
|
int64_t days = (int64_t)prior_year * 365 + prior_year / 4 -
|
|
prior_year / 100 + prior_year / 400;
|
|
int current_month;
|
|
for (current_month = 1; current_month < month; ++current_month)
|
|
days += days_in_month(year, current_month);
|
|
return days + day - 1;
|
|
}
|
|
|
|
bool trainlog_timestamp_parse(
|
|
const char *value, size_t length, TrainlogTimestampKey *output)
|
|
{
|
|
size_t zone;
|
|
int year, month, day, hour, minute, second = 0;
|
|
int offset_hour = 0, offset_minute = 0, offset_sign = 0;
|
|
int64_t local_second;
|
|
|
|
if (value == NULL || output == NULL || length < 17U ||
|
|
!parse_digits(value, 4U, &year) || value[4] != '-' ||
|
|
!parse_digits(value + 5, 2U, &month) || value[7] != '-' ||
|
|
!parse_digits(value + 8, 2U, &day) ||
|
|
(value[10] != 'T' && value[10] != 't') ||
|
|
!parse_digits(value + 11, 2U, &hour) || value[13] != ':' ||
|
|
!parse_digits(value + 14, 2U, &minute)) return false;
|
|
if (year < 1 || month < 1 || month > 12 || day < 1 ||
|
|
day > days_in_month(year, month) || hour > 23 || minute > 59)
|
|
return false;
|
|
|
|
zone = 16U;
|
|
output->fraction = NULL;
|
|
output->fraction_length = 0U;
|
|
if (zone < length && value[zone] == ':') {
|
|
if (zone + 3U > length || !parse_digits(value + zone + 1U, 2U, &second) || second > 59)
|
|
return false;
|
|
zone += 3U;
|
|
if (zone < length && value[zone] == '.') {
|
|
size_t fraction_start = ++zone;
|
|
while (zone < length && value[zone] >= '0' && value[zone] <= '9') ++zone;
|
|
if (zone == fraction_start) return false;
|
|
output->fraction = value + fraction_start;
|
|
output->fraction_length = zone - fraction_start;
|
|
}
|
|
}
|
|
|
|
if (zone + 1U == length && (value[zone] == 'Z' || value[zone] == 'z')) {
|
|
offset_sign = 0;
|
|
} else {
|
|
if (zone + 6U != length || (value[zone] != '+' && value[zone] != '-') ||
|
|
value[zone + 3U] != ':' ||
|
|
!parse_digits(value + zone + 1U, 2U, &offset_hour) ||
|
|
!parse_digits(value + zone + 4U, 2U, &offset_minute) ||
|
|
offset_hour > 23 || offset_minute > 59) return false;
|
|
offset_sign = value[zone] == '+' ? 1 : -1;
|
|
}
|
|
|
|
output->local_day = day_number(year, month, day);
|
|
local_second = output->local_day * INT64_C(86400) +
|
|
(int64_t)hour * INT64_C(3600) + (int64_t)minute * INT64_C(60) + second;
|
|
output->utc_second = local_second - offset_sign *
|
|
((int64_t)offset_hour * INT64_C(3600) + (int64_t)offset_minute * INT64_C(60));
|
|
return true;
|
|
}
|
|
|
|
int trainlog_timestamp_compare(
|
|
const TrainlogTimestampKey *left, const TrainlogTimestampKey *right)
|
|
{
|
|
size_t index;
|
|
size_t count;
|
|
if (left->utc_second != right->utc_second)
|
|
return left->utc_second < right->utc_second ? -1 : 1;
|
|
count = left->fraction_length > right->fraction_length
|
|
? left->fraction_length : right->fraction_length;
|
|
for (index = 0U; index < count; ++index) {
|
|
char left_digit = index < left->fraction_length ? left->fraction[index] : '0';
|
|
char right_digit = index < right->fraction_length ? right->fraction[index] : '0';
|
|
if (left_digit != right_digit) return left_digit < right_digit ? -1 : 1;
|
|
}
|
|
return 0;
|
|
}
|