Skip to content

Commit

Permalink
datetimeUtc
Browse files Browse the repository at this point in the history
  • Loading branch information
xanthospap committed Dec 31, 2023
1 parent 4205b5a commit 40f94d9
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 37 deletions.
33 changes: 33 additions & 0 deletions src/datetime_read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,39 @@ dso::datetime<S> from_char(const char *str, const char **end = nullptr) {
return datetime<S>(ymd, hms);
}

template <YMDFormat FD, HMSFormat FT, typename S>
dso::datetimeUtc<S> from_utc_char(const char *str, const char **end = nullptr) {
const char *stop;
/* resolve date part */
const ymd_date ymd(ReadInDate<FD>::read(str, &stop));
if (!ymd.is_valid()) {
fprintf(stderr, "[ERROR] Failed to resolved read-in date (traceback: %s)\n",
__func__);
throw std::runtime_error("[ERROR] Failed to resolved read-in date\n");
}
/* resolve time */
str = stop;
const hms_time<S> hms(ReadInTime<S, FT>::read(str, &stop));
if (!hms.is_valid()) {
/* not always an error, if seconds are 60, it could be ok on a leap
* insertion day
*/
int leap;
dat(modified_julian_day(ymd), leap);
if (!hms.is_valid(leap)) {
fprintf(stderr,
"[ERROR] Failed to resolved read-in time (traceback: %s)\n",
__func__);
throw std::runtime_error("[ERROR] Failed to resolved read-in time\n");
}
}
/* set output pointer */
if (end)
*end = stop;
/* compile datetime instance */
return datetimeUtc<S>(ymd, hms);
}

/** Read in a Date and Time of Day string and resolve it to a TwoPartDate
* instance.
*
Expand Down
27 changes: 27 additions & 0 deletions src/datetime_write.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,33 @@ const char *to_char(const datetime<S> &d, char *buffer) {
return buffer;
}

template <YMDFormat FD, HMSFormat FT, typename S>
const char *to_char(const datetimeUtc<S> &d, char *buffer) {
/* write date to buffer */
ymd_date ymd(d.as_ymd());
if (SpitDate<FD>::spit(ymd, buffer) != SpitDate<FD>::numChars) {
throw std::runtime_error("[ERROR] Failed to format date to string\n");
}
/* move pointer to write time */
char *ptr = buffer + SpitDate<FD>::numChars;
*ptr = ' ';
++ptr;
/* write time of day to buffer */
hms_time<S> hms(d.sec());
const nanoseconds ns(cast_to<S, nanoseconds>(d.sec()));
/* in case of UTC, this will be 24h:00m:00s. Re-arange to 23:59:60 */
if (ns == nanoseconds(nanoseconds::max_in_day)) {
hms = hms_time<nanoseconds>(
hours(23), minutes(59),
nanoseconds(60 * nanoseconds::sec_factor<long>()));
}
if (SpitTime<nanoseconds, FT>::spit(hms, ptr) !=
SpitTime<nanoseconds, FT>::numChars) {
throw std::runtime_error("[ERROR] Failed to format time to string\n");
}
return buffer;
}

template <YMDFormat FD, HMSFormat FT>
const char *to_char(const TwoPartDate &d, char *buffer) {
/* write date to buffer */
Expand Down
111 changes: 88 additions & 23 deletions src/dtdatetime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,14 @@ template <gconcepts::is_sec_dt S>
template <class S, typename = std::enable_if_t<S::is_of_sec_type>>
#endif
class datetime {
private:
/** A constructor that will NOT call normalize! use with extra care. The
* char parameter is actually usseless, but is there to make sure that
* the user intents to use this function.
*/
datetime(modified_julian_day mjd, S sec, [[maybe_unused]] char c) noexcept
: m_mjd(mjd), m_sec(sec){};

public:
/** Expose the underlying sec type S */
using SecIntType = typename S::underlying_type;
Expand Down Expand Up @@ -358,8 +366,8 @@ class datetime {
/** Constructor from modified julian day, hours, minutes and second type S.
* If an invalid date is passed-in, the constructor will throw.
*/
constexpr datetime(modified_julian_day mjd, hours hr = hours(0),
minutes mn = minutes(0), S sec = S(0)) noexcept
constexpr datetime(modified_julian_day mjd, hours hr, minutes mn,
S sec) noexcept
: m_mjd(mjd), m_sec(hr, mn, sec) {
this->normalize();
}
Expand All @@ -370,6 +378,14 @@ class datetime {
this->normalize();
}

/** Constructor from modified julian day, and second type S. This version
* will **NOT** call normalize, hence be very very carefull when using it
*/
static constexpr datetime non_normalize_construct(modified_julian_day mjd,
S sec) noexcept {
return datetime(mjd, sec, 'y');
}

/** Constructor from GPS Week and Seconds of Week */
constexpr datetime(gps_week w, S sow) noexcept
: m_mjd(w.as_underlying_type() * 7 +
Expand Down Expand Up @@ -562,7 +578,7 @@ class datetime {
T::template sec_factor<unsigned long>());
return __add_seconds_impl<T>(nsec, std::integral_constant<bool, TT>{});
}

/** @brief Transform instance to TT, assuming it is in TAI
*
* The two time scales are connected by the formula:
Expand All @@ -573,7 +589,7 @@ class datetime {
TT_MINUS_TAI * S::template sec_factor<double>());
return datetime(m_mjd, m_sec + dtat);
}

/** @brief Transform an instance to TAI assuming it is in TT
*
* The two time scales are connected by the formula:
Expand All @@ -585,6 +601,25 @@ class datetime {
return datetime(m_mjd, m_sec - dtat);
}

/** @brief Transform an instance to GPS Time assuming it is in TAI
*
* The two time scales are connected by the formula:
* \f$ TAI = GPSTime + 19 [sec] \f$
*/
constexpr datetime<S> tai2gps() const noexcept {
constexpr const SecIntType dt =
static_cast<SecIntType>(19 * S::template sec_factor<SecIntType>());
return datetime(m_mjd, m_sec - dt);
}

constexpr datetime<S> gps2tai() const noexcept {
constexpr const SecIntType dt =
static_cast<SecIntType>(19 * S::template sec_factor<SecIntType>());
return datetime(m_mjd, m_sec + dt);
}

constexpr datetime<S> gps2tt() const noexcept { return gps2tai().tai2tt(); }

private:
/** @brief Add any second type T where S is of higher resolution than T
*
Expand Down Expand Up @@ -622,14 +657,6 @@ class datetime {
* @warning The input seconds (parameter) is of higher resolution than the
* instance, thus loss of accuracy may happen.
*/
// template <class T>
// constexpr void __add_seconds_impl(T sec, std::false_type) noexcept {
// T sect = dso::cast_to<S, T>(m_sec);
// sect += sec;
// m_sec = dso::cast_to<T, S>(sect);
// this->normalize();
// return;
// }
template <class T>
constexpr void __add_seconds_impl(T sec, std::false_type) noexcept {
m_sec += dso::cast_to<T, S>(sec);
Expand Down Expand Up @@ -682,15 +709,16 @@ class datetimeUtc {
* If an invalid date is passed-in, the constructor will throw.
*/
constexpr datetimeUtc(year y, month m, day_of_month d, hours hr, minutes mn,
double fsecs)
double fsecs)
: m_mjd(y, m, d), m_sec(hr, mn, fsecs) {
this->normalize();
}

/** Constructor from year, day of year and fractional seconds.
* If an invalid date is passed-in, the constructor will throw.
*/
constexpr datetimeUtc(year y, day_of_year d, hours hr, minutes mn, double fsecs)
constexpr datetimeUtc(year y, day_of_year d, hours hr, minutes mn,
double fsecs)
: m_mjd(y, d), m_sec(hr, mn, fsecs) {
this->normalize();
}
Expand All @@ -701,7 +729,7 @@ class datetimeUtc {
* If an invalid date is passed-in, the constructor will throw.
*/
constexpr datetimeUtc(year y, month m, day_of_month d, hours hr = hours(0),
minutes mn = minutes(0), S sec = S(0))
minutes mn = minutes(0), S sec = S(0))
: m_mjd(y, m, d), m_sec(hr, mn, sec) {
this->normalize();
}
Expand All @@ -716,8 +744,8 @@ class datetimeUtc {
/** Constructor from year, day of year, hours, minutes and second type S.
* If an invalid date is passed-in, the constructor will throw.
*/
datetimeUtc(year y, day_of_year d, hours hr = hours(0), minutes mn = minutes(0),
S sec = S(0))
datetimeUtc(year y, day_of_year d, hours hr = hours(0),
minutes mn = minutes(0), S sec = S(0))
: m_mjd(y, d), m_sec(hr, mn, sec) {
this->normalize();
}
Expand All @@ -732,8 +760,8 @@ class datetimeUtc {
/** Constructor from modified julian day, hours, minutes and second type S.
* If an invalid date is passed-in, the constructor will throw.
*/
constexpr datetimeUtc(modified_julian_day mjd, hours hr = hours(0),
minutes mn = minutes(0), S sec = S(0)) noexcept
constexpr datetimeUtc(modified_julian_day mjd, hours hr, minutes mn,
S sec) noexcept
: m_mjd(mjd), m_sec(hr, mn, sec) {
this->normalize();
}
Expand All @@ -751,7 +779,7 @@ class datetimeUtc {
* @return The number of *seconds (as type S) of the instance.
*/
constexpr S sec() const noexcept { return m_sec; }

/** Cast to any datetime<T> instance, regardless of what T is
*
* @tparam T A 'second type'
Expand Down Expand Up @@ -796,6 +824,42 @@ class datetimeUtc {
return m_mjd < d.m_mjd || (m_mjd == d.m_mjd && m_sec <= d.m_sec);
}

/** @brief Cast to year, month, day of month */
constexpr ymd_date as_ymd() const noexcept { return m_mjd.to_ymd(); }

/** @brief Cast to year, day_of_year */
constexpr ydoy_date as_ydoy() const noexcept { return m_mjd.to_ydoy(); }

/** Operator '-' between two instances, produces a (signed) interval */
constexpr datetime_interval<S>
operator-(const datetimeUtc<S> &dt) const noexcept {
/* big date at d1, small at d2 */
auto d1 = *this;
auto d2 = dt;
int sgn = 1;
if (*this < dt) {
d1 = dt;
d2 = *this;
sgn = -1;
}
/* let's see what happens with leap seconds */
int dat1 = dso::dat(d1.imjd());
int dat2 = dso::dat(d2.imjd());
/* seconds, could be negative */
SecIntType secs =
d1.sec().as_underlying_type() - d2.sec().as_underlying_type();
/* branchless TODO which is faster ? */
d1.m_mjd = d1.m_mjd - modified_julian_day(1 * (secs < 0));
secs = secs + S::max_in_day * (secs < 0);
DaysIntType days =
d1.imjd().as_underlying_type() - d2.imjd().as_underlying_type();
SecIntType ddat((dat1 - dat2) * S::template sec_factor<SecIntType>());
/* note that id days are 0 and the sign is negative, it must be applied to
* the seconds part */
return datetime_interval<S>(
days * sgn, S(std::copysign(secs + ddat, (days == 0) * sgn)));
}

/** @brief Normalize a datetime instance.
*
* Split the date and time parts such that the time part is always less
Expand All @@ -810,8 +874,9 @@ class datetimeUtc {
* remove whole days using the number of seconds for each day
*/
S daysec;
while (m_sec >= (daysec = S::max_in_day + S(extra_sec_in_day *
S:: template sec_factor<SecIntType>()))) {
while (m_sec >= (daysec = S(S::max_in_day +
extra_sec_in_day *
S::template sec_factor<SecIntType>()))) {
m_sec -= daysec;
++m_mjd;
dat(m_mjd, extra_sec_in_day);
Expand All @@ -828,7 +893,7 @@ class datetimeUtc {
T::template sec_factor<unsigned long>());
return __add_seconds_impl<T>(nsec, std::integral_constant<bool, TT>{});
}

private:
/** @brief Add any second type T where S is of higher resolution than T
*
Expand Down
1 change: 1 addition & 0 deletions src/dtfund.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ int dat(modified_julian_day mjd, int &extra_sec_in_day) noexcept;
/** A simple struct to signal fractional seconds; just to secure type safety */
struct FractionalSeconds {
double fsec;
FractionalSeconds(double _fsec = 0e0) noexcept : fsec(_fsec){};
}; /* FractionalSeconds */

/** A simple struct to signal fractional days; just to secure type safety */
Expand Down
6 changes: 3 additions & 3 deletions src/hms_time.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ class hms_time {
/** Convert time-of-day to seconds-of-day for any second type (as double)
*/
template <typename Sto, typename = std::enable_if_t<Sto::is_of_sec_type>>
double fractional_seconds() const noexcept {
FractionalSeconds fractional_seconds() const noexcept {
const double scale =
Sto::template sec_factor<double>() / S::template sec_factor<double>();
const SecIntType b =
mn().as_underlying_type() * 60L + hr().as_underlying_type() * 60L * 60L;
return (nsec().as_underlying_type() * scale) +
(b * Sto::template sec_factor<double>());
return FractionalSeconds((nsec().as_underlying_type() * scale) +
(b * Sto::template sec_factor<double>()));
}

/** Constructor from any second type */
Expand Down
13 changes: 13 additions & 0 deletions src/tpdate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,19 @@ class TwoPartDate {
return TwoPartDate(_mjd, _fsec - dtat);
}

/** @brief Transform an instance to GPS Time assuming it is in TAI
*
* The two time scales are connected by the formula:
* \f$ TAI = GPSTime + 19 [sec] \f$
*/
TwoPartDate tai2gps() const noexcept {
return TwoPartDate(_mjd, _fsec - 19e0);
}

TwoPartDate gps2tai() const noexcept {
return TwoPartDate(_mjd, _fsec + 19e0);
}

/** Transform an instance to UTC assuming it is in TAI */
/* taisec = _fsec + dat(modified_julian_day(_mjd)); */
TwoPartDateUTC tai2utc() const noexcept {
Expand Down
Loading

0 comments on commit 40f94d9

Please sign in to comment.