43#include "boost/format.hpp"
52constexpr long long dafBase::DateTime::invalid_nsecs;
55static double const MJD_TO_JD = 2400000.5;
56static double const EPOCH_IN_MJD = 40587.0;
57static double const JD2000 = 2451545.0;
60static double const NSEC_PER_DAY = 86.4e12;
63static long long const LL_NSEC_PER_SEC = 1000000000LL;
71static double const MAX_DAYS = 106751.99;
74static double const HOURS_PER_DAY = 24.0;
75static double const MIN_PER_DAY = 1440.0;
76static double const SEC_PER_DAY = 86400.0;
80static long long const TT_MINUS_TAI_NSECS = 32184000000LL;
881961 JAN 1 =JD 2437300.5 TAI-UTC= 1.4228180 S + (MJD - 37300.) X 0.001296 S\n\
891961 AUG 1 =JD 2437512.5 TAI-UTC= 1.3728180 S + (MJD - 37300.) X 0.001296 S\n\
901962 JAN 1 =JD 2437665.5 TAI-UTC= 1.8458580 S + (MJD - 37665.) X 0.0011232S\n\
911963 NOV 1 =JD 2438334.5 TAI-UTC= 1.9458580 S + (MJD - 37665.) X 0.0011232S\n\
921964 JAN 1 =JD 2438395.5 TAI-UTC= 3.2401300 S + (MJD - 38761.) X 0.001296 S\n\
931964 APR 1 =JD 2438486.5 TAI-UTC= 3.3401300 S + (MJD - 38761.) X 0.001296 S\n\
941964 SEP 1 =JD 2438639.5 TAI-UTC= 3.4401300 S + (MJD - 38761.) X 0.001296 S\n\
951965 JAN 1 =JD 2438761.5 TAI-UTC= 3.5401300 S + (MJD - 38761.) X 0.001296 S\n\
961965 MAR 1 =JD 2438820.5 TAI-UTC= 3.6401300 S + (MJD - 38761.) X 0.001296 S\n\
971965 JUL 1 =JD 2438942.5 TAI-UTC= 3.7401300 S + (MJD - 38761.) X 0.001296 S\n\
981965 SEP 1 =JD 2439004.5 TAI-UTC= 3.8401300 S + (MJD - 38761.) X 0.001296 S\n\
991966 JAN 1 =JD 2439126.5 TAI-UTC= 4.3131700 S + (MJD - 39126.) X 0.002592 S\n\
1001968 FEB 1 =JD 2439887.5 TAI-UTC= 4.2131700 S + (MJD - 39126.) X 0.002592 S\n\
1011972 JAN 1 =JD 2441317.5 TAI-UTC= 10.0 S + (MJD - 41317.) X 0.0 S\n\
1021972 JUL 1 =JD 2441499.5 TAI-UTC= 11.0 S + (MJD - 41317.) X 0.0 S\n\
1031973 JAN 1 =JD 2441683.5 TAI-UTC= 12.0 S + (MJD - 41317.) X 0.0 S\n\
1041974 JAN 1 =JD 2442048.5 TAI-UTC= 13.0 S + (MJD - 41317.) X 0.0 S\n\
1051975 JAN 1 =JD 2442413.5 TAI-UTC= 14.0 S + (MJD - 41317.) X 0.0 S\n\
1061976 JAN 1 =JD 2442778.5 TAI-UTC= 15.0 S + (MJD - 41317.) X 0.0 S\n\
1071977 JAN 1 =JD 2443144.5 TAI-UTC= 16.0 S + (MJD - 41317.) X 0.0 S\n\
1081978 JAN 1 =JD 2443509.5 TAI-UTC= 17.0 S + (MJD - 41317.) X 0.0 S\n\
1091979 JAN 1 =JD 2443874.5 TAI-UTC= 18.0 S + (MJD - 41317.) X 0.0 S\n\
1101980 JAN 1 =JD 2444239.5 TAI-UTC= 19.0 S + (MJD - 41317.) X 0.0 S\n\
1111981 JUL 1 =JD 2444786.5 TAI-UTC= 20.0 S + (MJD - 41317.) X 0.0 S\n\
1121982 JUL 1 =JD 2445151.5 TAI-UTC= 21.0 S + (MJD - 41317.) X 0.0 S\n\
1131983 JUL 1 =JD 2445516.5 TAI-UTC= 22.0 S + (MJD - 41317.) X 0.0 S\n\
1141985 JUL 1 =JD 2446247.5 TAI-UTC= 23.0 S + (MJD - 41317.) X 0.0 S\n\
1151988 JAN 1 =JD 2447161.5 TAI-UTC= 24.0 S + (MJD - 41317.) X 0.0 S\n\
1161990 JAN 1 =JD 2447892.5 TAI-UTC= 25.0 S + (MJD - 41317.) X 0.0 S\n\
1171991 JAN 1 =JD 2448257.5 TAI-UTC= 26.0 S + (MJD - 41317.) X 0.0 S\n\
1181992 JUL 1 =JD 2448804.5 TAI-UTC= 27.0 S + (MJD - 41317.) X 0.0 S\n\
1191993 JUL 1 =JD 2449169.5 TAI-UTC= 28.0 S + (MJD - 41317.) X 0.0 S\n\
1201994 JUL 1 =JD 2449534.5 TAI-UTC= 29.0 S + (MJD - 41317.) X 0.0 S\n\
1211996 JAN 1 =JD 2450083.5 TAI-UTC= 30.0 S + (MJD - 41317.) X 0.0 S\n\
1221997 JUL 1 =JD 2450630.5 TAI-UTC= 31.0 S + (MJD - 41317.) X 0.0 S\n\
1231999 JAN 1 =JD 2451179.5 TAI-UTC= 32.0 S + (MJD - 41317.) X 0.0 S\n\
1242006 JAN 1 =JD 2453736.5 TAI-UTC= 33.0 S + (MJD - 41317.) X 0.0 S\n\
1252009 JAN 1 =JD 2454832.5 TAI-UTC= 34.0 S + (MJD - 41317.) X 0.0 S\n\
1262012 JUL 1 =JD 2456109.5 TAI-UTC= 35.0 S + (MJD - 41317.) X 0.0 S\n\
1272015 JUL 1 =JD 2457204.5 TAI-UTC= 36.0 S + (MJD - 41317.) X 0.0 S\n\
1282017 JAN 1 =JD 2457754.5 TAI-UTC= 37.0 S + (MJD - 41317.) X 0.0 S\n\
149LeapTable leapSecTable;
151LeapTable::LeapTable(
void) { dafBase::DateTime::initializeLeapSeconds(leapString); }
159template <
typename NsType>
160NsType utcToTai(NsType nsecs) {
162 for (i = 0; i < leapSecTable.size(); ++i) {
163 if (nsecs < leapSecTable[i].whenUtc)
break;
168 (boost::format(
"DateTime value too early for UTC-TAI conversion: %1%") % nsecs).str());
170 Leap
const& l(leapSecTable[i - 1]);
171 double mjd =
static_cast<double>(nsecs) / NSEC_PER_DAY + EPOCH_IN_MJD;
172 double leapSecs = l.offset + (mjd - l.mjdRef) * l.drift;
173 NsType leapNSecs =
static_cast<NsType
>(leapSecs * 1.0e9 + 0.5);
174 return nsecs + leapNSecs;
183template <
typename NsType>
184NsType taiToUtc(NsType nsecs) {
186 for (i = 0; i < leapSecTable.size(); ++i) {
187 if (nsecs < leapSecTable[i].whenTai)
break;
192 (boost::format(
"DateTime value too early for TAI-UTC conversion: %1%") % nsecs).str());
194 Leap
const& l(leapSecTable[i - 1]);
195 double mjd =
static_cast<double>(nsecs) / NSEC_PER_DAY + EPOCH_IN_MJD;
196 double leapSecs = l.offset + (mjd - l.mjdRef) * l.drift;
198 leapSecs /= 1.0 + l.drift * 1.0e9 / NSEC_PER_DAY;
199 NsType leapNSecs =
static_cast<NsType
>(leapSecs * 1.0e9 + 0.5);
200 return nsecs - leapNSecs;
210long long nsecAnyToTai(
long long nsecs, dafBase::DateTime::Timescale scale) {
212 case dafBase::DateTime::TAI:
214 case dafBase::DateTime::TT:
215 return nsecs - TT_MINUS_TAI_NSECS;
216 case dafBase::DateTime::UTC:
217 return utcToTai(nsecs);
220 os <<
"Unsupported scale " <<
scale;
231long long nsecTaiToAny(
long long nsecs, dafBase::DateTime::Timescale scale) {
233 case dafBase::DateTime::TAI:
235 case dafBase::DateTime::TT:
236 return nsecs + TT_MINUS_TAI_NSECS;
237 case dafBase::DateTime::UTC:
238 return taiToUtc(nsecs);
241 os <<
"Unsupported scale " <<
scale;
257double calendarToJd(
int year,
int month,
int day,
int hour,
int min,
double sec) {
262 int a = int(year / 100);
263 int b = 2 -
a + int(
a / 4);
265 int yy = 1582, mm = 10;
266 if (year < yy || (year == yy && month < mm) || (year == yy && month == mm && day <= 4)) {
270 double jd =
static_cast<int>(365.25 * (year + 4716)) +
static_cast<int>(30.6001 * (month + 1)) + day +
b -
272 jd += hour / HOURS_PER_DAY +
min / MIN_PER_DAY + sec / SEC_PER_DAY;
284void DateTime::setNsecsFromMjd(
double mjd, Timescale scale) {
285 if (mjd > EPOCH_IN_MJD + MAX_DAYS) {
287 (boost::format(
"MJD too far in the future: %1%") % mjd).str());
289 if (mjd < EPOCH_IN_MJD - MAX_DAYS) {
291 (boost::format(
"MJD too far in the past: %1%") % mjd).str());
293 _nsecs = nsecAnyToTai(
static_cast<long long>((mjd - EPOCH_IN_MJD) * NSEC_PER_DAY), scale);
296void DateTime::setNsecsFromJd(
double jd, Timescale scale) { setNsecsFromMjd(jd - MJD_TO_JD, scale); }
303void DateTime::setNsecsFromEpoch(
double epoch, Timescale scale) {
304 setNsecsFromMjd(365.25 * (epoch - 2000.0) + JD2000 - MJD_TO_JD, scale);
314 setNsecsFromMjd(date, scale);
317 setNsecsFromJd(date, scale);
320 setNsecsFromEpoch(date, scale);
329 int const minYear = 1902;
330 int const maxYear = 2261;
331 if ((year < minYear) || (year > maxYear)) {
334 (boost::format(
"Year = %d out of range [%04d, %04d]") % year % minYear % maxYear).str());
338 tm.tm_year = year - 1900;
339 tm.tm_mon = month - 1;
353 time_t secs = timegm(&tm);
371 if (timegm(&tm) != -1) {
377 (boost::format(
"Unconvertible date: %04d-%02d-%02dT%02d:%02d:%02d") % year %
378 month % day % hr %
min % sec)
383 _nsecs = nsecAnyToTai(secs * LL_NSEC_PER_SEC, scale);
391 "(\\d{4})-?(\\d{2})-?(\\d{2})"
393 "(\\d{2}):?(\\d{2}):?(\\d{2})"
399 "(\\d{4})-?(\\d{2})-?(\\d{2})"
401 "(\\d{2}):?(\\d{2}):?(\\d{2})"
405 if (!regex_match(iso8601, matches, re)) {
410 DateTime dt(atoi(matches.
str(1).c_str()), atoi(matches.
str(2).c_str()), atoi(matches.
str(3).c_str()),
411 atoi(matches.
str(4).c_str()), atoi(matches.
str(5).c_str()), atoi(matches.
str(6).c_str()),
415 if (matches[7].matched) {
417 int places = frac.
size();
421 int value = atoi(frac.
c_str());
434 return _getMjd(scale);
437 return _getJd(scale);
440 return _getEpoch(scale);
453 return nsecTaiToAny(_nsecs, scale);
456double DateTime::_getMjd(Timescale scale)
const {
458 double nsecs = nsecTaiToAny(_nsecs, scale);
459 return nsecs / NSEC_PER_DAY + EPOCH_IN_MJD;
462double DateTime::_getJd(Timescale scale)
const {
return _getMjd(scale) + MJD_TO_JD; }
464double DateTime::_getEpoch(Timescale scale)
const {
return 2000.0 + (_getJd(scale) - JD2000) / 365.25; }
469 long long nsecs = nsecTaiToAny(_nsecs, scale);
471 long long frac = nsecs % LL_NSEC_PER_SEC;
472 if (nsecs < 0 && frac < 0) {
473 nsecs -= LL_NSEC_PER_SEC + frac;
477 time_t secs =
static_cast<time_t
>(nsecs / LL_NSEC_PER_SEC);
478 gmtime_r(&secs, &gmt);
482struct timespec
DateTime::timespec(Timescale scale) const {
485 long long nsecs = nsecTaiToAny(_nsecs, scale);
486 ts.tv_sec =
static_cast<time_t
>(nsecs / LL_NSEC_PER_SEC);
487 ts.tv_nsec =
static_cast<int>(nsecs % LL_NSEC_PER_SEC);
491struct timeval
DateTime::timeval(Timescale scale) const {
494 long long nsecs = nsecTaiToAny(_nsecs, scale);
495 tv.tv_sec =
static_cast<time_t
>(nsecs / LL_NSEC_PER_SEC);
496 tv.tv_usec =
static_cast<int>((nsecs % LL_NSEC_PER_SEC) / 1000);
502 struct tm gmt(this->
gmtime(scale));
504 long long fracnsecs = nsecTaiToAny(_nsecs, scale) % LL_NSEC_PER_SEC;
506 fracnsecs += LL_NSEC_PER_SEC;
508 auto fmtStr = scale ==
UTC ?
"%04d-%02d-%02dT%02d:%02d:%02d.%09dZ" :
"%04d-%02d-%02dT%02d:%02d:%02d.%09d";
509 return (boost::format(fmtStr) % (gmt.tm_year + 1900) % (gmt.tm_mon + 1) % gmt.tm_mday % gmt.tm_hour %
510 gmt.tm_min % gmt.tm_sec % fracnsecs)
520 int ret = gettimeofday(&tv, 0);
524 long long nsecs = tv.tv_sec * LL_NSEC_PER_SEC + tv.tv_usec * 1000LL;
530 leapSecTable.clear();
532 "\\d{4}.*?=JD\\s*([\\d.]+)\\s+TAI-UTC=\\s+([\\d.]+)\\s+S"
533 " \\+ \\(MJD - ([\\d.]+)\\) X ([\\d.]+)\\s*S");
538 double mjdUtc =
std::stod((*i)[1]) - MJD_TO_JD;
542 l.whenUtc =
static_cast<long long>((mjdUtc - EPOCH_IN_MJD) * NSEC_PER_DAY);
543 l.whenTai = l.whenUtc +
static_cast<long long>(1.0e9 * (l.offset + (mjdUtc - l.mjdRef) * l.drift));
544 leapSecTable.push_back(l);
Interface for DateTime class.
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Class for handling dates/times, including MJD, UTC, and TAI.
DateTime()
Default constructor: construct an invalid DateTime.
std::size_t hash_value() const noexcept
Return a hash of this object.
std::string toString(Timescale scale) const
Get date as an ISO8601-formatted string.
bool isValid() const
Is this date valid?
static DateTime now(void)
Return current time as a DateTime.
double get(DateSystem system=MJD, Timescale scale=TAI) const
Get date as a double in a specified representation, such as MJD.
struct timeval timeval(Timescale scale) const
Get date as a timeval struct, with time in seconds and microseconds.
struct tm gmtime(Timescale scale) const
Get date as a tm struct, with truncated fractional seconds.
static void initializeLeapSeconds(std::string const &leapString)
Initialize the leap second table from USNO.
bool operator==(DateTime const &rhs) const
static constexpr long long invalid_nsecs
long long nsecs(Timescale scale=TAI) const
Get date as nanoseconds since the unix epoch.
Reports arguments outside the domain of an operation.
Reports invalid arguments.
Reports errors that are due to events beyond the control of the program.
scale(algorithm, min, max=None, frame=None)