#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include "time64.h"
#include "time64_limits.h"
static const char days_in_month[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};
static const short julian_days_by_month[2][12] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
};
static const char wday_name[7][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char mon_name[12][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const short length_of_year[2] = { 365, 366 };
static const Year years_in_gregorian_cycle = 400;
#define days_in_gregorian_cycle ((365 * 400) + 100 - 4 + 1)
static const Time64_T seconds_in_gregorian_cycle = days_in_gregorian_cycle * 60LL * 60LL * 24LL;
#define MAX_SAFE_YEAR 2037
#define MIN_SAFE_YEAR 1971
#define SOLAR_CYCLE_LENGTH 28
static const short safe_years_high[SOLAR_CYCLE_LENGTH] = {
2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023,
2024, 2025, 2026, 2027,
2028, 2029, 2030, 2031,
2032, 2033, 2034, 2035,
2036, 2037, 2010, 2011,
2012, 2013, 2014, 2015
};
static const int safe_years_low[SOLAR_CYCLE_LENGTH] = {
1996, 1997, 1998, 1971,
1972, 1973, 1974, 1975,
1976, 1977, 1978, 1979,
1980, 1981, 1982, 1983,
1984, 1985, 1986, 1987,
1988, 1989, 1990, 1991,
1992, 1993, 1994, 1995,
};
#if 0#endif
#define CHEAT_DAYS (1199145600 / 24 / 60 / 60)
#define CHEAT_YEARS 108
#define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))
#ifdef USE_SYSTEM_LOCALTIME
# define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \
(a) <= SYSTEM_LOCALTIME_MAX && \
(a) >= SYSTEM_LOCALTIME_MIN \
)
#else
# define SHOULD_USE_SYSTEM_LOCALTIME(a) (0)
#endif
#ifdef USE_SYSTEM_GMTIME
# define SHOULD_USE_SYSTEM_GMTIME(a) ( \
(a) <= SYSTEM_GMTIME_MAX && \
(a) >= SYSTEM_GMTIME_MIN \
)
#else
# define SHOULD_USE_SYSTEM_GMTIME(a) (0)
#endif
#ifdef TIME_64_DEBUG
# define TIME64_TRACE(format) (fprintf(stderr, format))
# define TIME64_TRACE1(format, var1) (fprintf(stderr, format, var1))
# define TIME64_TRACE2(format, var1, var2) (fprintf(stderr, format, var1, var2))
# define TIME64_TRACE3(format, var1, var2, var3) (fprintf(stderr, format, var1, var2, var3))
#else
# define TIME64_TRACE(format) ((void)0)
# define TIME64_TRACE1(format, var1) ((void)0)
# define TIME64_TRACE2(format, var1, var2) ((void)0)
# define TIME64_TRACE3(format, var1, var2, var3) ((void)0)
#endif
static int is_exception_century(Year year)
{
int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
TIME64_TRACE1("# is_exception_century: %s\n", is_exception ? "yes" : "no");
return(is_exception);
}
static int cmp_date( const struct TM* left, const struct tm* right ) {
if( left->tm_year > right->tm_year )
return 1;
if( left->tm_year < right->tm_year )
return -1;
if( left->tm_mon > right->tm_mon )
return 1;
if( left->tm_mon < right->tm_mon )
return -1;
if( left->tm_mday > right->tm_mday )
return 1;
if( left->tm_mday < right->tm_mday )
return -1;
if( left->tm_hour > right->tm_hour )
return 1;
if( left->tm_hour < right->tm_hour )
return -1;
if( left->tm_min > right->tm_min )
return 1;
if( left->tm_min < right->tm_min )
return -1;
if( left->tm_sec > right->tm_sec )
return 1;
if( left->tm_sec < right->tm_sec )
return -1;
return 0;
}
static int date_in_safe_range( const struct TM* date, const struct tm* min, const struct tm* max ) {
if( cmp_date(date, min) == -1 )
return 0;
if( cmp_date(date, max) == 1 )
return 0;
return 1;
}
Time64_T timegm64(const struct TM *date) {
Time64_T days = 0;
Time64_T seconds = 0;
Year year;
assert(date != NULL);
Year orig_year = (Year)date->tm_year;
int cycles = 0;
if( (orig_year > 100) || (orig_year < -300) ) {
cycles = (orig_year - 100) / 400;
orig_year -= cycles * 400;
days += (Time64_T)cycles * days_in_gregorian_cycle;
}
TIME64_TRACE3("# timegm/ cycles: %d, days: %lld, orig_year: %lld\n", cycles, days, orig_year);
if( orig_year > 70 ) {
year = 70;
while( year < orig_year ) {
days += length_of_year[IS_LEAP(year)];
year++;
}
}
else if ( orig_year < 70 ) {
year = 69;
do {
days -= length_of_year[IS_LEAP(year)];
year--;
} while( year >= orig_year );
}
days += julian_days_by_month[IS_LEAP(orig_year)][date->tm_mon];
days += date->tm_mday - 1;
seconds = days * 60 * 60 * 24;
seconds += date->tm_hour * 60 * 60;
seconds += date->tm_min * 60;
seconds += date->tm_sec;
return(seconds);
}
static int check_tm(struct TM *tm)
{
assert(tm->tm_sec >= 0);
assert(tm->tm_sec <= 61);
assert(tm->tm_min >= 0);
assert(tm->tm_min <= 59);
assert(tm->tm_hour >= 0);
assert(tm->tm_hour <= 23);
assert(tm->tm_mday >= 1);
assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
assert(tm->tm_mon >= 0);
assert(tm->tm_mon <= 11);
assert(tm->tm_wday >= 0);
assert(tm->tm_wday <= 6);
assert(tm->tm_yday >= 0);
assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
#ifdef HAVE_TM_TM_GMTOFF
assert(tm->tm_gmtoff >= -24 * 60 * 60);
assert(tm->tm_gmtoff <= 24 * 60 * 60);
#endif
return 1;
}
static Year cycle_offset(Year year)
{
const Year start_year = 2000;
Year year_diff = year - start_year;
Year exceptions;
if( year > start_year )
year_diff--;
exceptions = year_diff / 100;
exceptions -= year_diff / 400;
TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n",
year, exceptions, year_diff);
return exceptions * 16;
}
static int safe_year(const Year year)
{
int _safe_year = (int)year;
Year year_cycle;
if( year >= MIN_SAFE_YEAR && year <= MAX_SAFE_YEAR ) {
return _safe_year;
}
year_cycle = year + cycle_offset(year);
if( year < MIN_SAFE_YEAR )
year_cycle -= 8;
if( is_exception_century(year) )
year_cycle += 11;
if( is_exception_century(year - 1) )
year_cycle += 17;
year_cycle %= SOLAR_CYCLE_LENGTH;
if( year_cycle < 0 )
year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
assert( year_cycle >= 0 );
assert( year_cycle < SOLAR_CYCLE_LENGTH );
if( year < MIN_SAFE_YEAR )
_safe_year = safe_years_low[year_cycle];
else if( year > MAX_SAFE_YEAR )
_safe_year = safe_years_high[year_cycle];
else
assert(0);
TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n",
year, year_cycle, _safe_year);
assert(_safe_year <= MAX_SAFE_YEAR && _safe_year >= MIN_SAFE_YEAR);
return _safe_year;
}
void copy_tm_to_TM64(const struct tm *src, struct TM *dest) {
if( src == NULL ) {
memset(dest, 0, sizeof(*dest));
}
else {
# ifdef USE_TM64
dest->tm_sec = src->tm_sec;
dest->tm_min = src->tm_min;
dest->tm_hour = src->tm_hour;
dest->tm_mday = src->tm_mday;
dest->tm_mon = src->tm_mon;
dest->tm_year = (Year)src->tm_year;
dest->tm_wday = src->tm_wday;
dest->tm_yday = src->tm_yday;
dest->tm_isdst = src->tm_isdst;
# ifdef HAVE_TM_TM_GMTOFF
dest->tm_gmtoff = src->tm_gmtoff;
# endif
# ifdef HAVE_TM_TM_ZONE
dest->tm_zone = src->tm_zone;
# endif
# else
memcpy(dest, src, sizeof(*dest));
# endif
}
}
void copy_TM64_to_tm(const struct TM *src, struct tm *dest) {
if( src == NULL ) {
memset(dest, 0, sizeof(*dest));
}
else {
# ifdef USE_TM64
dest->tm_sec = src->tm_sec;
dest->tm_min = src->tm_min;
dest->tm_hour = src->tm_hour;
dest->tm_mday = src->tm_mday;
dest->tm_mon = src->tm_mon;
dest->tm_year = (int)src->tm_year;
dest->tm_wday = src->tm_wday;
dest->tm_yday = src->tm_yday;
dest->tm_isdst = src->tm_isdst;
# ifdef HAVE_TM_TM_GMTOFF
dest->tm_gmtoff = src->tm_gmtoff;
# endif
# ifdef HAVE_TM_TM_ZONE
dest->tm_zone = src->tm_zone;
# endif
# else
memcpy(dest, src, sizeof(*dest));
# endif
}
}
#if !defined(HAVE_LOCALTIME_R) && !defined(_WIN32)
static struct tm * fake_localtime_r(const time_t *time, struct tm *result) {
const struct tm *static_result = localtime(time);
assert(result != NULL);
if( static_result == NULL ) {
memset(result, 0, sizeof(*result));
return NULL;
}
else {
memcpy(result, static_result, sizeof(*result));
return result;
}
}
#endif
#if !defined(HAVE_GMTIME_R) && !defined(_WIN32)
static struct tm * fake_gmtime_r(const time_t *time, struct tm *result) {
const struct tm *static_result = gmtime(time);
assert(result != NULL);
if( static_result == NULL ) {
memset(result, 0, sizeof(*result));
return NULL;
}
else {
memcpy(result, static_result, sizeof(*result));
return result;
}
}
#endif
static Time64_T seconds_between_years(Year left_year, Year right_year) {
int increment = (left_year > right_year) ? 1 : -1;
Time64_T seconds = 0;
int cycles;
if( left_year > 2400 ) {
cycles = (left_year - 2400) / 400;
left_year -= cycles * 400;
seconds += cycles * seconds_in_gregorian_cycle;
}
else if( left_year < 1600 ) {
cycles = (left_year - 1600) / 400;
left_year += cycles * 400;
seconds += cycles * seconds_in_gregorian_cycle;
}
while( left_year != right_year ) {
seconds += length_of_year[IS_LEAP(right_year - 1900)] * 60 * 60 * 24;
right_year += increment;
}
return seconds * increment;
}
Time64_T mktime64(struct TM *input_date) {
struct tm safe_date;
struct TM date;
Time64_T timev;
Year year = input_date->tm_year + 1900;
if( date_in_safe_range(input_date, &SYSTEM_MKTIME_MIN, &SYSTEM_MKTIME_MAX) )
{
copy_TM64_to_tm(input_date, &safe_date);
timev = (Time64_T)mktime(&safe_date);
copy_tm_to_TM64(&safe_date, input_date);
return timev;
}
date = *input_date;
date.tm_year = safe_year(year) - 1900;
copy_TM64_to_tm(&date, &safe_date);
timev = (Time64_T)mktime(&safe_date);
copy_tm_to_TM64(&safe_date, input_date);
timev += seconds_between_years(year, (Year)(safe_date.tm_year) + 1900);
return timev;
}
Time64_T timelocal64(struct TM *date) {
return mktime64(date);
}
struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
{
int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
Time64_T v_tm_tday;
int leap;
Time64_T m;
Time64_T timev = *in_time;
Year year = 70;
int cycles = 0;
assert(p != NULL);
if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
time_t safe_time = (time_t)*in_time;
struct tm safe_date;
GMTIME_R(&safe_time, &safe_date);
copy_tm_to_TM64(&safe_date, p);
assert(check_tm(p));
return p;
}
#ifdef HAVE_TM_TM_GMTOFF
p->tm_gmtoff = 0;
#endif
p->tm_isdst = 0;
#ifdef HAVE_TM_TM_ZONE
p->tm_zone = (char*)"UTC";
#endif
v_tm_sec = (int)(timev % 60);
timev /= 60;
v_tm_min = (int)(timev % 60);
timev /= 60;
v_tm_hour = (int)(timev % 24);
timev /= 24;
v_tm_tday = timev;
WRAP (v_tm_sec, v_tm_min, 60);
WRAP (v_tm_min, v_tm_hour, 60);
WRAP (v_tm_hour, v_tm_tday, 24);
v_tm_wday = (int)((v_tm_tday + 4) % 7);
if (v_tm_wday < 0)
v_tm_wday += 7;
m = v_tm_tday;
if (m >= CHEAT_DAYS) {
year = CHEAT_YEARS;
m -= CHEAT_DAYS;
}
if (m >= 0) {
cycles = (int)(m / (Time64_T) days_in_gregorian_cycle);
if( cycles ) {
m -= (cycles * (Time64_T) days_in_gregorian_cycle);
year += (cycles * years_in_gregorian_cycle);
}
leap = IS_LEAP (year);
while (m >= (Time64_T) length_of_year[leap]) {
m -= (Time64_T) length_of_year[leap];
year++;
leap = IS_LEAP (year);
}
v_tm_mon = 0;
while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
m -= (Time64_T) days_in_month[leap][v_tm_mon];
v_tm_mon++;
}
} else {
year--;
cycles = (int)((m / (Time64_T) days_in_gregorian_cycle) + 1);
if( cycles ) {
m -= (cycles * (Time64_T) days_in_gregorian_cycle);
year += (cycles * years_in_gregorian_cycle);
}
leap = IS_LEAP (year);
while (m < (Time64_T) -length_of_year[leap]) {
m += (Time64_T) length_of_year[leap];
year--;
leap = IS_LEAP (year);
}
v_tm_mon = 11;
while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
m += (Time64_T) days_in_month[leap][v_tm_mon];
v_tm_mon--;
}
m += (Time64_T) days_in_month[leap][v_tm_mon];
}
p->tm_year = year;
if( p->tm_year != year ) {
#ifdef EOVERFLOW
errno = EOVERFLOW;
#endif
return NULL;
}
p->tm_mday = (int) m + 1;
p->tm_yday = julian_days_by_month[leap][v_tm_mon] + (int)m;
p->tm_sec = v_tm_sec;
p->tm_min = v_tm_min;
p->tm_hour = v_tm_hour;
p->tm_mon = v_tm_mon;
p->tm_wday = v_tm_wday;
assert(check_tm(p));
return p;
}
struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
{
time_t safe_time;
struct tm safe_date;
struct TM gm_tm;
Year orig_year;
int month_diff;
assert(local_tm != NULL);
if( SHOULD_USE_SYSTEM_LOCALTIME(*timev) ) {
safe_time = (time_t)*timev;
TIME64_TRACE1("Using system localtime for %lld\n", *timev);
LOCALTIME_R(&safe_time, &safe_date);
copy_tm_to_TM64(&safe_date, local_tm);
assert(check_tm(local_tm));
return local_tm;
}
if( gmtime64_r(timev, &gm_tm) == NULL ) {
TIME64_TRACE1("gmtime64_r returned null for %lld\n", *timev);
return NULL;
}
orig_year = gm_tm.tm_year;
if (gm_tm.tm_year > (2037 - 1900) ||
gm_tm.tm_year < (1970 - 1900)
)
{
TIME64_TRACE1("Mapping tm_year %lld to safe_year\n", (Year)gm_tm.tm_year);
gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year) + 1900) - 1900;
}
safe_time = (time_t)timegm64(&gm_tm);
if( LOCALTIME_R(&safe_time, &safe_date) == NULL ) {
TIME64_TRACE1("localtime_r(%d) returned NULL\n", (int)safe_time);
return NULL;
}
copy_tm_to_TM64(&safe_date, local_tm);
local_tm->tm_year = orig_year;
if( local_tm->tm_year != orig_year ) {
TIME64_TRACE2("tm_year overflow: tm_year %lld, orig_year %lld\n",
(Year)local_tm->tm_year, (Year)orig_year);
#ifdef EOVERFLOW
errno = EOVERFLOW;
#endif
return NULL;
}
month_diff = local_tm->tm_mon - gm_tm.tm_mon;
if( month_diff == 11 ) {
local_tm->tm_year--;
}
if( month_diff == -11 ) {
local_tm->tm_year++;
}
if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
local_tm->tm_yday--;
assert(check_tm(local_tm));
return local_tm;
}
static int valid_tm_wday( const struct TM* date ) {
if( 0 <= date->tm_wday && date->tm_wday <= 6 )
return 1;
return 0;
}
static int valid_tm_mon( const struct TM* date ) {
if( 0 <= date->tm_mon && date->tm_mon <= 11 )
return 1;
return 0;
}
char *asctime64_r( const struct TM* date, char *result ) {
if( !valid_tm_wday(date) || !valid_tm_mon(date) )
return NULL;
sprintf(result, TM64_ASCTIME_FORMAT,
wday_name[date->tm_wday],
mon_name[date->tm_mon],
date->tm_mday, date->tm_hour,
date->tm_min, date->tm_sec,
1900 + date->tm_year);
return result;
}
char *ctime64_r( const Time64_T* timev, char* result ) {
struct TM date;
if (!localtime64_r( timev, &date ))
return NULL;
return asctime64_r( &date, result );
}