lsst.daf.base  15.0-1-gf4f1c34+8
DateTime.cc
Go to the documentation of this file.
1 // -*- lsst-c++ -*-
2 
3 /*
4  * LSST Data Management System
5  * Copyright 2008, 2009, 2010 LSST Corporation.
6  *
7  * This product includes software developed by the
8  * LSST Project (http://www.lsst.org/).
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the LSST License Statement and
21  * the GNU General Public License along with this program. If not,
22  * see <http://www.lsstcorp.org/LegalNotices/>.
23  */
24 
37 #include <limits>
38 #include <cmath>
39 #include <vector>
40 
41 #include "boost/format.hpp"
42 #include "boost/regex.hpp"
43 
44 #include "lsst/daf/base/DateTime.h"
45 #include "lsst/pex/exceptions.h"
46 
47 namespace dafBase = lsst::daf::base;
48 namespace pexEx = lsst::pex::exceptions;
49 
50 // invalid_nsecs is odr used but has an in-class initializer
51 constexpr long long dafBase::DateTime::invalid_nsecs;
52 
53 // Epoch = 1970 JAN 1 00:00:00 = JD 2440587.5 = MJD 40587.0
54 static double const MJD_TO_JD = 2400000.5;
55 static double const EPOCH_IN_MJD = 40587.0;
56 static double const JD2000 = 2451544.50;
57 
59 static double const NSEC_PER_DAY = 86.4e12;
60 
62 static long long const LL_NSEC_PER_SEC = 1000000000LL;
63 // static long long const LL_NSEC_PER_DAY = 86400 * LL_NSEC_PER_SEC;
64 
65 // Maximum number of days expressible as signed 64-bit nanoseconds.
66 // 2^64 / 2 / 1e9 / 86400
67 // NOTE: long long nsecs will wrap:
68 // -- earliest date representable = sep 21, 1677 00:00:00
69 // -- latest date representable = apr 12, 2262 00:00:00
70 static double const MAX_DAYS = 106751.99;
71 
72 
73 #ifdef CAL_TO_JD
74 static double const HOURS_PER_DAY = 24.0;
75 static double const MIN_PER_DAY = 1440.0;
76 static double const SEC_PER_DAY = 86400.0;
77 #endif
78 
79 // Difference between Terrestrial Time and TAI.
80 static long long const TT_MINUS_TAI_NSECS = 32184000000LL;
81 
82 /* Leap second table as string.
83  *
84  * Source: http://maia.usno.navy.mil/ser7/tai-utc.dat
85  */
86 static std::string leapString =
87 "\
88 1961 JAN 1 =JD 2437300.5 TAI-UTC= 1.4228180 S + (MJD - 37300.) X 0.001296 S\n\
89 1961 AUG 1 =JD 2437512.5 TAI-UTC= 1.3728180 S + (MJD - 37300.) X 0.001296 S\n\
90 1962 JAN 1 =JD 2437665.5 TAI-UTC= 1.8458580 S + (MJD - 37665.) X 0.0011232S\n\
91 1963 NOV 1 =JD 2438334.5 TAI-UTC= 1.9458580 S + (MJD - 37665.) X 0.0011232S\n\
92 1964 JAN 1 =JD 2438395.5 TAI-UTC= 3.2401300 S + (MJD - 38761.) X 0.001296 S\n\
93 1964 APR 1 =JD 2438486.5 TAI-UTC= 3.3401300 S + (MJD - 38761.) X 0.001296 S\n\
94 1964 SEP 1 =JD 2438639.5 TAI-UTC= 3.4401300 S + (MJD - 38761.) X 0.001296 S\n\
95 1965 JAN 1 =JD 2438761.5 TAI-UTC= 3.5401300 S + (MJD - 38761.) X 0.001296 S\n\
96 1965 MAR 1 =JD 2438820.5 TAI-UTC= 3.6401300 S + (MJD - 38761.) X 0.001296 S\n\
97 1965 JUL 1 =JD 2438942.5 TAI-UTC= 3.7401300 S + (MJD - 38761.) X 0.001296 S\n\
98 1965 SEP 1 =JD 2439004.5 TAI-UTC= 3.8401300 S + (MJD - 38761.) X 0.001296 S\n\
99 1966 JAN 1 =JD 2439126.5 TAI-UTC= 4.3131700 S + (MJD - 39126.) X 0.002592 S\n\
100 1968 FEB 1 =JD 2439887.5 TAI-UTC= 4.2131700 S + (MJD - 39126.) X 0.002592 S\n\
101 1972 JAN 1 =JD 2441317.5 TAI-UTC= 10.0 S + (MJD - 41317.) X 0.0 S\n\
102 1972 JUL 1 =JD 2441499.5 TAI-UTC= 11.0 S + (MJD - 41317.) X 0.0 S\n\
103 1973 JAN 1 =JD 2441683.5 TAI-UTC= 12.0 S + (MJD - 41317.) X 0.0 S\n\
104 1974 JAN 1 =JD 2442048.5 TAI-UTC= 13.0 S + (MJD - 41317.) X 0.0 S\n\
105 1975 JAN 1 =JD 2442413.5 TAI-UTC= 14.0 S + (MJD - 41317.) X 0.0 S\n\
106 1976 JAN 1 =JD 2442778.5 TAI-UTC= 15.0 S + (MJD - 41317.) X 0.0 S\n\
107 1977 JAN 1 =JD 2443144.5 TAI-UTC= 16.0 S + (MJD - 41317.) X 0.0 S\n\
108 1978 JAN 1 =JD 2443509.5 TAI-UTC= 17.0 S + (MJD - 41317.) X 0.0 S\n\
109 1979 JAN 1 =JD 2443874.5 TAI-UTC= 18.0 S + (MJD - 41317.) X 0.0 S\n\
110 1980 JAN 1 =JD 2444239.5 TAI-UTC= 19.0 S + (MJD - 41317.) X 0.0 S\n\
111 1981 JUL 1 =JD 2444786.5 TAI-UTC= 20.0 S + (MJD - 41317.) X 0.0 S\n\
112 1982 JUL 1 =JD 2445151.5 TAI-UTC= 21.0 S + (MJD - 41317.) X 0.0 S\n\
113 1983 JUL 1 =JD 2445516.5 TAI-UTC= 22.0 S + (MJD - 41317.) X 0.0 S\n\
114 1985 JUL 1 =JD 2446247.5 TAI-UTC= 23.0 S + (MJD - 41317.) X 0.0 S\n\
115 1988 JAN 1 =JD 2447161.5 TAI-UTC= 24.0 S + (MJD - 41317.) X 0.0 S\n\
116 1990 JAN 1 =JD 2447892.5 TAI-UTC= 25.0 S + (MJD - 41317.) X 0.0 S\n\
117 1991 JAN 1 =JD 2448257.5 TAI-UTC= 26.0 S + (MJD - 41317.) X 0.0 S\n\
118 1992 JUL 1 =JD 2448804.5 TAI-UTC= 27.0 S + (MJD - 41317.) X 0.0 S\n\
119 1993 JUL 1 =JD 2449169.5 TAI-UTC= 28.0 S + (MJD - 41317.) X 0.0 S\n\
120 1994 JUL 1 =JD 2449534.5 TAI-UTC= 29.0 S + (MJD - 41317.) X 0.0 S\n\
121 1996 JAN 1 =JD 2450083.5 TAI-UTC= 30.0 S + (MJD - 41317.) X 0.0 S\n\
122 1997 JUL 1 =JD 2450630.5 TAI-UTC= 31.0 S + (MJD - 41317.) X 0.0 S\n\
123 1999 JAN 1 =JD 2451179.5 TAI-UTC= 32.0 S + (MJD - 41317.) X 0.0 S\n\
124 2006 JAN 1 =JD 2453736.5 TAI-UTC= 33.0 S + (MJD - 41317.) X 0.0 S\n\
125 2009 JAN 1 =JD 2454832.5 TAI-UTC= 34.0 S + (MJD - 41317.) X 0.0 S\n\
126 2012 JUL 1 =JD 2456109.5 TAI-UTC= 35.0 S + (MJD - 41317.) X 0.0 S\n\
127 2015 JUL 1 =JD 2457204.5 TAI-UTC= 36.0 S + (MJD - 41317.) X 0.0 S\n\
128 2017 JAN 1 =JD 2457754.5 TAI-UTC= 37.0 S + (MJD - 41317.) X 0.0 S\n\
129 ";
130 
131 // Anonymous namespace for structures, classes, and formerly file-static
132 // functions.
133 namespace {
134 
136 struct Leap {
137  long long whenUtc;
138  long long whenTai;
139  double offset;
140  double mjdRef;
141  double drift;
142 };
143 
144 class LeapTable : public std::vector<Leap> {
145 public:
146  LeapTable(void);
147 };
148 
149 LeapTable leapSecTable;
150 
151 LeapTable::LeapTable(void) {
153 }
154 
161 template<typename NsType>
162 NsType utcToTai(NsType nsecs) {
163  size_t i;
164  for (i = 0; i < leapSecTable.size(); ++i) {
165  if (nsecs < leapSecTable[i].whenUtc) break;
166  }
167  if (i == 0) {
168  throw LSST_EXCEPT(
170  (boost::format(
171  "DateTime value too early for UTC-TAI conversion: %1%"
172  ) % nsecs).str());
173  }
174  Leap const& l(leapSecTable[i - 1]);
175  double mjd = static_cast<double>(nsecs) / NSEC_PER_DAY + EPOCH_IN_MJD;
176  double leapSecs = l.offset + (mjd - l.mjdRef) * l.drift;
177  NsType leapNSecs = static_cast<NsType>(leapSecs * 1.0e9 + 0.5);
178  return nsecs + leapNSecs;
179 }
180 
187 template<typename NsType>
188 NsType taiToUtc(NsType nsecs) {
189  size_t i;
190  for (i = 0; i < leapSecTable.size(); ++i) {
191  if (nsecs < leapSecTable[i].whenTai) break;
192  }
193  if (i == 0) {
194  throw LSST_EXCEPT(
196  (boost::format(
197  "DateTime value too early for TAI-UTC conversion: %1%"
198  ) % nsecs).str());
199  }
200  Leap const& l(leapSecTable[i - 1]);
201  double mjd = static_cast<double>(nsecs) / NSEC_PER_DAY + EPOCH_IN_MJD;
202  double leapSecs = l.offset + (mjd - l.mjdRef) * l.drift;
203  // Correct for TAI MJD vs. UTC MJD.
204  leapSecs /= 1.0 + l.drift * 1.0e9 / NSEC_PER_DAY;
205  NsType leapNSecs = static_cast<NsType>(leapSecs * 1.0e9 + 0.5);
206  return nsecs - leapNSecs;
207 }
208 
216 long long nsecAnyToTai(long long nsecs, dafBase::DateTime::Timescale scale) {
217  switch(scale) {
219  return nsecs;
221  return nsecs - TT_MINUS_TAI_NSECS;
223  return utcToTai(nsecs);
224  }
226  os << "Unsupported scale " << scale;
228 }
229 
237 long long nsecTaiToAny(long long nsecs, dafBase::DateTime::Timescale scale) {
238  switch(scale) {
240  return nsecs;
242  return nsecs + TT_MINUS_TAI_NSECS;
244  return taiToUtc(nsecs);
245  }
247  os << "Unsupported scale " << scale;
249 }
250 
251 
252 #ifdef CAL_TO_JD
253 
264 double calendarToJd(int year, int month, int day, int hour, int min, double sec) {
265  if ( month <= 2 ) {
266  year -= 1;
267  month += 12;
268  }
269  int a = int(year/100);
270  int b = 2 - a + int(a/4);
271 
272  int yy = 1582, mm = 10; //, d = 4;
273  if (year < yy || (year == yy && month < mm) || (year == yy && month == mm && day <= 4)) {
274  b = 0;
275  }
276 
277  double jd = static_cast<int>(365.25*(year + 4716)) +
278  static_cast<int>(30.6001*(month + 1)) + day + b - 1524.5;
279  jd += hour/HOURS_PER_DAY + min/MIN_PER_DAY + sec/SEC_PER_DAY;
280 
281  return jd;
282 }
283 
284 #endif // CAL_TO_JD
285 } // end anonymous namespace
286 
287 
288 namespace lsst {
289 namespace daf {
290 namespace base {
291 
292 void DateTime::setNsecsFromMjd(double mjd, Timescale scale) {
293 
294  if (mjd > EPOCH_IN_MJD + MAX_DAYS) {
295  throw LSST_EXCEPT(
297  (boost::format("MJD too far in the future: %1%") % mjd).str());
298  }
299  if (mjd < EPOCH_IN_MJD - MAX_DAYS) {
300  throw LSST_EXCEPT(
302  (boost::format("MJD too far in the past: %1%") % mjd).str());
303  }
304  _nsecs = nsecAnyToTai(static_cast<long long>((mjd - EPOCH_IN_MJD) * NSEC_PER_DAY), scale);
305 }
306 
307 void DateTime::setNsecsFromJd(double jd, Timescale scale) {
308  setNsecsFromMjd(jd - MJD_TO_JD, scale);
309 }
310 
316 void DateTime::setNsecsFromEpoch(double epoch, Timescale scale) {
317  setNsecsFromMjd(365.25*(epoch - 2000.0) + JD2000 - MJD_TO_JD, scale);
318 }
319 
320 DateTime::DateTime() :
321  _nsecs(DateTime::invalid_nsecs)
322 { }
323 
324 DateTime::DateTime(long long nsecs, Timescale scale) :
325  _nsecs(nsecAnyToTai(nsecs, scale))
326 { }
327 
328 DateTime::DateTime(double date, DateSystem system, Timescale scale) {
329  switch (system) {
330  case MJD:
331  setNsecsFromMjd(date, scale);
332  break;
333  case JD:
334  setNsecsFromJd(date, scale);
335  break;
336  case EPOCH:
337  setNsecsFromEpoch(date, scale);
338  break;
339  default:
340  throw LSST_EXCEPT(pexEx::InvalidParameterError, "DateSystem must be MJD, JD, or EPOCH");
341  break;
342  }
343 }
344 
345 DateTime::DateTime(int year, int month, int day, int hr, int min, int sec, Timescale scale) {
346  int const minYear = 1902;
347  int const maxYear = 2261;
348  if ((year < minYear) || (year > maxYear)) {
349  throw LSST_EXCEPT(
351  (boost::format("Year = %d out of range [%04d, %04d]") % year % minYear % maxYear).str());
352  }
353 
354  struct tm tm;
355  tm.tm_year = year - 1900;
356  tm.tm_mon = month - 1;
357  tm.tm_mday = day;
358  tm.tm_hour = hr;
359  tm.tm_min = min;
360  tm.tm_sec = sec;
361  tm.tm_wday = 0;
362  tm.tm_yday = 0;
363  tm.tm_isdst = 0;
364  tm.tm_gmtoff = 0;
365 
366  // Convert to seconds since the epoch, correcting to UTC.
367  // Although timegm() is non-standard, it is a commonly-supported
368  // extension and is much safer/more reliable than mktime(3) in that
369  // it doesn't try to deal with the anomalies of local time zones.
370  time_t secs = timegm(&tm);
371 
372  // long long nsecs will blow out beyond 1677-09-21T00:00:00 and 2262-04-12T00:00:00
373  // (refering to the values of EPOCH_IN_MJD +/- MAX_DAYS ... exceeds 64 bits.)
374  // On older machines a tm struct is only 32 bits, and saturates at:
375  // low end - 1901-12-13, 20:45:52
376  // hi end - 2038-01-19, 03:14:07
377  // On newer machines the upper limit is a date in 2262, but the low end is unchanged,
378  // and a unit test will show the problem for dates later than 2038-01-19
379 
380  // timegm returns -1 on error, but the date at unix epoch -1 second also returns a valid value of -1,
381  // so be sure to test for that
382 
383  if (secs == -1) {
384  bool isBad = true; // assume the worst
385  if (year == 1969) {
386  // date may be the one date at which unix sec = -1; try a different year
387  tm.tm_year = 70;
388  if (timegm(&tm) != -1) {
389  isBad = false;
390  }
391  }
392  if (isBad) {
393  throw LSST_EXCEPT(
395  (boost::format("Unconvertible date: %04d-%02d-%02dT%02d:%02d:%02d")
396  % year % month % day % hr % min % sec).str());
397  }
398  }
399 
400  _nsecs = nsecAnyToTai(secs * LL_NSEC_PER_SEC, scale);
401 }
402 
403 DateTime::DateTime(std::string const& iso8601, Timescale scale) {
404  boost::regex re;
405  if (scale == UTC) {
406  // time zone "Z" required
407  re = boost::regex("(\\d{4})-?(\\d{2})-?(\\d{2})" "T"
408  "(\\d{2}):?(\\d{2}):?(\\d{2})" "([.,](\\d*))?" "Z");
409  } else {
410  // no time zone character accepted
411  re = boost::regex("(\\d{4})-?(\\d{2})-?(\\d{2})" "T"
412  "(\\d{2}):?(\\d{2}):?(\\d{2})" "([.,](\\d*))?");
413  }
414  boost::smatch matches;
415  if (!regex_match(iso8601, matches, re)) {
417  "Not in acceptable ISO8601 format: " + iso8601);
418  }
419  // determine TAI nsec truncated to integer seconds
420  // by constructing a DateTime from year, month, day...
421  DateTime dt(atoi(matches.str(1).c_str()), atoi(matches.str(2).c_str()),
422  atoi(matches.str(3).c_str()), atoi(matches.str(4).c_str()),
423  atoi(matches.str(5).c_str()), atoi(matches.str(6).c_str()),
424  scale);
425  _nsecs = dt._nsecs;
426  // add fractional seconds, if any
427  if (matches[7].matched) {
428  std::string frac = matches.str(8);
429  int places = frac.size();
430  if (places > 9) { // truncate fractional nanosec
431  frac.erase(9);
432  }
433  int value = atoi(frac.c_str());
434  while (places < 9) {
435  value *= 10;
436  ++places;
437  }
438  _nsecs += value;
439  }
440 }
441 
442 double DateTime::get(DateSystem system, Timescale scale) const {
443  _assertValid();
444  switch (system) {
445  case MJD:
446  return _getMjd(scale);
447  break;
448  case JD:
449  return _getJd(scale);
450  break;
451  case EPOCH:
452  return _getEpoch(scale);
453  break;
454  default:
455  throw LSST_EXCEPT(pexEx::InvalidParameterError, "DateSystem must be MJD, JD, or EPOCH");
456  break;
457  }
458 }
459 
460 long long DateTime::nsecs(Timescale scale) const {
461  if (!isValid()) {
462  // return the same invalid value for all time scales
464  }
465  return nsecTaiToAny(_nsecs, scale);
466 }
467 
468 double DateTime::_getMjd(Timescale scale) const {
469  _assertValid();
470  double nsecs = nsecTaiToAny(_nsecs, scale);
471  return nsecs / NSEC_PER_DAY + EPOCH_IN_MJD;
472 }
473 
474 double DateTime::_getJd(Timescale scale) const {
475  return _getMjd(scale) + MJD_TO_JD;
476 }
477 
478 double DateTime::_getEpoch(Timescale scale) const {
479  return 2000.0 + (_getJd(scale) - JD2000)/365.25;
480 }
481 
482 struct tm DateTime::gmtime(Timescale scale) const {
483  _assertValid();
484  struct tm gmt;
485  long long nsecs = nsecTaiToAny(_nsecs, scale);
486  // Round to negative infinity
487  long long frac = nsecs % LL_NSEC_PER_SEC;
488  if (nsecs < 0 && frac < 0) {
489  nsecs -= LL_NSEC_PER_SEC + frac;
490  }
491  else {
492  nsecs -= frac;
493  }
494  time_t secs = static_cast<time_t>(nsecs / LL_NSEC_PER_SEC);
495  gmtime_r(&secs, &gmt);
496  return gmt;
497 }
498 
499 struct timespec DateTime::timespec(Timescale scale) const {
500  _assertValid();
501  struct timespec ts;
502  long long nsecs = nsecTaiToAny(_nsecs, scale);
503  ts.tv_sec = static_cast<time_t>(nsecs / LL_NSEC_PER_SEC);
504  ts.tv_nsec = static_cast<int>(nsecs % LL_NSEC_PER_SEC);
505  return ts;
506 }
507 
508 struct timeval DateTime::timeval(Timescale scale) const {
509  _assertValid();
510  struct timeval tv;
511  long long nsecs = nsecTaiToAny(_nsecs, scale);
512  tv.tv_sec = static_cast<time_t>(nsecs / LL_NSEC_PER_SEC);
513  tv.tv_usec = static_cast<int>((nsecs % LL_NSEC_PER_SEC) / 1000);
514  return tv;
515 }
516 
518  _assertValid();
519  struct tm gmt(this->gmtime(scale));
520 
521  long long fracnsecs = nsecTaiToAny(_nsecs, scale) % LL_NSEC_PER_SEC;
522  if (fracnsecs < 0) {
523  fracnsecs += LL_NSEC_PER_SEC;
524  }
525  auto fmtStr = scale == UTC ? "%04d-%02d-%02dT%02d:%02d:%02d.%09dZ"
526  : "%04d-%02d-%02dT%02d:%02d:%02d.%09d";
527  return (boost::format(fmtStr) %
528  (gmt.tm_year + 1900) % (gmt.tm_mon + 1) % gmt.tm_mday %
529  gmt.tm_hour % gmt.tm_min % gmt.tm_sec % fracnsecs).str();
530 }
531 
532 bool DateTime::operator==(DateTime const& rhs) const {
533  return _nsecs == rhs._nsecs;
534 }
535 
537  struct timeval tv;
538  int ret = gettimeofday(&tv, 0);
539  if (ret != 0) {
541  "Unable to get current time");
542  }
543  long long nsecs = tv.tv_sec * LL_NSEC_PER_SEC + tv.tv_usec * 1000LL;
544  return DateTime(nsecs, DateTime::UTC);
545 }
546 
548  Leap l;
549  leapSecTable.clear();
550  boost::regex re("^\\d{4}.*?=JD\\s*([\\d.]+)\\s+TAI-UTC=\\s+([\\d.]+)\\s+S"
551  " \\+ \\(MJD - ([\\d.]+)\\) X ([\\d.]+)\\s*S$");
552  for (boost::cregex_iterator i = make_regex_iterator(leapString.c_str(), re);
553  i != boost::cregex_iterator(); ++i) {
554  double mjdUtc = strtod((*i)[1].first, 0) - MJD_TO_JD;
555  l.offset = strtod((*i)[2].first, 0);
556  l.mjdRef = strtod((*i)[3].first, 0);
557  l.drift = strtod((*i)[4].first, 0);
558  l.whenUtc = static_cast<long long>(
559  (mjdUtc - EPOCH_IN_MJD) * NSEC_PER_DAY);
560  l.whenTai = l.whenUtc + static_cast<long long>(
561  1.0e9 * (l.offset + (mjdUtc - l.mjdRef) * l.drift));
562  leapSecTable.push_back(l);
563  }
564 }
565 
566 }}} // namespace lsst::daf::base
struct timespec timespec(Timescale scale) const
Get date as a timespec struct, with time in seconds and nanoseconds.
Definition: DateTime.cc:499
double get(DateSystem system=MJD, Timescale scale=TAI) const
Get date as a double in a specified representation, such as MJD.
Definition: DateTime.cc:442
Class for handling dates/times, including MJD, UTC, and TAI.
Definition: DateTime.h:62
struct tm gmtime(Timescale scale) const
Get date as a tm struct, with truncated fractional seconds.
Definition: DateTime.cc:482
static void initializeLeapSeconds(std::string const &leapString)
Initialize the leap second table from USNO.
Definition: DateTime.cc:547
std::string toString(Timescale scale) const
Get date as an ISO8601-formatted string.
Definition: DateTime.cc:517
bool operator==(DateTime const &rhs) const
Definition: DateTime.cc:532
struct timeval timeval(Timescale scale) const
Get date as a timeval struct, with time in seconds and microseconds.
Definition: DateTime.cc:508
STL class.
static DateTime now(void)
Return current time as a DateTime.
Definition: DateTime.cc:536
long long nsecs(Timescale scale=TAI) const
Get date as nanoseconds since the unix epoch.
Definition: DateTime.cc:460
T erase(T... args)
T str(T... args)
Interface for DateTime class.
T size(T... args)
#define LSST_EXCEPT(type,...)
STL class.
bool isValid() const
Is this date valid?
Definition: DateTime.h:196
static constexpr long long invalid_nsecs
Definition: DateTime.h:68
DateTime()
Default constructor: construct an invalid DateTime.
Definition: DateTime.cc:320
T c_str(T... args)