#pragma once
#include <cmath>
#include <time.h>
#include <cstdint>
#include <errno.h>
#include <system_error>
#include <panda/string.h>
#include <panda/date/inc.h>
#include <panda/date/parse.h>
namespace panda { namespace date {
using panda::time::TimezoneSP;
using panda::time::datetime;
using panda::time::days_in_month;
using panda::time::tzget;
using panda::time::tzlocal;
struct DateRel;
constexpr const uint32_t MICROSECONDS_IN_SECOND = 1000000;
constexpr const ptime_t MAX_MICROSECONDS = 9223372036854000000;
struct Date {
static inline Date now () { return Date(::time(NULL)); }
static inline Date now_hires (clockid_t clock_id = CLOCK_REALTIME) {
struct timespec ts;
int status = clock_gettime(clock_id, &ts);
if (status == 0) {
auto ret = Date(ts.tv_sec);
ret._mksec = ts.tv_nsec / 1000;
return ret;
}
throw std::system_error(errno, std::generic_category(), "clock_gettime");
}
static inline Date today () {
Date ret = now();
ret.truncate();
return ret;
}
explicit Date (ptime_t epoch = 0, const TimezoneSP& zone = TimezoneSP()) : _error(E_OK) { set(epoch, zone); }
template <class T, typename = std::enable_if_t<std::is_floating_point<T>::value>> // otherwise would be ambiguity with previous ctor if passed not ptime_t and not double
explicit Date (T epoch, const TimezoneSP& zone = TimezoneSP()) : _error(E_OK) { set(epoch, zone); }
Date (ptime_t epoch, ptime_t mksec, const TimezoneSP& zone = TimezoneSP()) : _error(E_OK) { set(epoch, mksec, zone); }
explicit Date (string_view str, const TimezoneSP& zone = TimezoneSP()) { set(str, zone); }
Date (int32_t year, ptime_t mon, ptime_t day, ptime_t hour = 0, ptime_t min = 0, ptime_t sec = 0, ptime_t mksec = 0, int isdst = -1, const TimezoneSP& zone = TimezoneSP()) {
set(year, mon, day, hour, min, sec, mksec, isdst, zone);
}
Date (const Date& source, const TimezoneSP& zone = TimezoneSP()) { set(source, zone); }
void set (ptime_t ep, const TimezoneSP& zone = TimezoneSP()) {
_zone_set(zone);
epoch(ep);
}
void set (ptime_t ep, ptime_t mksec, const TimezoneSP& zone = TimezoneSP()) {
if (mksec >= 0 && mksec < MICROSECONDS_IN_SECOND) {
set(ep, zone);
_mksec = mksec;
} else {
auto tmp = (uint64_t)(mksec + MAX_MICROSECONDS) % MICROSECONDS_IN_SECOND;
set((ptime_t)(ep + (mksec - tmp) / MICROSECONDS_IN_SECOND), zone);
_mksec = tmp;
}
}
void set (double ep, const TimezoneSP& zone = TimezoneSP()) {
_zone_set(zone);
epoch(ep);
}
err_t set (string_view str, const TimezoneSP& zone = TimezoneSP()) {
TimezoneSP instring_zone;
_mksec = 0;
_error = parse(str, _date, &_mksec, instring_zone); // parse() can parse and create zone
_zone_set(instring_zone ? instring_zone : zone);
if (_error == E_OK) {
_has_date = true;
dchg_auto();
if (_range_check) validate_range();
}
else epoch(0);
return (err_t)_error;
}
err_t set (int32_t year, ptime_t month, ptime_t day, ptime_t hour = 0, ptime_t min = 0, ptime_t sec = 0, ptime_t mksec = 0, int isdst= -1, const TimezoneSP& zone = TimezoneSP()) {
_zone_set(zone);
_error = E_OK;
_date.year = year;
_date.mon = month - 1;
_date.mday = day;
_date.hour = hour;
_date.min = min;
_date.sec = sec;
_date.isdst = isdst;
_has_date = true;
if (mksec >= 0 && mksec < MICROSECONDS_IN_SECOND) _mksec = mksec;
else {
_mksec = (uint64_t)(mksec + MAX_MICROSECONDS) % MICROSECONDS_IN_SECOND;
_date.sec += (mksec - _mksec) / MICROSECONDS_IN_SECOND;
}
dchg();
if (_range_check) validate_range();
return (err_t)_error;
}
void set (const Date& source, const TimezoneSP& zone = TimezoneSP()) {
_error = source._error;
if (!zone || _error) {
_has_epoch = source._has_epoch;
_has_date = source._has_date;
_normalized = source._normalized;
_zone = source._zone;
_epoch = source._epoch;
_mksec = source._mksec;
if (_has_date) _date = source._date;
} else {
source.dcheck();
_has_epoch = false;
_has_date = true;
_normalized = source._normalized;
_date = source._date;
_zone = zone;
_mksec = source._mksec;
}
}
Date& operator= (const Date& source) { set(source); return *this; }
Date clone (int32_t year, ptime_t mon=-1, ptime_t day=-1, ptime_t hour=-1, ptime_t min=-1, ptime_t sec=-1, ptime_t mksec=-1, int isdst=-1, const TimezoneSP& zone = TimezoneSP()) const {
dcheck();
return Date(
year >= 0 ? year : _date.year,
mon > 0 ? mon : _date.mon+1,
day > 0 ? day : _date.mday,
hour >= 0 ? hour : _date.hour,
min >= 0 ? min : _date.min,
sec >= 0 ? sec : _date.sec,
mksec >= 0 ? mksec : _mksec,
isdst,
zone ? zone : _zone
);
}
const datetime& date () const { dcheck(); return _date; }
bool has_epoch () const { return _has_epoch; }
bool has_date () const { return _has_date; }
bool normalized () const { return _normalized; }
err_t error () const { return (err_t) _error; }
void error (err_t val) { error_set(val); }
const TimezoneSP& timezone () const { return _zone; }
void timezone (const TimezoneSP& zone) {
dcheck();
_zone = zone ? zone : tzlocal();
dchg_auto();
}
void to_timezone (const TimezoneSP& zone) {
echeck();
_zone = zone ? zone : tzlocal();
echg();
}
ptime_t epoch () const { echeck(); return _epoch; }
void epoch (ptime_t val) {
namespace pt = panda::time;
_epoch = val;
_mksec = 0;
_has_epoch = true;
echg();
if ((val > pt::EPOCH_MAX) || (val < pt::EPOCH_MIN)) error_set(E_RANGE);
}
template <class T, typename = std::enable_if_t<std::is_floating_point<T>::value>> // otherwise ambiguity
void epoch (T val) { epoch((ptime_t)val); _mksec = std::round((val - (ptime_t)val) * MICROSECONDS_IN_SECOND); }
double epoch_mks () const { echeck(); return (double)_epoch + (double(_mksec) / MICROSECONDS_IN_SECOND); }
void epoch_mks (double val) { epoch(val); }
int32_t year () const { dcheck(); return _date.year; }
void year (int32_t val) { dcheck(); _date.year = val; dchg_auto(); }
int32_t c_year () const { return year() - 1900; }
void c_year (int32_t val) { year(val + 1900); }
int8_t yr () const { return year() % 100; }
void yr (int val) { year( year() - yr() + val ); }
uint8_t month () const { dcheck(); return _date.mon + 1; }
void month (ptime_t val) { dcheck(); _date.mon = val - 1; dchg_auto(); }
uint8_t c_month () const { return month() - 1; }
void c_month (ptime_t val) { month(val + 1); }
uint8_t mday () const { dcheck(); return _date.mday; }
void mday (ptime_t val) { dcheck(); _date.mday = val; dchg_auto(); }
uint8_t day () const { return mday(); }
void day (ptime_t val) { mday(val); }
uint8_t hour () const { dcheck(); return _date.hour; }
void hour (ptime_t val) { dcheck(); _date.hour = val; dchg_auto(); }
uint8_t min () const { dcheck(); return _date.min; }
void min (ptime_t val) { dcheck(); _date.min = val; dchg_auto(); }
uint8_t sec () const { dcheck(); return _date.sec; }
void sec (ptime_t val) { dcheck(); _date.sec = val; dchg_auto(); }
uint32_t mksec () const { return _mksec; }
void mksec (ptime_t val) {
if (val >= 0 && val < MICROSECONDS_IN_SECOND) _mksec = val;
else {
_mksec = (uint64_t)(val + MAX_MICROSECONDS) % MICROSECONDS_IN_SECOND;
ptime_t add_secs = (val - _mksec) / MICROSECONDS_IN_SECOND;
if (_has_epoch) {
_epoch += add_secs;
echg();
} else {
_date.sec += add_secs;
dchg_auto();
}
}
}
uint8_t wday () const { dcheck(); return _date.wday + 1; }
void wday (ptime_t val) { dcheck(); _date.mday += val - (_date.wday + 1); dchg_auto(); }
uint8_t c_wday () const { return wday() - 1; }
void c_wday (ptime_t val) { wday(val + 1); }
uint8_t ewday () const { dcheck(); return _date.wday == 0 ? 7 : _date.wday; }
void ewday (ptime_t val) { _date.mday += val - ewday(); dchg_auto(); }
uint16_t yday () const { dcheck(); return _date.yday + 1; }
void yday (ptime_t val) { dcheck(); _date.mday += val - 1 - _date.yday; dchg_auto(); }
uint16_t c_yday () const { return yday() - 1; }
void c_yday (ptime_t val) { yday(val + 1); }
bool isdst () const { dcheck(); return _date.isdst > 0 ? true : false; }
int32_t gmtoff () const { dcheck(); return _date.gmtoff; }
string_view tzabbr () const { dcheck(); return (const char*)_date.zone; }
int days_in_month () const { dcheck(); return panda::time::days_in_month(_date.year, _date.mon); }
Date& month_begin () { mday(1); return *this; }
Date& month_end () { mday(days_in_month()); return *this; }
Date month_begin_new () const { Date ret(*this); ret.mday(1); return ret; }
Date month_end_new () const { Date ret(*this); ret.mday(days_in_month()); return ret; }
Date& truncate () {
dcheck();
_date.hour = _date.min = _date.sec = _mksec = 0;
dchg_auto();
return *this;
}
Date truncated () const {
Date ret(*this);
ret.truncate();
return ret;
}
panda::string strftime (const char*, panda::string*) const;
string_view errstr () const;
panda::string to_string () const {
panda::string result;
if (!_error) {
if (!_strfmt) iso(result);
else this->strftime(_strfmt.c_str(), &result);
}
return result;
}
ptime_t compare (const Date&) const;
Date& operator+= (const DateRel&);
Date& operator-= (const DateRel&);
panda::string iso () const;
void iso (panda::string&) const;
panda::string iso_sec () const;
void iso_sec (panda::string&) const;
panda::string mysql () const;
void mysql (panda::string&) const;
panda::string hms () const;
void hms (panda::string&) const;
panda::string ymd () const;
void ymd (panda::string& ) const;
panda::string mdy () const;
void mdy (panda::string&) const;
panda::string dmy () const;
void dmy (panda::string& ) const;
panda::string meridiam () const;
void meridiam (panda::string& ) const;
panda::string ampm () const;
void ampm (panda::string&) const;
static bool range_check () { return _range_check; }
static void range_check (bool val) { _range_check = val; }
static const string& string_format () { return _strfmt; }
static void string_format (const string& fmt) { _strfmt = fmt; }
private:
friend void swap (Date&, Date&);
static string _strfmt;
static bool _range_check;
TimezoneSP _zone;
union {
ptime_t _epoch;
mutable ptime_t _epochMUT;
};
union {
datetime _date;
mutable datetime _dateMUT;
};
union {
bool _has_epoch;
mutable bool _has_epochMUT;
};
union {
bool _has_date;
mutable bool _has_dateMUT;
};
union {
bool _normalized;
mutable bool _normalizedMUT;
};
union {
uint8_t _error;
mutable uint8_t _errorMUT;
};
union {
uint32_t _mksec;
mutable uint32_t _mksecMUT;
};
void esync () const;
void dsync () const;
err_t validate_range ();
void echeck () const { if (!_has_epoch) esync(); }
void dcheck () const { if (!_has_date || !_normalized) dsync(); };
void dchg () {
_has_epoch = false;
_normalized = false;
}
void dchg_auto () {
dchg();
_date.isdst = -1;
}
void echg () const {
_has_dateMUT = false;
_normalizedMUT = false;
}
void error_set(err_t val) const {
_errorMUT = val;
_epochMUT = 0;
_mksecMUT = 0;
_has_epochMUT = true;
echg();
}
void _zone_set (const TimezoneSP& zone) {
if (!_zone) _zone = zone ? zone : tzlocal();
else if (zone) _zone = zone;
}
};
inline bool operator== (const Date& lhs, const Date& rhs) { return !lhs.compare(rhs); }
inline bool operator!= (const Date& lhs, const Date& rhs) { return lhs.compare(rhs); }
inline bool operator< (const Date& lhs, const Date& rhs) { return lhs.compare(rhs) < 0; }
inline bool operator<= (const Date& lhs, const Date& rhs) { return lhs.compare(rhs) <= 0; }
inline bool operator> (const Date& lhs, const Date& rhs) { return lhs.compare(rhs) > 0; }
inline bool operator>= (const Date& lhs, const Date& rhs) { return lhs.compare(rhs) >= 0; }
inline Date operator+ (const Date& d, const DateRel& dr) { return Date(d) += dr; }
inline Date operator+ (const DateRel& dr, const Date& d) { return Date(d) += dr; }
inline Date operator- (const Date& d, const DateRel& dr) { return Date(d) -= dr; }
void swap (Date&, Date&);
}}