#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;
static const int64_t absolute_zero_year = -292277022399LL;
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;
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,
};
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;
}
static uint64_t days_since_epoch(int year) {
uint64_t y = year - absolute_zero_year;
uint64_t n = y / 400;
y -= 400 * n;
uint64_t d = days_per_400_years * n;
n = y / 100;
y -= 100 * n;
d += days_per_100_years * n;
n = y / 4;
y -= 4 * n;
d += days_per_4_years * n;
n = y;
d += 365 * n;
return d;
}
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};
}
static uint64_t abs_time(Time t) {
return t.sec + internal_to_absolute;
}
static enum Weekday abs_weekday(uint64_t abs) {
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) {
uint64_t d = abs / seconds_per_day;
uint64_t n = d / days_per_400_years;
uint64_t y = 400 * n;
d -= days_per_400_years * n;
n = d / days_per_100_years;
n -= n >> 2;
y += 100 * n;
d -= days_per_100_years * n;
n = d / days_per_4_years;
y += 4 * n;
d -= days_per_4_years * n;
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)) {
if (*day > 31 + 29 - 1) {
*day -= 1;
}
if (*day == 31 + 29 - 1) {
*month = February;
*day = 29;
return;
}
}
*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; *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;
}
static bool tless_than_half(Duration x, Duration y) {
return (uint64_t)x + (uint64_t)x < (uint64_t)y;
}
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) {
neg = true;
sec = -sec;
nsec = -nsec;
if (nsec < 0) {
nsec += 1e9;
sec--; }
}
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 time_now(void) {
struct timespec ts;
timespec_get(&ts, TIME_UTC);
return unix_time(ts.tv_sec, ts.tv_nsec);
}
Time time_date(int year,
enum Month month,
int day,
int hour,
int min,
int sec,
int nsec,
int offset_sec) {
int m = month - 1;
norm(year, m, 12, &year, &m);
month = m + 1;
norm(sec, nsec, 1000000000, &sec, &nsec);
norm(min, sec, 60, &min, &sec);
norm(hour, min, 60, &hour, &min);
norm(day, hour, 24, &day, &hour);
uint64_t d = days_since_epoch(year);
d += days_before[month - 1];
if (is_leap(year) && month >= March) {
d++; }
d += day - 1;
uint64_t abs = d * seconds_per_day;
abs += hour * seconds_per_hour + min * seconds_per_minute + sec;
abs -= offset_sec;
return (Time){abs + absolute_to_internal, nsec};
}
#pragma endregion
#pragma region Time parts
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);
}
int time_get_year(Time t) {
uint64_t abs = abs_time(t);
int year, yday;
abs_date(abs, &year, &yday);
return year;
}
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;
}
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;
}
void time_get_clock(Time t, int* hour, int* min, int* sec) {
uint64_t abs = abs_time(t);
abs_clock(abs, hour, min, sec);
}
int time_get_hour(Time t) {
uint64_t abs = abs_time(t);
return (abs % seconds_per_day) / seconds_per_hour;
}
int time_get_minute(Time t) {
uint64_t abs = abs_time(t);
return (abs % seconds_per_hour) / seconds_per_minute;
}
int time_get_second(Time t) {
uint64_t abs = abs_time(t);
return abs % seconds_per_minute;
}
int time_get_nano(Time t) {
return t.nsec;
}
enum Weekday time_get_weekday(Time t) {
uint64_t abs = abs_time(t);
return abs_weekday(abs);
}
int time_get_yearday(Time t) {
uint64_t abs = abs_time(t);
int year, yday;
abs_date(abs, &year, &yday);
return yday + 1;
}
void time_get_isoweek(Time t, int* year, int* week) {
uint64_t abs = abs_time(t);
int d = (Thursday - abs_weekday(abs));
if (d == 4) {
d = -3;
}
int yday;
abs += d * seconds_per_day;
abs_date(abs, year, &yday);
*week = yday / 7 + 1;
}
#pragma endregion
#pragma region Unix time
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 time_milli(int64_t msec) {
return time_unix(msec / 1000, (msec % 1000) * 1000000);
}
Time time_micro(int64_t usec) {
return time_unix(usec / 1000000, (usec % 1000000) * 1000);
}
Time time_nano(int64_t nsec) {
return time_unix(0, nsec);
}
int64_t time_to_unix(Time t) {
return unix_sec(t);
}
int64_t time_to_milli(Time t) {
return unix_sec(t) * 1000 + t.nsec / 1000000;
}
int64_t time_to_micro(Time t) {
return unix_sec(t) * 1000000 + t.nsec / 1000;
}
int64_t time_to_nano(Time t) {
return unix_sec(t) * 1000000000 + t.nsec;
}
#pragma endregion
#pragma region Calendar time
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);
}
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
bool time_after(Time t, Time u) {
return t.sec > u.sec || (t.sec == u.sec && t.nsec > u.nsec);
}
bool time_before(Time t, Time u) {
return t.sec < u.sec || (t.sec == u.sec && t.nsec < u.nsec);
}
int time_compare(Time t, Time u) {
if (time_before(t, u)) {
return -1;
}
if (time_after(t, u)) {
return +1;
}
return 0;
}
bool time_equal(Time t, Time u) {
return t.sec == u.sec && t.nsec == u.nsec;
}
bool time_is_zero(Time t) {
return t.sec == 0 && t.nsec == 0;
}
#pragma endregion
#pragma region Arithmetic
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};
}
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; }
if (time_before(t, u)) {
return MIN_DURATION; }
return MAX_DURATION; }
Duration time_since(Time t) {
return time_sub(time_now(), t);
}
Duration time_until(Time t) {
return time_sub(t, time_now());
}
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 time_truncate(Time t, Duration d) {
if (d <= 0) {
return t;
}
Duration r = time_div(t, d);
return time_add(t, -r);
}
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
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;
}
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);
}
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);
}
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 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) {
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) {
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) {
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) {
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) {
int n = sscanf(value, "%d-%d-%d", &year, &month, &day);
if (n != 3) {
return zero;
}
}
if (len == 8) {
int n = sscanf(value, "%d:%d:%d", &hour, &min, &sec);
if (n != 3) {
return zero;
}
}
if (tz[0] != '\0') {
int sign = (tz[0] == '-') ? -1 : 1;
offset_sec = ((tz[1] - '0') * 10 + (tz[2] - '0')) * 3600 * sign;
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 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};
}
void time_to_blob(Time t, uint8_t* buf) {
const uint8_t version = 1;
buf[0] = version;
buf[1] = t.sec >> 56; 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; buf[10] = t.nsec >> 16;
buf[11] = t.nsec >> 8;
buf[12] = t.nsec;
}
#pragma endregion