mirror of
https://github.com/tursodatabase/libsql.git
synced 2025-03-08 23:41:50 +00:00
A common complain with libSQL is how to run extensions. The main mechanism, with a .so, has a lot of issues around how those .so are distributed. The most common extensions are the ones in the sqlean package. We can improve this experience by bundling them in our sqlite build. Not all SQLean extensions are kosher: some of them, like fileio, use the vfs. Others, are deemed too complex. The extensions included here are a subset that we deem important enough, and low risk enough, to just be a part of the main bundle.
876 lines
27 KiB
C
876 lines
27 KiB
C
// 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
|