0
0
mirror of https://github.com/tursodatabase/libsql.git synced 2025-05-23 05:58:29 +00:00
Files
libsql/libsql-ffi/bundled/sqlean/time/time.c

876 lines
27 KiB
C
Raw Normal View History

// Copyright (c) 2024 Anton Zhiyanov, MIT License
// https://github.com/nalgeon/sqlean
// Based on Go's time package, BSD 3-Clause License
// https://github.com/golang/go
// Time functions and methods.
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "time/timex.h"
#pragma region Private
static const int64_t seconds_per_minute = 60;
static const int64_t seconds_per_hour = 60 * seconds_per_minute;
static const int64_t seconds_per_day = 24 * seconds_per_hour;
static const int64_t seconds_per_week = 7 * seconds_per_day;
static const int64_t days_per_400_years = 365 * 400 + 97;
static const int64_t days_per_100_years = 365 * 100 + 24;
static const int64_t days_per_4_years = 365 * 4 + 1;
// The unsigned zero year for internal calculations.
// Must be 1 mod 400, and times before it will not compute correctly,
// but otherwise can be changed at will.
static const int64_t absolute_zero_year = -292277022399LL;
// Offsets to convert between internal and absolute or Unix times.
// = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay
static const int64_t absolute_to_internal = -9223371966579724800LL;
static const int64_t internal_to_absolute = -absolute_to_internal;
static const int64_t unix_to_internal =
(1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400) * seconds_per_day;
static const int64_t internal_to_unix = -unix_to_internal;
// days_before[m] counts the number of days in a non-leap year
// before month m begins. There is an entry for m=12, counting
// the number of days before January of next year (365).
static const int days_before[] = {
0,
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
};
// norm returns nhi, nlo such that
//
// hi * base + lo == nhi * base + nlo
// 0 <= nlo < base
static void norm(int hi, int lo, int base, int* nhi, int* nlo) {
if (lo < 0) {
int n = (-lo - 1) / base + 1;
hi -= n;
lo += n * base;
}
if (lo >= base) {
int n = lo / base;
hi += n;
lo -= n * base;
}
*nhi = hi;
*nlo = lo;
}
// days_since_epoch takes a year and returns the number of days from
// the absolute epoch to the start of that year.
// This is basically (year - zeroYear) * 365, but accounting for leap days.
static uint64_t days_since_epoch(int year) {
uint64_t y = year - absolute_zero_year;
// Add in days from 400-year cycles.
uint64_t n = y / 400;
y -= 400 * n;
uint64_t d = days_per_400_years * n;
// Add in 100-year cycles.
n = y / 100;
y -= 100 * n;
d += days_per_100_years * n;
// Add in 4-year cycles.
n = y / 4;
y -= 4 * n;
d += days_per_4_years * n;
// Add in non-leap years.
n = y;
d += 365 * n;
return d;
}
// is_leap reports whether the year is a leap year.
static bool is_leap(int year) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
static int64_t unix_sec(Time t) {
return t.sec + internal_to_unix;
}
static Time unix_time(int64_t sec, int32_t nsec) {
return (Time){sec + unix_to_internal, nsec};
}
// abs_time returns the time t as an absolute time, adjusted by the zone offset.
// It is called when computing a presentation property like Month or Hour.
static uint64_t abs_time(Time t) {
return t.sec + internal_to_absolute;
}
// abs_weekday is like Weekday but operates on an absolute time.
static enum Weekday abs_weekday(uint64_t abs) {
// January 1 of the absolute year, like January 1 of 2001, was a Monday.
uint64_t sec = (abs + Monday * seconds_per_day) % seconds_per_week;
return sec / seconds_per_day;
}
static void abs_date(uint64_t abs, int* year, int* yday) {
// Split into time and day.
uint64_t d = abs / seconds_per_day;
// Account for 400 year cycles.
uint64_t n = d / days_per_400_years;
uint64_t y = 400 * n;
d -= days_per_400_years * n;
// Cut off 100-year cycles.
// The last cycle has one extra leap year, so on the last day
// of that year, day / days_per_100_years will be 4 instead of 3.
// Cut it back down to 3 by subtracting n>>2.
n = d / days_per_100_years;
n -= n >> 2;
y += 100 * n;
d -= days_per_100_years * n;
// Cut off 4-year cycles.
// The last cycle has a missing leap year, which does not
// affect the computation.
n = d / days_per_4_years;
y += 4 * n;
d -= days_per_4_years * n;
// Cut off years within a 4-year cycle.
// The last year is a leap year, so on the last day of that year,
// day / 365 will be 4 instead of 3. Cut it back down to 3
// by subtracting n>>2.
n = d / 365;
n -= n >> 2;
y += n;
d -= 365 * n;
*year = y + absolute_zero_year;
*yday = d;
}
static void abs_date_full(uint64_t abs, int* year, enum Month* month, int* day, int* yday) {
abs_date(abs, year, yday);
*day = *yday;
if (is_leap(*year)) {
// Leap year
if (*day > 31 + 29 - 1) {
// After leap day; pretend it wasn't there.
*day -= 1;
}
if (*day == 31 + 29 - 1) {
// Leap day.
*month = February;
*day = 29;
return;
}
}
// Estimate month on assumption that every month has 31 days.
// The estimate may be too low by at most one month, so adjust.
*month = *day / 31;
int end = days_before[(int)(*month) + 1];
int begin;
if (*day >= end) {
*month += 1;
begin = end;
} else {
begin = days_before[(int)(*month)];
}
*month += 1; // because January is 1
*day = *day - begin + 1;
}
void abs_clock(uint64_t abs, int* hour, int* min, int* sec) {
*sec = abs % seconds_per_day;
*hour = *sec / seconds_per_hour;
*sec -= *hour * seconds_per_hour;
*min = *sec / seconds_per_minute;
*sec -= *min * seconds_per_minute;
}
// tless_than_half reports whether x+x < y but avoids overflow,
// assuming x and y are both positive (Duration is signed).
static bool tless_than_half(Duration x, Duration y) {
return (uint64_t)x + (uint64_t)x < (uint64_t)y;
}
// time_div divides t by d and returns the remainder.
// Only supports d which is a multiple of 1 second.
static Duration time_div(Time t, Duration d) {
if (d % Second != 0) {
return 0;
}
bool neg = false;
int64_t sec = t.sec;
int64_t nsec = t.nsec;
if (sec < 0) {
// Operate on absolute value.
neg = true;
sec = -sec;
nsec = -nsec;
if (nsec < 0) {
nsec += 1e9;
sec--; // sec >= 1 before the -- so safe
}
}
// d is a multiple of 1 second.
int64_t d1 = d / Second;
Duration r = (sec % d1) * Second + nsec;
if (neg && r != 0) {
r = d - r;
}
return r;
}
#pragma endregion
#pragma region Constructors
// time_now returns the current time in UTC.
Time time_now(void) {
struct timespec ts;
timespec_get(&ts, TIME_UTC);
return unix_time(ts.tv_sec, ts.tv_nsec);
}
// time_date returns the Time corresponding to
// yyyy-mm-dd hh:mm:ss + nsec nanoseconds
//
// The month, day, hour, min, sec, and nsec values may be outside
// their usual ranges and will be normalized during the conversion.
// For example, October 32 converts to November 1.
//
// The time is converted to UTC using offset_sec in seconds east of UTC.
Time time_date(int year,
enum Month month,
int day,
int hour,
int min,
int sec,
int nsec,
int offset_sec) {
// Normalize month, overflowing into year.
int m = month - 1;
norm(year, m, 12, &year, &m);
month = m + 1;
// Normalize nsec, sec, min, hour, overflowing into day.
norm(sec, nsec, 1000000000, &sec, &nsec);
norm(min, sec, 60, &min, &sec);
norm(hour, min, 60, &hour, &min);
norm(day, hour, 24, &day, &hour);
// Compute days since the absolute epoch.
uint64_t d = days_since_epoch(year);
// Add in days before this month.
d += days_before[month - 1];
if (is_leap(year) && month >= March) {
d++; // February 29
}
// Add in days before today.
d += day - 1;
// Add in time elapsed today.
uint64_t abs = d * seconds_per_day;
abs += hour * seconds_per_hour + min * seconds_per_minute + sec;
// Convert to UTC.
abs -= offset_sec;
return (Time){abs + absolute_to_internal, nsec};
}
#pragma endregion
#pragma region Time parts
// time_get_date returns the year, month, and day in which t occurs.
void time_get_date(Time t, int* year, enum Month* month, int* day) {
uint64_t abs = abs_time(t);
int yday;
abs_date_full(abs, year, month, day, &yday);
}
// time_get_year returns the year in which t occurs.
int time_get_year(Time t) {
uint64_t abs = abs_time(t);
int year, yday;
abs_date(abs, &year, &yday);
return year;
}
// time_get_month returns the month of the year specified by t.
enum Month time_get_month(Time t) {
uint64_t abs = abs_time(t);
int year, day, yday;
enum Month month;
abs_date_full(abs, &year, &month, &day, &yday);
return month;
}
// time_get_day returns the day of the month specified by t.
int time_get_day(Time t) {
uint64_t abs = abs_time(t);
int year, day, yday;
enum Month month;
abs_date_full(abs, &year, &month, &day, &yday);
return day;
}
// time_get_clock returns the hour, minute, and second within the day specified by t.
void time_get_clock(Time t, int* hour, int* min, int* sec) {
uint64_t abs = abs_time(t);
abs_clock(abs, hour, min, sec);
}
// time_get_hour returns the hour within the day specified by t, in the range [0, 23].
int time_get_hour(Time t) {
uint64_t abs = abs_time(t);
return (abs % seconds_per_day) / seconds_per_hour;
}
// time_get_minute returns the minute offset within the hour specified by t, in the range [0, 59].
int time_get_minute(Time t) {
uint64_t abs = abs_time(t);
return (abs % seconds_per_hour) / seconds_per_minute;
}
// time_get_second returns the second offset within the minute specified by t, in the range [0, 59].
int time_get_second(Time t) {
uint64_t abs = abs_time(t);
return abs % seconds_per_minute;
}
// time_get_nano returns the nanosecond offset within the second specified by t,
// in the range [0, 999999999].
int time_get_nano(Time t) {
return t.nsec;
}
// time_get_weekday returns the day of the week specified by t.
enum Weekday time_get_weekday(Time t) {
uint64_t abs = abs_time(t);
return abs_weekday(abs);
}
// time_get_yearday returns the day of the year specified by t, in the range [1,365] for non-leap
// years, and [1,366] in leap years.
int time_get_yearday(Time t) {
uint64_t abs = abs_time(t);
int year, yday;
abs_date(abs, &year, &yday);
return yday + 1;
}
// time_get_isoweek returns the ISO 8601 year and week number in which t occurs.
// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to
// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 of year n+1.
void time_get_isoweek(Time t, int* year, int* week) {
// According to the rule that the first calendar week of a calendar year is
// the week including the first Thursday of that year, and that the last one is
// the week immediately preceding the first calendar week of the next calendar year.
// See https://www.iso.org/obp/ui#iso:std:iso:8601:-1:ed-1:v1:en:term:3.1.1.23 for details.
// weeks start with Monday
// Monday Tuesday Wednesday Thursday Friday Saturday Sunday
// 1 2 3 4 5 6 7
// +3 +2 +1 0 -1 -2 -3
// the offset to Thursday
uint64_t abs = abs_time(t);
int d = (Thursday - abs_weekday(abs));
// handle Sunday
if (d == 4) {
d = -3;
}
// find the Thursday of the calendar week
int yday;
abs += d * seconds_per_day;
abs_date(abs, year, &yday);
*week = yday / 7 + 1;
}
#pragma endregion
#pragma region Unix time
// time_unix returns the Time corresponding to the given Unix time,
// sec seconds and nsec nanoseconds since January 1, 1970 UTC.
// It is valid to pass nsec outside the range [0, 999999999].
// Not all sec values have a corresponding time value. One such
// value is 1<<63-1 (the largest int64 value).
Time time_unix(int64_t sec, int64_t nsec) {
if (nsec < 0 || nsec >= 1000000000) {
int64_t n = nsec / 1000000000;
sec += n;
nsec -= n * 1000000000;
if (nsec < 0) {
nsec += 1000000000;
sec--;
}
}
return unix_time(sec, nsec);
}
// time_milli returns the Time corresponding to the given Unix time,
// msec milliseconds since January 1, 1970 UTC.
Time time_milli(int64_t msec) {
return time_unix(msec / 1000, (msec % 1000) * 1000000);
}
// time_micro returns the Time corresponding to the given Unix time,
// usec microseconds since January 1, 1970 UTC.
Time time_micro(int64_t usec) {
return time_unix(usec / 1000000, (usec % 1000000) * 1000);
}
// time_nano returns the Time corresponding to the given Unix time,
// nsec nanoseconds since January 1, 1970 UTC.
Time time_nano(int64_t nsec) {
return time_unix(0, nsec);
}
// time_to_unix returns t as a Unix time, the number of seconds elapsed
// since January 1, 1970 UTC.
// Unix-like operating systems often record time as a 32-bit
// count of seconds, but since the method here returns a 64-bit
// value it is valid for billions of years into the past or future.
int64_t time_to_unix(Time t) {
return unix_sec(t);
}
// time_to_milli returns t as a Unix time, the number of milliseconds elapsed since
// January 1, 1970 UTC. The result is undefined if the Unix time in
// milliseconds cannot be represented by an int64 (a date more than 292 million
// years before or after 1970).
int64_t time_to_milli(Time t) {
return unix_sec(t) * 1000 + t.nsec / 1000000;
}
// time_to_micro returns t as a Unix time, the number of microseconds elapsed since
// January 1, 1970 UTC. The result is undefined if the Unix time in
// microseconds cannot be represented by an int64 (a date before year -290307 or
// after year 294246).
int64_t time_to_micro(Time t) {
return unix_sec(t) * 1000000 + t.nsec / 1000;
}
// time_to_nano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC. The result is undefined if the Unix time
// in nanoseconds cannot be represented by an int64 (a date before the year
// 1678 or after 2262). Note that this means the result of calling UnixNano
// on the zero Time is undefined.
int64_t time_to_nano(Time t) {
return unix_sec(t) * 1000000000 + t.nsec;
}
#pragma endregion
#pragma region Calendar time
// time_tm returns the Time corresponding to the given calendar time at the given timezone offset.
Time time_tm(struct tm tm, int offset_sec) {
int year = tm.tm_year + 1900;
int month = tm.tm_mon + 1;
int day = tm.tm_mday;
int hour = tm.tm_hour;
int min = tm.tm_min;
int sec = tm.tm_sec;
return time_date(year, month, day, hour, min, sec, 0, offset_sec);
}
// time_to_tm returns t in the given timezone offset as a calendar time.
struct tm time_to_tm(Time t, int offset_sec) {
Time loc_t = time_add(t, offset_sec * Second);
int year, day, hour, min, sec;
enum Month month;
time_get_date(loc_t, &year, &month, &day);
time_get_clock(loc_t, &hour, &min, &sec);
struct tm tm = {
.tm_year = year - 1900,
.tm_mon = month - 1,
.tm_mday = day,
.tm_hour = hour,
.tm_min = min,
.tm_sec = sec,
.tm_isdst = -1,
};
return tm;
}
#pragma endregion
#pragma region Comparison
// time_after reports whether the time instant t is after u.
bool time_after(Time t, Time u) {
return t.sec > u.sec || (t.sec == u.sec && t.nsec > u.nsec);
}
// time_before reports whether the time instant t is before u.
bool time_before(Time t, Time u) {
return t.sec < u.sec || (t.sec == u.sec && t.nsec < u.nsec);
}
// time_compare compares the time instant t with u. If t is before u, it returns -1;
// if t is after u, it returns +1; if they're the same, it returns 0.
int time_compare(Time t, Time u) {
if (time_before(t, u)) {
return -1;
}
if (time_after(t, u)) {
return +1;
}
return 0;
}
// time_equal reports whether t and u represent the same time instant.
bool time_equal(Time t, Time u) {
return t.sec == u.sec && t.nsec == u.nsec;
}
// time_is_zero reports whether t represents the zero time instant,
// January 1, year 1, 00:00:00 UTC.
bool time_is_zero(Time t) {
return t.sec == 0 && t.nsec == 0;
}
#pragma endregion
#pragma region Arithmetic
// time_add returns the time t+d.
Time time_add(Time t, Duration d) {
int64_t dsec = d / Second;
int64_t nsec = t.nsec + d % 1000000000;
if (nsec >= 1e9) {
dsec++;
nsec -= 1e9;
} else if (nsec < 0) {
dsec--;
nsec += 1e9;
}
return (Time){t.sec + dsec, nsec};
}
// time_sub returns the duration t-u. If the result exceeds the maximum (or minimum)
// value that can be stored in a Duration, the maximum (or minimum) duration
// will be returned.
Duration time_sub(Time t, Time u) {
int64_t d = (t.sec - u.sec) * Second + (t.nsec - u.nsec);
if (time_equal(time_add(u, d), t)) {
return d; // d is correct
}
if (time_before(t, u)) {
return MIN_DURATION; // t - u is negative out of range
}
return MAX_DURATION; // t - u is positive out of range
}
// time_since returns the time elapsed since t.
// It is shorthand for time_sub(time_now(), t).
Duration time_since(Time t) {
return time_sub(time_now(), t);
}
// time_until returns the duration until t.
// It is shorthand for time_sub(t, time_now()).
Duration time_until(Time t) {
return time_sub(t, time_now());
}
// time_add_date returns the time corresponding to adding the
// given number of years, months, and days to t.
// For example, time_add_date(-1, 2, 3) applied to January 1, 2011
// returns March 4, 2010.
//
// time_add_date normalizes its result in the same way that Date does,
// so, for example, adding one month to October 31 yields
// December 1, the normalized form for November 31.
Time time_add_date(Time t, int years, int months, int days) {
int year, day;
enum Month month;
time_get_date(t, &year, &month, &day);
int hour, min, sec;
time_get_clock(t, &hour, &min, &sec);
return time_date(year + years, month + months, day + days, hour, min, sec, t.nsec, TIMEX_UTC);
}
#pragma endregion
#pragma region Rounding
// time_truncate returns the result of rounding t down to a multiple of d (since the zero time).
// Only supports d which is a multiple of 1 second. If d <= 0, returns t unchanged.
Time time_truncate(Time t, Duration d) {
if (d <= 0) {
return t;
}
Duration r = time_div(t, d);
return time_add(t, -r);
}
// time_round returns the result of rounding t to the nearest multiple of d (since the zero time).
// The rounding behavior for halfway values is to round up.
// If d <= 0, returns t unchanged.
Time time_round(Time t, Duration d) {
if (d <= 0) {
return t;
}
Duration r = time_div(t, d);
if (tless_than_half(r, d)) {
return time_add(t, -r);
}
return time_add(t, d - r);
}
#pragma endregion
#pragma region Formatting
// time_fmt_iso returns an ISO 8601 time string for the given time value.
// Converts the time value to the given timezone offset before formatting.
// Chooses the most compact representation:
// - 2006-01-02T15:04:05.999999999+07:00
// - 2006-01-02T15:04:05.999999999Z
// - 2006-01-02T15:04:05+07:00
// - 2006-01-02T15:04:05Z
size_t time_fmt_iso(char* buf, size_t size, Time t, int offset_sec) {
int year, day, hour, min, sec;
enum Month month;
const char* layout;
size_t n = 0;
if (offset_sec == 0) {
time_get_date(t, &year, &month, &day);
time_get_clock(t, &hour, &min, &sec);
if (t.nsec == 0) {
layout = "%04d-%02d-%02dT%02d:%02d:%02dZ";
n = snprintf(buf, size, layout, year, month, day, hour, min, sec);
} else {
layout = "%04d-%02d-%02dT%02d:%02d:%02d.%09dZ";
n = snprintf(buf, size, layout, year, month, day, hour, min, sec, t.nsec);
}
} else {
Time loc_t = time_add(t, offset_sec * Second);
time_get_date(loc_t, &year, &month, &day);
time_get_clock(loc_t, &hour, &min, &sec);
int ofhour = offset_sec / 3600;
int ofmin = (offset_sec % 3600) / 60;
if (ofmin < 0) {
ofmin = -ofmin;
}
if (loc_t.nsec == 0) {
layout = "%04d-%02d-%02dT%02d:%02d:%02d%+03d:%02d";
n = snprintf(buf, size, layout, year, month, day, hour, min, sec, ofhour, ofmin);
} else {
layout = "%04d-%02d-%02dT%02d:%02d:%02d.%09d%+03d:%02d";
n = snprintf(buf, size, layout, year, month, day, hour, min, sec, loc_t.nsec, ofhour,
ofmin);
}
}
return n;
}
// time_fmt_datetime returns a datetime string
// (2006-01-02 15:04:05) for the given time value.
// Converts the time value to the given timezone offset before formatting.
size_t time_fmt_datetime(char* buf, size_t size, Time t, int offset_sec) {
int year, day, hour, min, sec;
enum Month month;
if (offset_sec == 0) {
time_get_date(t, &year, &month, &day);
time_get_clock(t, &hour, &min, &sec);
} else {
Time loc_t = time_add(t, offset_sec * Second);
time_get_date(loc_t, &year, &month, &day);
time_get_clock(loc_t, &hour, &min, &sec);
}
return snprintf(buf, size, "%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec);
}
// time_fmt_date returns a date string
// (2006-01-02) for the given time value.
// Converts the time value to the given timezone offset before formatting.
size_t time_fmt_date(char* buf, size_t size, Time t, int offset_sec) {
int year, day;
enum Month month;
if (offset_sec == 0) {
time_get_date(t, &year, &month, &day);
} else {
Time loc_t = time_add(t, offset_sec * Second);
time_get_date(loc_t, &year, &month, &day);
}
return snprintf(buf, size, "%04d-%02d-%02d", year, month, day);
}
// time_fmt_time returns a time string
// (15:04:05) for the given time value.
// Converts the time value to the given timezone offset before formatting.
size_t time_fmt_time(char* buf, size_t size, Time t, int offset_sec) {
int hour, min, sec;
if (offset_sec == 0) {
time_get_clock(t, &hour, &min, &sec);
} else {
Time loc_t = time_add(t, offset_sec * Second);
time_get_clock(loc_t, &hour, &min, &sec);
}
return snprintf(buf, size, "%02d:%02d:%02d", hour, min, sec);
}
// time_parse parses a formatted string and returns the time value it represents.
// Supports a limited set of layouts:
// - "2006-01-02T15:04:05.999999999+07:00" (ISO 8601 with nanoseconds and timezone)
// - "2006-01-02T15:04:05.999999999Z" (ISO 8601 with nanoseconds, UTC)
// - "2006-01-02T15:04:05+07:00" (ISO 8601 with timezone)
// - "2006-01-02T15:04:05Z" (ISO 8601, UTC)
// - "2006-01-02 15:04:05" (date and time, UTC)
// - "2006-01-02" (date only, UTC)
// - "15:04:05" (time only, UTC)
Time time_parse(const char* value) {
Time zero = {0, 0};
size_t len = strlen(value);
if (len < 8 || len > 35) {
return zero;
}
int year = 1, month = 1, day = 1, hour = 0, min = 0, sec = 0, nsec = 0, offset_sec = TIMEX_UTC;
char tz[7] = "";
if (len == 35) {
// "2006-01-02T15:04:05.999999999+07:00"
int n = sscanf(value, "%d-%d-%dT%d:%d:%d.%d%6s", &year, &month, &day, &hour, &min, &sec,
&nsec, tz);
if (n != 8) {
return zero;
}
}
if (len == 30) {
// "2006-01-02T15:04:05.999999999Z"
int n =
sscanf(value, "%d-%d-%dT%d:%d:%d.%dZ", &year, &month, &day, &hour, &min, &sec, &nsec);
if (n != 7) {
return zero;
}
}
if (len == 25) {
// "2006-01-02T15:04:05+07:00"
int n = sscanf(value, "%d-%d-%dT%d:%d:%d%6s", &year, &month, &day, &hour, &min, &sec, tz);
if (n != 7) {
return zero;
}
}
if (len == 19 || len == 20) {
// "2006-01-02T15:04:05Z"
// "2006-01-02 15:04:05"
int n = sscanf(value, "%d-%d-%d%*c%d:%d:%d", &year, &month, &day, &hour, &min, &sec);
if (n != 6) {
return zero;
}
}
if (len == 10) {
// "2006-01-02"
int n = sscanf(value, "%d-%d-%d", &year, &month, &day);
if (n != 3) {
return zero;
}
}
if (len == 8) {
// "15:04:05"
int n = sscanf(value, "%d:%d:%d", &hour, &min, &sec);
if (n != 3) {
return zero;
}
}
if (tz[0] != '\0') {
// Parse timezone offset.
// + 0 7 : 0 0
// ⁰ ¹ ² ³ ⁴ ⁵
// tz[0] is the sign.
int sign = (tz[0] == '-') ? -1 : 1;
// tz[1] and tz[2] are hours.
offset_sec = ((tz[1] - '0') * 10 + (tz[2] - '0')) * 3600 * sign;
// tz[4] and tz[5] are minutes.
offset_sec += ((tz[4] - '0') * 10 + (tz[5] - '0')) * 60 * sign;
}
return time_date(year, (enum Month)month, day, hour, min, sec, nsec, offset_sec);
}
#pragma endregion
#pragma region Marshaling
// time_blob returns the time instant represented by the binary data.
// The blob must have been created by time_to_blob and be at least 13 bytes long.
Time time_blob(const uint8_t* buf) {
const uint8_t version = buf[0];
if (version != 1) {
return (Time){0, 0};
}
int64_t sec = (int64_t)buf[8] | (int64_t)buf[7] << 8 | (int64_t)buf[6] << 16 |
(int64_t)buf[5] << 24 | (int64_t)buf[4] << 32 | (int64_t)buf[3] << 40 |
(int64_t)buf[2] << 48 | (int64_t)buf[1] << 56;
int32_t nsec =
(int32_t)buf[12] | (int32_t)buf[11] << 8 | (int32_t)buf[10] << 16 | (int32_t)buf[9] << 24;
return (Time){sec, nsec};
}
// time_to_blob returns the binary representation of the time instant t.
// The result is a byte slice with the following layout:
// 0: version (currently 1)
// 1-8: seconds
// 9-12: nanoseconds
void time_to_blob(Time t, uint8_t* buf) {
const uint8_t version = 1;
buf[0] = version;
buf[1] = t.sec >> 56; // bytes 1-8: seconds
buf[2] = t.sec >> 48;
buf[3] = t.sec >> 40;
buf[4] = t.sec >> 32;
buf[5] = t.sec >> 24;
buf[6] = t.sec >> 16;
buf[7] = t.sec >> 8;
buf[8] = t.sec;
buf[9] = t.nsec >> 24; // bytes 9-12: nanoseconds
buf[10] = t.nsec >> 16;
buf[11] = t.nsec >> 8;
buf[12] = t.nsec;
}
#pragma endregion