/********************************************************************************
* *
* T i m e S t u f f *
* *
*********************************************************************************
* Copyright (C) 2019,2021 by Jeroen van der Zijp. All Rights Reserved. *
*********************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see *
********************************************************************************/
#include "xincs.h"
#include "fxver.h"
#include "fxdefs.h"
#include "fxmath.h"
#include "fxascii.h"
#include "FXAtomic.h"
#include "FXElement.h"
#include "FXString.h"
#include "FXSystem.h"
/*
Notes:
- Handy functions for manipulating calendar time.
*/
using namespace FX;
/*******************************************************************************/
namespace FX {
// Time units in terms of nanoseconds
static const FXTime seconds=1000000000;
static const FXTime minutes=60*seconds;
static const FXTime hours=60*minutes;
static const FXTime days=24*hours;
// Default formatting string used for time formatting
const FXchar FXSystem::defaultTimeFormat[]="%m/%d/%Y %H:%M:%S";
// ISO 8601 time format (yyyy-mm-ddThh:mm:ss+hhmm) formatting string
const FXchar FXSystem::isoTimeFormat[]="%FT%T%z";
// Cumulative days of the year, for non-leap-years and leap-years
static const FXint days_of_year[2][13]={
{0,31,59,90,120,151,181,212,243,273,304,334,365},
{0,31,60,91,121,152,182,213,244,274,305,335,366}
};
// Is year a leap-year
static FXint is_leap(FXint year){
return !(year%4) && ((year%100) || !(year%400));
}
// Returns day of week in civil calendar [0, 6] -> [Sun, Sat],
// from z is number of days since 1970-01-01.
static FXuint weekday_from_days(FXlong z){
return (FXuint)(z>=-4 ? (z+4)%7 : (z+5)%7+6);
}
// From year, month (1..12), day (1..31) return year-day (1..366).
static FXint yearday_from_date(FXint y,FXint m,FXint d){
return days_of_year[is_leap(y)][m-1]+d;
}
/*******************************************************************************/
// Returns number of days since civil 1970-01-01. Negative values indicate
// days prior to 1970-01-01.
// y is year, m is month of year (1..12), d is day of month (1..31).
FXlong FXSystem::daysFromCivil(FXint y,FXint m,FXint d){
y-=(m<=2); // March
FXlong era=(y>=0?y:y-399)/400; // Era
FXuint yoe=(FXuint)(y-era*400); // [0, 399]
FXuint doy=(153*(m+(m>2?-3:9))+2)/5+d-1; // [0, 365]
FXuint doe=yoe*365+yoe/4-yoe/100+doy; // [0, 146096]
return era*146097+doe-719468; // Relative to epoch
}
// Returns year/month/day in civil calendar.
// z is number of days since 1970-01-01. Negative values indicate
// days prior to 1970-01-01.
// y is year, m is month of year (1..12), d is day of month (1..31).
void FXSystem::civilFromDays(FXint& y,FXint& m,FXint& d,FXlong z){
z+=719468; // Relative to era
FXlong era=(z>=0?z:z-146096)/146097; // Era
FXuint doe=(FXuint)(z-era*146097); // [0, 146096]
FXuint yoe=(doe-doe/1460+doe/36524-doe/146096)/365; // [0, 399]
FXuint doy=doe-(365*yoe+yoe/4-yoe/100); // [0, 365]
FXuint mp=(5*doy+2)/153; // [0, 11]
y=(FXint)(era*400+yoe);
d=doy-(153*mp+2)/5+1; // [1, 31]
m=mp+(mp<10?3:-9); // [1, 12]
y+=(m<=2); // March
}
/*******************************************************************************/
// Compute nanoseconds since Unix Epoch from struct tm
FXTime FXSystem::timeFromSystemTime(const Time& st){
FXint year=st.year;
FXint month=st.month;
FXint day=st.mday;
FXint hour=st.hour;
FXint min=st.min;
FXint sec=st.sec;
FXint nano=st.nano;
FXint leap;
// Validate nanoseconds
if(nano>seconds){
sec+=nano/seconds;
nano%=seconds;
}
// Validate seconds
if(sec>=60){
min+=sec/60;
sec%=60;
}
// Validate minutes
if(min>=60){
hour+=min/60;
min%=60;
}
// Validate days
if(hour>=24){
day+=hour/24;
hour%=24;
}
// Validate month
if(month>=13){
year+=(month-1)/12;
month=(month-1)%12+1;
}
// Is leap year
leap=is_leap(year);
// Validate day of month
while(day>days_of_year[leap][month]){
day-=days_of_year[leap][month];
month+=1;
if(13<=month){
month=1;
year+=1;
leap=is_leap(year);
}
}
// Return nanoseconds since Epoch
return (((daysFromCivil(year,month,day)*24+hour)*60+min)*60+sec)*seconds+nano;
}
// Return system time from utc in nanoseconds since Unix Epoch
void FXSystem::systemTimeFromTime(Time& st,FXTime utc){
// Compute days from nanoseconds, rounding down
FXlong zz=(0<=utc ? utc : utc-(days-1))/days;
// Compute date from seconds
civilFromDays(st.year,st.month,st.mday,zz);
// Compute day of year
st.yday=yearday_from_date(st.year,st.month,st.mday);
// Compute day of week
st.wday=weekday_from_days(zz);
// Hours
utc=utc-zz*days;
st.hour=(FXint)(utc/hours);
// Minutes
utc=utc-st.hour*hours;
st.min=(FXint)(utc/minutes);
// Seconds
utc=utc-st.min*minutes;
st.sec=(FXint)(utc/seconds);
// Nanoseconds
utc=utc-st.sec*seconds;
st.nano=(FXint)utc;
// Offset utc
st.offset=0;
}
/*******************************************************************************/
// Time zone variables, set only once
static volatile FXuint local_zone_set=0;
#if defined(_WIN32)
//
// The Windows TIME_ZONE_INFORMATION struct contains:
//
// struct TIME_ZONE_INFORMATION {
// LONG Bias; // UTC = localtime + bias (minutes)
// WCHAR StandardName[32]; // Standard time name
// SYSTEMTIME StandardDate; // Time when daylight saving to standard time occurs
// LONG StandardBias; // Value added to Bias during standard time (most zones = 0)
// WCHAR DaylightName[32]; // Daylight savings time name
// SYSTEMTIME DaylightDate; // Time when standard time to daylight savings time occurs
// LONG DaylightBias; // Value added to Bias during daylight savings time (most zones = -60)
// };
//
// While the Windows SYSTEMTIME struct contains:
//
// struct SYSTEMTIME {
// WORD wYear; // Year, 1601..
// WORD wMonth; // Month, january..december (1..12)
// WORD wDayOfWeek; // Day of week, sunday..monday (0..7)
// WORD wDay; // Day of month, (1..31) [SEE NOTES BELOW]
// WORD wHour; // Hour (0..23)
// WORD wMinute; // Minutes (0..59)
// WORD wSecond; // Seconds (0..59)
// WORD wMilliseconds; // Milliseconds (0..999)
// };
//
static TIME_ZONE_INFORMATION tzi;
#endif
// Call tzset() only once
static void setuplocaltimezone(){
if(atomicSet(&local_zone_set,1)==0){
#if defined(_WIN32)
GetTimeZoneInformation(&tzi);
#else
tzset();
#endif
}
}
// Return offset between standard local time zone to UTC, in nanoseconds
FXTime FXSystem::localTimeZoneOffset(){
setuplocaltimezone();
#if defined(_WIN32)
return minutes*tzi.Bias; // +minutes*tzi.StandardBias;
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
struct tm tmresult;
time_t tmp=time(&tmp);
struct tm* ptm=localtime_r(&tmp,&tmresult);
return seconds*(-ptm->tm_gmtoff + ptm->tm_isdst*3600);
#else
return seconds*timezone;
#endif
}
// Return offset daylight savings time to standard time, in nanoseconds
FXTime FXSystem::daylightSavingsOffset(){
setuplocaltimezone();
#if defined(_WIN32)
return minutes*tzi.DaylightBias; // Or difference between standard and daylight bias.
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
return -hours*((tzname[1][0] == ' ') ? 0 : 1);
#else
return -hours*daylight;
#endif
}
// Return time zone name (or daylight savings time time zone name)
FXString FXSystem::localTimeZoneName(FXbool dst){
setuplocaltimezone();
#if defined(_WIN32)
return FXString(dst?tzi.DaylightName:tzi.StandardName);
#else
return FXString(tzname[dst]);
#endif
}
#if defined(WIN32)
// Convert utc (ns) since 01/01/1970 to 100ns since 01/01/1601
static inline FXTime fxwintime(FXTime utc){
return utc/FXLONG(100)+FXLONG(116444736000000000);
}
// Compare incoming date cur against changeover date chg; there are
// two ways to compare the numbers.
// When chg.wYear!=0, the change over occurs only once on the indicated
// absolute time.
// If chg.wYear==0, the switch between standard and daylight savings time
// occurs yearly, on the nth occurrence of wDayOfWeek, where n is given
// in the wDay variable of the TIME_ZONE_INFORMATION StandardDate or
// DaylightDate struct.
static FXint compare_zone_switch_over(const SYSTEMTIME& cur,const SYSTEMTIME& chg){
static const FXint month_lengths[12]={31,28,31,30,31,30,31,31,30,31,30,31};
FXint cursecs,chgsecs,chgday,first,last;
// Absolute date
if(chg.wYear){
if(cur.wYear==chg.wYear){
if(cur.wMonth==chg.wMonth){
if(cur.wDay==chg.wDay){
cursecs=(cur.wHour*60+cur.wMinute)*60+cur.wSecond;
chgsecs=(chg.wHour*60+chg.wMinute)*60+chg.wSecond;
return cursecs-chgsecs;
}
return cur.wDay-chg.wDay;
}
return cur.wMonth-chg.wMonth;
}
return cur.wYear-chg.wYear;
}
// Relative date
if(cur.wMonth==chg.wMonth){
// Calculate the day of the first wDayOfWeek in the month
first=(6+chg.wDayOfWeek-cur.wDayOfWeek+cur.wDay)%7+1;
// Check needed for the 5th weekday of the month
last=month_lengths[cur.wMonth-1]+(cur.wMonth==2 && is_leap(cur.wYear));
// Switch at the nth occurrence (value in chg.wDay) of certain day of week
chgday=first+7*(chg.wDay-1);
if(chgday>last) chgday-=7;
chgsecs=((chgday*24+chg.wHour)*60+chg.wMinute)*60+chg.wSecond;
cursecs=((cur.wDay*24+cur.wHour)*60+cur.wMinute)*60+cur.wSecond;
return cursecs-chgsecs;
}
return (FXint)cur.wMonth-(FXint)chg.wMonth;
}
// Determine if daylight savings in effect
// The daylight savings time switch is tied to local time, and local
// time's year is tied to time zone. However, time zone is a function
// of the local year [time zone info can change from year to year].
// We assume that the time zone is relatively stable, but we may
// call GetTimeZoneInformationForYear() in a future revision to
// obtain only DST/STD time switches, which are changed more rapidly.
static FXint daylightSavingsState(FXTime utc,FXbool local){
// Expanded date/time
SYSTEMTIME loc;
// Assume local time
FXTime ftloc=utc;
FXTime ftdst=utc;
FXTime ftstd=utc;
// If UTC, convert to local using
if(!local){
ftloc=ftloc-tzi.Bias*minutes;
ftdst=ftloc-tzi.DaylightBias*minutes;
ftstd=ftloc-tzi.StandardBias*minutes;
}
// Convert to windows time
ftloc=fxwintime(ftloc);
// Get expanded date/time
FileTimeToSystemTime((const FILETIME*)&ftloc,&loc);
// If wMonth is zero then no daylight savings in effect
if(tzi.DaylightDate.wMonth && tzi.StandardDate.wMonth){
// Convert UNIX Epoch to windows time
ftdst=fxwintime(ftdst);
ftstd=fxwintime(ftstd);
// Expanded date/time
SYSTEMTIME dst;
SYSTEMTIME std;
// Get expanded date/time
FileTimeToSystemTime((const FILETIME*)&ftdst,&dst);
FileTimeToSystemTime((const FILETIME*)&ftstd,&std);
// Daylight savings time prior to switch to standard time
FXbool before_std_date=(compare_zone_switch_over(dst,tzi.StandardDate)<0);
// Standard time after switch to daylight savings time
FXbool after_dst_date=(compare_zone_switch_over(std,tzi.DaylightDate)>=0);
// Northern hemisphere
if(tzi.DaylightDate.wMonthtm_isdst!=0));
return ptm && ptm->tm_isdst!=0;
#else
time_t tmp=(time_t)(utc/seconds);
struct tm* ptm=localtime(&tmp);
FXTRACE((100,"FXSystem::daylightSavingsActive(%lld) = %d\n",utc,ptm && ptm->tm_isdst!=0));
return ptm && ptm->tm_isdst!=0;
#endif
}
/*******************************************************************************/
// Format utc in nanoseconds since Unix Epoch to date-time string using given format
FXString FXSystem::universalTime(FXTime utc,const FXchar *format){
FXSystem::Time st={0,0,0,0,0,0,0,0,0,0};
FXSystem::systemTimeFromTime(st,utc);
return FXSystem::systemTimeFormat(st,format);
}
// Parse utc date-time string to UTC nanoseconds since Unix Epoch using given format
// Assume string is time in UTC, unless a time zone offset was parsed; in that
// case, adjust the time according to the zone offset.
FXTime FXSystem::universalTime(const FXchar* string,const FXchar* format){
FXSystem::Time st={0,0,0,0,0,0,0,0,0,0};
if(FXSystem::systemTimeParse(st,string,format)){
return FXSystem::timeFromSystemTime(st)+st.offset*seconds;
}
return forever;
}
// Parse date-time string to UTC nanoseconds since Unix Epoch using given format
FXTime FXSystem::universalTime(const FXString& string,const FXchar* format){
return FXSystem::universalTime(string.text(),format);
}
/*******************************************************************************/
// Format utc in nanoseconds since Unix Epoch to local date-time string using given format
FXString FXSystem::localTime(FXTime utc,const FXchar *format){
FXTime zoneoffset=FXSystem::localTimeZoneOffset()+FXSystem::daylightSavingsOffset()*FXSystem::daylightSavingsActive(utc);
FXSystem::Time st={0,0,0,0,0,0,0,0,0,(FXint)(zoneoffset/seconds)};
FXSystem::systemTimeFromTime(st,utc-zoneoffset);
return FXSystem::systemTimeFormat(st,format);
}
// Parse local date-time string to UTC nanoseconds since Unix Epoch using given format
// Assume string is time in local time, unless a time zone offset was parsed; in that
// case, adjust the time according to the zone offset.
FXTime FXSystem::localTime(const FXchar* string,const FXchar* format){
FXSystem::Time st={0,0,0,0,0,0,0,0,0,0};
if(FXSystem::systemTimeParse(st,string,format)){
//FXTime zoneoffset=FXSystem::localTimeZoneOffset()+FXSystem::daylightSavingsOffset()*FXSystem::daylightSavingsActive(FXThread::time());
return FXSystem::timeFromSystemTime(st)+st.offset*seconds;
}
return forever;
}
// Parse local date-time string to UTC nanoseconds since Unix Epoch using given format
FXTime FXSystem::localTime(const FXString& string,const FXchar* format){
return FXSystem::localTime(string.text(),format);
}
}