diff --git a/CMakeLists.txt b/CMakeLists.txt index 54dc141..6bdb5d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ target_include_directories(mjd2ymd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) add_executable(mjd2ydoy src/bin/mjd2ydoy.cpp) target_link_libraries(mjd2ydoy PRIVATE datetime) target_include_directories(mjd2ydoy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) +add_executable(integral_seconds_limits src/bin/integral_datetime_limits.cpp) +target_link_libraries(integral_seconds_limits PRIVATE datetime) +target_include_directories(integral_seconds_limits PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) # disable clang-tidy (targets that follow will not be checked) set(CMAKE_CXX_CLANG_TIDY "") diff --git a/include/date_integral_types.hpp b/include/date_integral_types.hpp index c0c59b9..3fb6218 100644 --- a/include/date_integral_types.hpp +++ b/include/date_integral_types.hpp @@ -1,13 +1,31 @@ /** @file + * + * Define date types/classes that are based on integral types (i.e. a year). + * + * The following table lists the classes defined here, along with their main + * (internal) member functions/vars used by generic template function to + * define their behavior. See core/fundamental_types_generic_utilities.hpp + * --------------------+----------------+-------------+--------------+ + * Class Name |is_dt_\ |__member_\ |__member_\ | + * |fundamental_type|const_ref__()|_ref__() | + * --------------------+----------------+-------------+--------------+ + * year | yes | yes | yes | + * month | yes | yes | yes | + * gps_week | yes | yes | yes | + * day_of_month | yes | yes | yes | + * day_of_year | yes | yes | yes | + * ymd_date | no | no | no | + * ydoy_date | no | no | no | + * modified_julian_day | yes | yes | yes | + * --------------------+----------------+-------------+--------------+ * */ #ifndef __DSO_DATE_INTEGRAL_TYPES_HPP__ #define __DSO_DATE_INTEGRAL_TYPES_HPP__ -#include "core/fundamental_types_generic_utilities.hpp" #include "core/fundamental_calendar_utils.hpp" -#include +#include "core/fundamental_types_generic_utilities.hpp" namespace dso { @@ -95,23 +113,17 @@ int dat(modified_julian_day mjd, int &extra_sec_in_day) noexcept; * A year is represented by just an integer number. There are no limits * (excpept from integer overflow) to the range of the year. * - * A year is not an integer; hence, operations with integral values (aka - * addition, subtraction, etc) are not allowed (they will actually triger a - * compilation error). The only thing that is allowed, is assigning from - * integral types. - * @code - * year yr {2019}; - * yr = year + 1; // error! - * yr = 2018; // ok - * @endcode + * A year is not an integer; hence, operations with integral values are not + * allowed (they will actually triger a compilation error). * * This is a fundamental class, which means it only has one arithmetic member * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * The same goes for operators '++' (post- pre-increment) and '--' (post- - * pre-decrement), '+=T' and '-=T' where T is either a year or any integral - * type. + * pre-decrement), '+=T' and '-=T' where T is year. + * + * @see core/fundamental_types_generic_utilities.hpp */ class year { public: @@ -140,11 +152,13 @@ class year { constexpr underlying_type &__member_ref__() noexcept { return m_year; } /** Constructor; default year is 1900. + * * Note that the constrcutor is NOT explicit to allow construction from * int (aka to allow lines of codes of type: year y = 1901;) */ constexpr year(underlying_type i = 1900) noexcept : m_year(i) {} +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * * @tparam I any integral type, aka any type with std::is_integral_v is @@ -161,6 +175,7 @@ class year { __member_ref__() = static_cast(_intv); return *this; } +#endif /** Get the year as year::underlying_type. */ constexpr underlying_type as_underlying_type() const noexcept { @@ -203,8 +218,9 @@ class year { * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * The same goes for operators '++' (post- pre-increment) and '--' (post- - * pre-decrement), '+=T' and '-=T' where T is either a year or any integral - * type. + * pre-decrement), '+=T' and '-=T' where T is month. + * + * @see core/fundamental_types_generic_utilities.hpp * * @warning Most functions (within dso) accept months in the range [1,12]; * do not use the range [0,11], except if you realy know what you're @@ -234,6 +250,7 @@ class month { * * This is an explicit constructor, we do not want users to be able to do * month m = 1; + * * @warning No check is performed by default for the input value \p i , so * you can practically assign month=123. If you want a validity check, use * the month::is_valid function (after construction). @@ -241,6 +258,7 @@ class month { explicit constexpr month(underlying_type i = 1) noexcept : m_month(i) {}; /** @brief Constructor from a c-string. + * * Given a c-string (i.e. null-terminating char array), resolve the month. * The c-string can be either a short name (i.e. a 3-character name), e.g. * "Jan", or the whole, normal month name e.g. "January". @@ -249,14 +267,15 @@ class month { * than 3-chars, the month::long_names array is used. * The function is case-insensitive, i.e. "January", "JANUARY" and "JanUAry" * are all considered the same. - * If the input string cannot be matced to any of the strings in short_names - * and long_names, then an exception is thrown of type: std::invalid_argument - * Note that the month will be returned in the "normal" range [1,12], - * **not** [0-11]. + * If the input string cannot be matched to any of the strings in + * short_names or long_names, then an exception is thrown of type: + * std::invalid_argument. Note that the month will be returned in the + * "normal" range [1,12], **not** [0-11]. * * @param[in] str The month's name; The string should match a month in the * month::short_names or month::long_names array. The string should be * null-trerminated. + * * @throw An std::invalid_argument exception is thrown if a) no * match is found, or b) the input string is too short. */ @@ -284,6 +303,7 @@ class month { * The function will first perform a validity check on the instance (i.e. * make sure the month is within [1,12]; if the instance is invalid, it will * throw. + * * @return Returns a pointer to the class's (static member) month::long_names * string array. */ @@ -319,18 +339,21 @@ class month { * of 05 January 1980 / morning of 06 January 1980. Since that time, the * count has been incremented by 1 each week, and broadcast as part of the GPS * message. + * * A gps week is represented by just an integer number. There are no limits - * (excpept from integer overflow) to the range of the month (integer), i.e. - * the week is not checked (by default) to be in any range. So, a user - * can construct a gps_week from whatever integer. + * (excpept from integer overflow) to the range of gps_week, i.e. the week is + * not checked (by default) to be in any range. So, a user can construct a + * gps_week from whatever integer. You can use the is_valid member function, + * but this will only check it the GPS Week is > 0. * * This is a fundamental class, which means it only has one arithmetic member * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * The same goes for operators '++' (post- pre-increment) and '--' (post- - * pre-decrement), '+=T' and '-=T' where T is either a year or any integral - * type. + * pre-decrement), '+=T' and '-=T' where T is gps_weeek. + * + * @see core/fundamental_types_generic_utilities.hpp type. */ class gps_week { public: @@ -358,6 +381,7 @@ class gps_week { */ explicit constexpr gps_week(underlying_type i = 1) noexcept : m_week(i) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -373,6 +397,7 @@ class gps_week { __member_ref__() = static_cast(_intv); return *this; } +#endif /** Get the month as month::underlying_type */ constexpr underlying_type as_underlying_type() const noexcept { @@ -380,7 +405,7 @@ class gps_week { } /** Check if the instance is valid, aka if the week is the range [0,+Inf) */ - constexpr bool is_valid() const noexcept { return m_week >= 0; } + constexpr bool is_valid() const noexcept { return m_week > 0; } private: /** The month as underlying_type. */ @@ -393,6 +418,13 @@ class gps_week { * are set though, so the user can construct a day_of_month from whatever * integer. Inluding negative numbers! * + * A day_of_month is just a plain integer, and there is no limimtation/check + * on its values. E.g., client code can be: + * day_of_month day(-1312); + * which is perfectly valid! If you want to check whether a given isntance is + * indded valid, you case use the is_valid() function, which expects (also) + * the month and year, to fully perform the check. + * * This is a fundamental class, which means it only has one arithmetic member * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template @@ -400,6 +432,8 @@ class gps_week { * The same goes for operators '++' (post- pre-increment) and '--' (post- * pre-decrement), '+=T' and '-=T' where T is either a year or any integral * type. + * + * @see core/fundamental_types_generic_utilities.hpp */ class day_of_month { public: @@ -430,6 +464,7 @@ class day_of_month { */ explicit constexpr day_of_month(underlying_type i = 1) noexcept : m_dom(i) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -445,6 +480,7 @@ class day_of_month { __member_ref__() = static_cast(_intv); return *this; } +#endif /** Get the day_of_month as day_of_month::underlying_type */ constexpr underlying_type as_underlying_type() const noexcept { @@ -465,10 +501,8 @@ class day_of_month { constexpr bool is_valid(dso::year y, dso::month m) const noexcept { if (m_dom < 1 || m_dom >= 32 || (!m.is_valid())) return false; - /* If February in a leap year, 1, otherwise 0 */ int ly = ((m.as_underlying_type() == 2) && y.is_leap()); - /* Validate day, taking into account leap years */ return (m_dom <= dso::core::mtab[m.as_underlying_type() - 1] + ly); } @@ -490,6 +524,8 @@ class day_of_month { * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. + * + * @see core/fundamental_types_generic_utilities.hpp */ class day_of_year { public: @@ -518,6 +554,7 @@ class day_of_year { * */ explicit constexpr day_of_year(underlying_type i = 0) noexcept : m_doy(i) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -533,6 +570,7 @@ class day_of_year { m_doy = i; return *this; } +#endif /** Cast to underlying type **/ constexpr underlying_type as_underlying_type() const noexcept { @@ -562,7 +600,8 @@ class day_of_year { */ class ymd_date { public: - /** @brief ymd_date constructor + /** @brief ymd_date constructor. + * * No check for validity will be performed. If you want to check the * validity of the created instance, use ymd_date::is_valid */ @@ -570,9 +609,16 @@ class ymd_date { day_of_month d = day_of_month{}) noexcept : __year(y), __month(m), __dom(d) {} + /** @brief Constructor from a year, day of year. + * + * This constrcutor will first check to see if the input parameter is valid + * (via ydoy_data::isvalid()) and then constrcuct the corresponding date as + * ymd_date instance. + */ ymd_date(const ydoy_date &ydoy); - /** @brief Check if the date is a valid calendar date + /** @brief Check if the date is a valid calendar date. + * * @return True if the date is valid, false otherwise. */ constexpr bool is_valid() const noexcept { @@ -589,7 +635,8 @@ class ymd_date { return !(this->operator==(d)); } - /** @brief Transform to year and day-of-year + /** @brief Transform to year and day-of-year. + * * The function will first check that the instance is a valid date, before * performing the transformation (to year and day of year). This is done * because an invalid ymd_date can result in a seamingly valid ydoy_date @@ -622,30 +669,36 @@ class ymd_date { * functions. Users can actually construct any date, even non-valid ones * (e.g. set year to 0). The constructor will not check the input parameters. * If users want to check the instance for validity, then they should use the - * ymd_date::is_valid function. + * ydoy_date::is_valid function. */ class ydoy_date { public: - /** @brief ymd_date constructor + /** @brief ydoy_date constructor. + * * No check for validity will be performed. If you want to check the - * validity of the created instance, use ymd_date::is_valid + * validity of the created instance, use ydoy_date::is_valid. */ constexpr ydoy_date(year y = year{}, day_of_year d = day_of_year{}) noexcept : __year(y), __doy(d) {} - /** @brief Constructor from a Year/Month/DayOfMonth instance - * In case the input argument \p ymd is not a valid date, the constructor + /** @brief Constructor from a Year/Month/DayOfMonth instance. + * + * In case the input argument ymd is not a valid date, the constructor * will throw. */ ydoy_date(const ymd_date &ymd) : __year(ymd.yr()), __doy(ymd.to_ydoy().dy()) {} - /** @brief Check if the date is a valid calendar date + /** @brief Check if the date is a valid calendar date. + * * @return True if the date is valid, false otherwise. */ constexpr bool is_valid() const noexcept { return __doy.is_valid(__year); } - /** @brief Transform to year, month, day-of-month */ + /** @brief Transform to year, month, day-of-month. + * + * No validation test performed on the calling instance. + */ ymd_date to_ymd() const noexcept; /** operator '==' for ydoy_date instances */ @@ -702,13 +755,15 @@ constexpr ymd_date mjd2ymd(long mjd) noexcept { /** @brief A wrapper class for Modified Julian Day (i.e. integral days). * - * A Modified Julian Day is represented by a long integer (there is no + * A Modified Julian Day is represented by an integral number (there is no * fractional part). Thus, a modified_julian_day only represents a date *not* * a datetime. + * * The Modified Julian Day, was introduced by space scientists in the late * 1950's. It is defined as \f$MJD = JD - 2400000.5\f$ The half day (used in * JD) is subtracted so that the day starts at midnight in conformance with * civil time reckoning. + * * The MJD is a convenient dating system with only 5 digits, sufficient for * most modern purposes. To convert between MJD and JD we need the Julian * Date of Modified Julian Date zero, aka MJD0_JD, which is 2400000.5 @@ -718,9 +773,9 @@ constexpr ymd_date mjd2ymd(long mjd) noexcept { * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * The same goes for operators '++' (post- pre-increment) and '--' (post- - * pre-decrement), '+=T' and '-=T' where T is either a year or any integral - * type. + * pre-decrement), '+=T' and '-=T' . * + * @see core/fundamental_types_generic_utilities.hpp * @see http://tycho.usno.navy.mil/mjd.html */ class modified_julian_day { @@ -749,9 +804,9 @@ class modified_julian_day { */ constexpr underlying_type &__member_ref__() noexcept { return m_mjd; } - /** @brief Max possible modified_julian_day + /** @brief Max possible modified_julian_day. * - * Note that we are return the maximum allowed integer here (not + * Note that we are returning the maximum allowed integer here (not * long/unsigned, etc..). This is for easy comparisson (i.e. guarding * against overflow when comparing with ints). */ @@ -759,7 +814,7 @@ class modified_julian_day { return modified_julian_day{std::numeric_limits::max()}; } - /** @brief Min possible modified_julian_day + /** @brief Min possible modified_julian_day. * * Note that we are return the minimum allowed integer here (not * long/unsigned, etc..). This is for easy comparisson (i.e. guarding @@ -769,13 +824,12 @@ class modified_julian_day { return modified_julian_day{std::numeric_limits::min()}; } - /** Constructor; default Modified Julian Day is 1. - * This is a non-explicit constructor, hence we can perform: - * modified_julian_day mjd = 123456; - */ - constexpr modified_julian_day(underlying_type i = 1) noexcept : m_mjd(i) {}; + /** @brief Constructor; default Modified Julian Day is 1. */ + constexpr explicit modified_julian_day(underlying_type i = 1) noexcept + : m_mjd(i) {}; /** @brief Constructor from Year and DayOfYear. + * * The passed in date (year and doy) are tested to see if they represent a * valid date. If not, the constructor will throw! * @@ -788,11 +842,24 @@ class modified_julian_day { constexpr modified_julian_day(year iy, day_of_year id) : m_mjd(core::ydoy2mjd(iy.as_underlying_type(), id.as_underlying_type())) {}; + + /** @brief Constructor from Year and DayOfYear. + * + * The passed in date (year and doy) are tested to see if they represent a + * valid date. If not, the constructor will throw! + * + * @param[in] iy The year. + * @param[in] id The day of year. + * + * @see "Remondi Date/Time Algorithms", + * http://www.ngs.noaa.gov/gps-toolbox/bwr-02.htm + */ modified_julian_day(const ydoy_date &ydoy) : m_mjd(core::ydoy2mjd(ydoy.yr().as_underlying_type(), ydoy.dy().as_underlying_type())) {}; - /** @brief Constructor from calendar date + /** @brief Constructor from calendar date. + * * The passed in date is tested to see if they represent a valid date. If * not, the constructor will throw! * @@ -806,11 +873,25 @@ class modified_julian_day { constexpr modified_julian_day(year y, month m, day_of_month d) : m_mjd(core::cal2mjd(y.as_underlying_type(), m.as_underlying_type(), d.as_underlying_type())) {}; + + /** @brief Constructor from calendar date. + * + * The passed in date is tested to see if they represent a valid date. If + * not, the constructor will throw! + * + * @param[in] y The year. + * @param[in] m The month. + * @param[in] d The day of month + * + * @see "Remondi Date/Time Algorithms", + * http://www.ngs.noaa.gov/gps-toolbox/bwr-02.htm + */ modified_julian_day(const ymd_date &ymd) : m_mjd(core::cal2mjd(ymd.yr().as_underlying_type(), ymd.mn().as_underlying_type(), ymd.dm().as_underlying_type())) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -826,13 +907,15 @@ class modified_julian_day { __member_ref__() = static_cast(_intv); return *this; } +#endif /** Get the modified_julian_day as modified_julian_day::underlying_type */ constexpr underlying_type as_underlying_type() const noexcept { return m_mjd; } - /** Transform to Julian Day + /** @brief Transform to Julian Day. + * * The Julian Day is returned as a double; computed by the formula: * MJD = JD − 2400000.5 * see https://en.wikipedia.org/wiki/Julian_day diff --git a/include/datetime_interval.hpp b/include/datetime_interval.hpp new file mode 100644 index 0000000..cdd38fd --- /dev/null +++ b/include/datetime_interval.hpp @@ -0,0 +1,291 @@ +/** @file + * + * Define a datetime_interval type. This corresponds, physically, to a time + * duration, measured in a given precision (e.g. seconds, milliseconds, etc). + * This class will assist the datetime class; for example, the difference of + * two datetime's will be a datetime_interval. + */ + +#ifndef __DSO_DATETIME_INTEGRAL_INTERVAL__HPP__ +#define __DSO_DATETIME_INTEGRAL_INTERVAL__HPP__ + +#include "date_integral_types.hpp" +#include "hms_time.hpp" +#ifdef DEBUG +#include +#endif + +namespace dso { + +namespace core { +/** @brief Return +1 or -1 depending on the sign of the input (arithmetic) + * parameter. + * + * @param[in] value Any arithmetic value + * @return +1 if the passed in value is >=0; -1 otherwise + */ +template ::value>> +constexpr int sgn(T val) noexcept { + return (T(0) <= val) - (val < T(0)); +} + +/** @brief Specialization of core::sgn for fundamental datetime types. */ +#if __cplusplus >= 202002L +template +#else +template , + typename = std::enable_if_t::value>> +#endif +constexpr int sgn(DType val) noexcept { + return (DType(0) <= val) - (val < DType(0)); +} + +/** @brief A copysign implementation for *seconds. + * + * Returns the magnitude of + * */ +#if __cplusplus >= 202002L +template + requires integral +#else +template , + typename = std::enable_if_t::value>, + typename = std::enable_if_t>> +#endif +constexpr DType copysign(DType val, I isgn) noexcept { + return DType{((val >= DType(0)) ? (val.as_underlying_type()) + : (val.as_underlying_type() * -1)) * + sgn(isgn)}; +} + +/** @brief A copysign implementation for integral types. */ +#if __cplusplus >= 202002L +template + requires integral && integral +#else +template >, + typename = std::enable_if_t>> +#endif +constexpr Iv copysign(Iv val, Is isgn) noexcept { + return ((val >= 0) ? (val) : (val * -1)) * sgn(isgn); +} + +} /* namespace core*/ + +/** @brief A generic, templatized class to hold a datetime period/interval. + * + * A datetime_interval represents a time (datetime) interval or period, i.e. + * 5 days, 12 hours and 49 seconds. We assume a continuous time scale, no leap + * seconds are taken into consideration --this is only an interval not an + * actual datetime instance--. + * + * A datetime_interval instance can only have positive (or zero) values (for + * both of its members). However, seperate field is stored (i.e. \p m_sign) to + * hold 'sign' information, so that a datetime_interval instance can be easily + * used with a datetime instance. That means that e.g. 'adding' a negative + * interval, will extend the datetime in the past. + * + * A datetime_interval instance has two fundamental parts (members): + * - a day part (i.e. holding the integral days), and + * - a time part (i.e. holding any type S of *second type) + * - a sign (i.e. an integer, we only consider its sign here, not the value) + * + * The purpose of this class is to work together with the datetime and + * datetimeUTC classes. + * + * @tparam S Any class of 'second type', i.e. any class S that has a (static) + * member variable S::is_of_sec_type set to true. This can be + * dso::seconds, dso::milliseconds, dso::microseconds. + */ +#if __cplusplus >= 202002L +template +#else +template > +#endif +class datetime_interval { + /* the integral type of (whole) days */ + typedef typename S::underlying_type SecIntType; + /* the integral type of S (*seconds) */ + typedef modified_julian_day::underlying_type DaysIntType; + +private: + /** number of whole days in interval */ + DaysIntType m_days; + /** number of *sec in interval */ + S m_secs; + /** sign of interval (only care for the sign of m_sign) */ + int m_sign; + +public: + /** @brief Default constructor (everything set to 0). */ + explicit constexpr datetime_interval() noexcept : m_days(0), m_secs(0) {}; + + /** @brief Constructor from number of days and number of *seconds. + * + * The sign of the interval is extracted from \p days. The number of days + * and number of *seconds of the interval, are considered positive (i.e. + * absolute values of input parameters). + * For example to construct an interval of 2days + 2sec, use: + * datetime_interval interval(2, seconds(2)); + * If you want to mark this interval as 'negative' (normally for algebraic + * operations with a datetime isntance), use: + * datetime_interval interval(-2, seconds(2)); + * If you want to mark a 'negative' interval but the number of days is zero, + * then you should use: + * datetime_interval interval(0, seconds(-2)); + * or + * datetime_interval interval(seconds(-2)); + * and **NOT** + * datetime_interval interval(-0, seconds(2)); + * since there is no 'signed 0' value. + * + * @warning It is not possible to differentiate between -0 and +0. Hence, + * to construct an interval of 0 days and 1 secs (S), use: + * datetime_interval(S(-1)) and **NOT** + * datetime_interval(-0, S(1)) + */ + constexpr datetime_interval(DaysIntType days, S secs) noexcept + : m_days(days), m_secs(secs), m_sign(core::sgn(days)) { + normalize(); + }; + + /** @brief Constructor from number of *seconds. + * + * The sign of the interval is taken from \p secs. + */ + constexpr datetime_interval(S secs) noexcept + : m_days(0), m_secs(secs), m_sign(1) { + normalize(); + }; + + /** @brief Normalize, i.e. split to integral days and *seconds of day. */ + constexpr void normalize() noexcept { + /* number of whole days in seconds */ + const DaysIntType more = + core::copysign(m_secs.as_underlying_type(), 1) / S::max_in_day; + /* leftover seconds (positive) */ + const SecIntType s = + core::copysign(m_secs.as_underlying_type(), 1) - more * S::max_in_day; + /* add to current days, with the right sign */ + const DaysIntType days = core::copysign(m_days, m_sign) + + core::copysign(more, m_secs.as_underlying_type()); + /* member vars */ + m_sign = + (days > 0) * 1 + (days < 0) * (-1) + (days == 0) * core::sgn(m_secs); + m_secs = S(s); + m_days = core::copysign(days, 1); +#ifdef DEBUG + assert(m_days >= 0 && (m_secs >= 0 && m_secs < S::max_in_day)); +#endif + } + + /** @brief Return number of days in interval, always positive. */ + constexpr DaysIntType days() const noexcept { return m_days; } + + /** @brief Return number of *secs (of day) in interval, always positive. */ + constexpr S sec() const noexcept { return m_secs; } + + /** @brief Return number of *secs (of day) in interval, signed. */ + constexpr S signed_sec() const noexcept { + return core::copysign(m_secs, m_sign); + } + + /** @brief Return the sign of the interval. */ + constexpr int sign() const noexcept { return m_sign; } + + /** @brief Return the interval as days + *seconds in seconds type S, + * ignoring sign (i.e. always positive). + */ + constexpr S unsigned_total_sec() const noexcept { + return m_secs + S(S::max_in_day * m_days); + } + + /** @brief Return the interval as days+ *seconds in seconds type S, + * using a negative value if the instance is marked as 'negative'. + */ + constexpr S signed_total_sec() const noexcept { + return core::copysign(unsigned_total_sec(), m_sign); + } + + /** @brief Cast the interval to a signed floating point representation, i.e. + * FractionalSeconds, FractionalDays or FractionalYears. + */ + template + typename DateTimeDifferenceTypeTraits
::dif_type + to_fraction() const noexcept { + /* the return type */ + using RT = typename DateTimeDifferenceTypeTraits
::dif_type; + + if constexpr (DT == DateTimeDifferenceType::FractionalSeconds) { + /* difference in fractional seconds */ + const double big = static_cast(seconds::max_in_day * m_days); + return RT(m_sign * (big + to_fractional_seconds(m_secs))); + } else if constexpr (DT == DateTimeDifferenceType::FractionalDays) { + /* difference in fractional days */ + const double big = static_cast(m_days); + return RT(m_sign * (big + to_fractional_days(m_secs))); + } else { + /* difference in fractional years */ + const double big = static_cast(m_days); + return RT(m_sign * (big + to_fractional_days(m_secs)) / + DAYS_IN_JULIAN_YEAR); + } + } + + /** @brief Overload equality operator. + * @warning Comparing only the 'absolute value' of the interval, the sign + * is not considered. + */ + constexpr bool operator==(const datetime_interval &d) const noexcept { + return m_days == d.m_days && m_secs == d.m_secs; + } + + /** @brief Overload in-equality operator. + * @warning Comparing only the 'absolute value' of the interval, the sign + * is not considered. + */ + constexpr bool operator!=(const datetime_interval &d) const noexcept { + return !(this->operator==(d)); + } + + /** @brief Overload ">" operator. + * @warning Comparing only the 'absolute value' of the interval, the sign + * is not considered. + */ + constexpr bool operator>(const datetime_interval &d) const noexcept { + return m_days > d.m_days || (m_days == d.m_days && m_secs > d.m_secs); + } + + /** @brief Overload ">=" operator. + * @warning Comparing only the 'absolute value' of the interval, the sign + * is not considered. + */ + constexpr bool operator>=(const datetime_interval &d) const noexcept { + return m_days > d.m_days || (m_days == d.m_days && m_secs >= d.m_secs); + } + + /** @brief Overload "<" operator. + * @warning Comparing only the 'absolute value' of the interval, the sign + * is not considered. + */ + constexpr bool operator<(const datetime_interval &d) const noexcept { + return m_days < d.m_days || (m_days == d.m_days && m_secs < d.m_secs); + } + + /** @brief Overload "<=" operator. + * @warning Comparing only the 'absolute value' of the interval, the sign + * is not considered. + */ + constexpr bool operator<=(const datetime_interval &d) const noexcept { + return m_days < d.m_days || (m_days == d.m_days && m_secs <= d.m_secs); + } +}; /* class datetime_interval */ + +} /* namespace dso */ + +#endif diff --git a/include/datetime_read.hpp b/include/datetime_read.hpp index 1e74643..47badb6 100644 --- a/include/datetime_read.hpp +++ b/include/datetime_read.hpp @@ -6,7 +6,7 @@ #ifndef __DSO_DATETIME_IO_READ_HPP__ #define __DSO_DATETIME_IO_READ_HPP__ -#include "datetime_io_core.hpp" +#include "core/datetime_io_core.hpp" #include "dtdatetime.hpp" #include "hms_time.hpp" #include "tpdate.hpp" diff --git a/include/datetime_write.hpp b/include/datetime_write.hpp index 109fd2a..38f2b10 100644 --- a/include/datetime_write.hpp +++ b/include/datetime_write.hpp @@ -6,9 +6,8 @@ #ifndef __DSO_DATETIME_IO_WRITE_HPP__ #define __DSO_DATETIME_IO_WRITE_HPP__ -#include "datetime_io_core.hpp" +#include "core/datetime_io_core.hpp" #include "dtdatetime.hpp" -#include "hms_time.hpp" #include "tpdate.hpp" #include #include diff --git a/include/dtdatetime.hpp b/include/dtdatetime.hpp index a9c9c0b..7ecab7a 100644 --- a/include/dtdatetime.hpp +++ b/include/dtdatetime.hpp @@ -8,8 +8,10 @@ #ifndef __DTCALENDAR_NGPT__HPP__ #define __DTCALENDAR_NGPT__HPP__ -#include "hms_time.hpp" #include "date_integral_types.hpp" +#include "time_integral_types.hpp" +#include "hms_time.hpp" +#include "datetime_interval.hpp" #include #include #include @@ -17,211 +19,6 @@ namespace dso { -namespace core { -/** Return +1 or -1 depending on the sign of the input (arithmetic) parameter - * - * @param[in] value Any arithmetic value - * @return +1 if the passed in value is >=0; -1 otherwise - */ -template ::value>> -int sgn(T val) noexcept { - return (T(0) <= val) - (val < T(0)); -} - -/** Specialization of core::sgn for fundamental datetime types */ -#if __cplusplus >= 202002L -template -#else -template , - typename = std::enable_if_t::value>> -#endif -int sgn(DType val) noexcept { - return (DType(0) <= val) - (val < DType(0)); -} - -} /* namespace core*/ - -/** @brief A generic, templatized class to hold a datetime period/interval. - * - * A datetime_interval represents a time (datetime) interval or period, i.e. - * 5 days, 12 hours and 49 seconds. We assume a continuous time scale, no leap - * seconds are taken into consideration --this is only an interval not an - * actual datetime instance--. - * - * A datetime_interval instance can only have positive (or zero) values (for - * both of its members). However, seperate field is stored (i.e. \p m_sign) to - * hold 'sign' information, so that a datetime_interval instance can be easily - * used with a datetime instance. That means that e.g. 'adding' a negative - * interval, will extend the datetime in the past. - * - * A datetime_interval instance has two fundamental parts (members): - * - a day part (i.e. holding the integral days), and - * - a time part (i.e. holding any type S of *second type) - * - a sign (i.e. an integer, we only consider its sign here, not the value) - * - * The purpose of this class is to work together with the datetime class. - * - * @tparam S Any class of 'second type', i.e. any class S that has a (static) - * member variable S::is_of_sec_type set to true. This can be - * dso::seconds, dso::milliseconds, dso::microseconds. - */ -#if __cplusplus >= 202002L -template -#else -template > -#endif -class datetime_interval { - typedef typename S::underlying_type SecIntType; - typedef modified_julian_day::underlying_type DaysIntType; - -private: - /** number of whole days in interval */ - DaysIntType m_days; - /** number of *sec in interval */ - SecIntType m_secs; - /** sign of interval (only care for the sign of m_sign) */ - int m_sign; - -public: - /** Default constructor (everything set to 0). */ - explicit constexpr datetime_interval() noexcept : m_days(0), m_secs(0) {}; - - /** Constructor from number of days and number of *seconds. - * - * The sign of the interval is extracted from \p days. The number of days - * and number of *seconds of the interval, are considered positive (i.e. - * absolute values of input parameters). - * For example to construct an interval of 2days + 2sec, use: - * datetime_interval interval(2, seconds(2)); - * If you want to mark this interval as 'negative' (normally for algebraic - * operations with a datetime isntance), use: - * datetime_interval interval(-2, seconds(2)); - * If you want to mark a 'negative' interval but the number of days is zero, - * then you should use: - * datetime_interval interval(0, seconds(-2)); - * or - * datetime_interval interval(seconds(-2)); - * and **NOT** - * datetime_interval interval(-0, seconds(2)); - * since there is no 'signed 0' value. - * - * @warning It is not possible to differentiate between -0 and +0. Hence, - * to construct an interval of 0 days and 1 secs (S), use: - * datetime_interval(S(-1)) and **NOT** - * datetime_interval(-0, S(1)) - */ - constexpr datetime_interval(DaysIntType days, S secs) noexcept - : m_days(days), m_secs(secs.as_underlying_type()), - m_sign(core::sgn(days)) { - normalize(); - }; - - /** Constructor from number of *seconds. The sign of the interval is taken - * from \p secs. The number of *seconds of the interval is considered - * positive (i.e. absolute values of input parameter). - */ - constexpr datetime_interval(S secs) noexcept - : m_days(0), m_secs(secs.as_underlying_type()), m_sign(1) { - normalize(); - }; - - void normalize() noexcept { - /* number of whole days in seconds */ - const DaysIntType more = std::copysign(m_secs, 1) / S::max_in_day; - /* leftover seconds (positive) */ - const SecIntType s = std::copysign(m_secs, 1) - more * S::max_in_day; - /* add to current days, with the right sign */ - const DaysIntType days = - std::copysign(m_days, m_sign) + std::copysign(more, m_secs); - /* member vars */ - m_sign = - (days > 0) * 1 + (days < 0) * (-1) + (days == 0) * core::sgn(m_secs); - m_secs = s; - m_days = std::copysign(days, 1); -#ifdef DEBUG - assert(m_days >= 0 && (m_secs >= 0 && m_secs < S::max_in_day)); -#endif - } - - /** return number of days in interval, always positive */ - constexpr DaysIntType days() const noexcept { return m_days; } - - /** return number of *secs in interval, always positive */ - constexpr S sec() const noexcept { return S(m_secs); } - - /** return number of *secs in interval, signed (not including whole days) */ - S signed_sec() const noexcept { return S(std::copysign(m_secs, m_sign)); } - - /** return the sign of the interval */ - constexpr int sign() const noexcept { return m_sign; } - - /** Return the interval (days+*seconds) in seconds type S, ignoring sign */ - constexpr S unsigned_total_sec() const noexcept { - return S(m_secs + S::max_in_day * m_days); - } - - /** Return the interval (days+*seconds) in seconds type S, using a negative - * value if the instance is marked as 'negative' - */ - constexpr S signed_total_sec() const noexcept { - return S(std::copysign(unsigned_total_sec().as_underlying_type(), m_sign)); - } - - template - typename DateTimeDifferenceTypeTraits
::dif_type - to_fraction() const noexcept { - /* the return type */ - using RT = typename DateTimeDifferenceTypeTraits
::dif_type; - - if constexpr (DT == DateTimeDifferenceType::FractionalSeconds) { - /* difference in fractional seconds */ - const double big = static_cast(seconds::max_in_day * m_days); - return RT(m_sign * (big + to_fractional_seconds(S(m_secs)).seconds())); - } else if constexpr (DT == DateTimeDifferenceType::FractionalDays) { - /* difference in fractional days */ - const double big = static_cast(m_days); - return RT(m_sign * (big + to_fractional_days(S(m_secs)).days())); - } else { - /* difference in fractional years */ - const double big = static_cast(m_days); - return RT(m_sign * (big + to_fractional_days(S(m_secs)).days()) / - DAYS_IN_JULIAN_YEAR); - } - } - - /** Overload equality operator. */ - constexpr bool operator==(const datetime_interval &d) const noexcept { - return m_days == d.m_days && m_secs == d.m_secs; - } - - /** Overload in-equality operator. */ - constexpr bool operator!=(const datetime_interval &d) const noexcept { - return !(this->operator==(d)); - } - - /** Overload ">" operator. */ - constexpr bool operator>(const datetime_interval &d) const noexcept { - return m_days > d.m_days || (m_days == d.m_days && m_secs > d.m_secs); - } - - /** Overload ">=" operator. */ - constexpr bool operator>=(const datetime_interval &d) const noexcept { - return m_days > d.m_days || (m_days == d.m_days && m_secs >= d.m_secs); - } - - /** Overload "<" operator. */ - constexpr bool operator<(const datetime_interval &d) const noexcept { - return m_days < d.m_days || (m_days == d.m_days && m_secs < d.m_secs); - } - - /** Overload "<=" operator. */ - constexpr bool operator<=(const datetime_interval &d) const noexcept { - return m_days < d.m_days || (m_days == d.m_days && m_secs <= d.m_secs); - } -}; /* end class datetime_interval */ - /** @brief A generic, templatized Date/Time class. * * A datetime instance has two fundamental parts (members): @@ -234,6 +31,7 @@ class datetime_interval { * dso::milliseconds, or dso::microseconds). Every method in the class will * (including constructors) will take provisions such that the *seconds are * in fact *seconds of day (i.e. do not surpass one day). + * * Never use negative times; they actually have no physical meaning. Besides * that, they can cause UB. * @@ -262,11 +60,13 @@ template > #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. + /** 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 + constexpr datetime(modified_julian_day mjd, S sec, + [[maybe_unused]] char c) noexcept : m_mjd(mjd), m_sec(sec) {}; public: @@ -279,31 +79,37 @@ class datetime { * possible. */ constexpr static datetime max() noexcept { - return datetime(modified_julian_day::max(), S(0)); + return datetime(modified_julian_day::max(), S(0), 'c'); } /** Minimum possible date (seconds are 0, modified_julian_day is min * possible. */ constexpr static datetime min() noexcept { - return datetime(modified_julian_day::min(), S(0)); + return datetime(modified_julian_day::min(), S(0), 'c'); } /** Reference epoch (J2000.0), as a Modified Julian Date. */ constexpr static datetime j2000_mjd() noexcept { - return datetime(modified_julian_day(51544), S(S::max_in_day / 2L)); + return datetime(modified_julian_day(51544), S(S::max_in_day / 2L), 'c'); } /** Default constructor. */ explicit constexpr datetime() noexcept : m_mjd(dso::J2000_MJD), m_sec(0) {}; + /** @brief Julian centuries since J2000. + * + * Note that a Julian Century has 365.25 days. + */ double jcenturies_sinceJ2000() const noexcept { const double d_mjd = (double)(m_mjd.as_underlying_type()); const double fdays = fractional_days(m_sec); return ((d_mjd - J2000_MJD) + fdays) / DAYS_IN_JULIAN_CENT; } - /** Constructor from year, month, day of month and sec type. + /** @brief Constructor from calendar date. + * + * Constructor from year, month, day of month and sec type. * If an invalid date is passed-in, the constructor will throw. */ constexpr datetime(year y, month m, day_of_month d, S s) @@ -311,8 +117,10 @@ class datetime { this->normalize(); }; - /** Constructor from year, month, day of month and fractional seconds. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from calendar date and time. + * + * Constructor from year, month, day of month and fractional seconds. + * If an invalid date is passed-in, the constructor will throw. */ constexpr datetime(year y, month m, day_of_month d, hours hr, minutes mn, double fsecs) @@ -320,18 +128,21 @@ class datetime { this->normalize(); } - /** Constructor from year, day of year and fractional seconds. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from year, day of year and time. + * + * Constructor from year, day of year and fractional seconds. + * If an invalid date is passed-in, the constructor will throw. */ constexpr datetime(year y, day_of_year d, hours hr, minutes mn, double fsecs) : m_mjd(y, d), m_sec(hr, mn, fsecs) { this->normalize(); } - /** Constructor from year, month, day of month, hours, minutes and second - * type S. + /** @brief Constructor from calendar date and time. * - * If an invalid date is passed-in, the constructor will throw. + * Constructor from year, month, day of month, hours, minutes and second + * type S. + * If an invalid date is passed-in, the constructor will throw. */ constexpr datetime(year y, month m, day_of_month d, hours hr = hours(0), minutes mn = minutes(0), S sec = S(0)) @@ -339,15 +150,20 @@ class datetime { this->normalize(); } - /** Constructor from ymd_date and hms_time. No validation performed. */ + /** @brief Constructor from calendar date and time. + * + * Constructor from ymd_date and hms_time. No validation performed. + */ datetime(const ymd_date &ymd, const hms_time &hms) noexcept : m_mjd(ymd.yr(), ymd.mn(), ymd.dm()), m_sec(hms.hr(), hms.mn(), hms.nsec()) { this->normalize(); } - /** Constructor from year, day of year, hours, minutes and second type S. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from year, day of year and time. + * + * Constructor from year, day of year, hours, minutes and second type S. + * If an invalid date is passed-in, the constructor will throw. */ datetime(year y, day_of_year d, hours hr = hours(0), minutes mn = minutes(0), S sec = S(0)) @@ -355,37 +171,40 @@ class datetime { this->normalize(); } - /** Constructor from year, day of year, and second type S. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from year, day of year and time. + * + * Constructor from year, day of year, and second type S. + * If an invalid date is passed-in, the constructor will throw. */ datetime(year y, day_of_year d, S sec) : m_mjd(y, d), m_sec(sec) { this->normalize(); } - /** Constructor from modified julian day, hours, minutes and second type S. - * If an invalid date is passed-in, the constructor will throw. - */ + /** @brief Constructor from MJD and time. */ constexpr datetime(modified_julian_day mjd, hours hr, minutes mn, S sec) noexcept : m_mjd(mjd), m_sec(hr, mn, sec) { this->normalize(); } - /** Constructor from modified julian day, and second type S. */ + /** @brief Constructor from modified julian day, and second type S. */ constexpr datetime(modified_julian_day mjd, S sec = S(0)) noexcept : m_mjd(mjd), m_sec(sec) { 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 + /** @brief Non-normalizing constrcutor; be careful! + * + * Constructor from modified julian day, and second type S. This version + * will **NOT** call normalize, hence be very very carefull when using it. + * The sec part, should be positive and less than *sec of day (for type S). */ 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 */ + /** @brief Constructor from GPS Week and Seconds of Week */ constexpr datetime(gps_week w, S sow) noexcept : m_mjd(w.as_underlying_type() * 7 + sow.as_underlying_type() / S::max_in_day + JAN61980), @@ -393,22 +212,22 @@ class datetime { m_sec.remove_days(); } - /** Get the Modified Julian Day (const). */ + /** @brief Get the Modified Julian Day (const). */ constexpr modified_julian_day imjd() const noexcept { return m_mjd; } - /** Get the number of *seconds (as type S) of the instance. - * @return The number of *seconds (as type S) of the instance. - */ + /** @brief Get the number of *seconds (as type S) of the instance. */ constexpr S sec() const noexcept { return m_sec; } - /** Seconds in day as fractional days */ + /** @brief Seconds in day as fractional days */ constexpr FractionalDays fractional_days() const noexcept { return dso::to_fractional_days(m_sec); } - /** Operator '+' where the right-hand-side is an interval. - * Note that the addition here is algebraic, i.e. the interval is added to - * or subtracted from the instance, depending on its sign. + /** @brief Add an interval to a datetime. + * + * Operator '+' where the right-hand-side is an interval. Note that the + * addition here is algebraic, i.e. the interval is added to or subtracted + * from the instance, depending on its sign. * Hence, an interval of e.g. (- 2 days, 30 sec) will actually be subtracted * from the instance, not added to it. */ @@ -420,8 +239,9 @@ class datetime { return datetime(mjd, sec); } - /** Add a time interval to a datetime instance. - * Note that the addition here is algebraic, i.e. the interval is added to + /** @brief Add an interval to a datetime. + * + * Note that the aidition here is algebraic, i.e. the interval is added to * or subtracted from the instance, depending on its sign. * Hence, an interval of e.g. (- 2 days, 30 sec) will actually be subtracted * from the instance, not added to it. @@ -432,14 +252,14 @@ class datetime { this->normalize(); } - /** Operator '-' between two instances, produces a (signed) interval */ + /** @brief Operator '-' between two instances, produces a (signed) interval */ constexpr datetime_interval - operator-(const datetime &dt) const noexcept { + operator-(const datetime &other) const noexcept { /* big date at d1, small at d2 */ auto d1 = *this; - auto d2 = dt; + auto d2 = other; int sgn = 1; - if (*this < dt) { + if (*this < other) { d1 = d2; d2 = *this; sgn = -1; @@ -452,13 +272,13 @@ class datetime { secs = secs + S::max_in_day * (secs < 0); DaysIntType days = d1.imjd().as_underlying_type() - d2.imjd().as_underlying_type(); - /* note that id days are 0 and the sign is negative, it must be applied to + /* note that if days are 0 and the sign is negative, it must be applied to * the seconds part */ return datetime_interval(days * sgn, S(std::copysign(secs, (days == 0) * sgn))); } - /** Cast to any datetime instance, regardless of what T is + /** @brief Cast to any datetime instance, regardless of what T is. * * @tparam T A 'second type' * @return The calling object as an instance of type datetime @@ -472,32 +292,32 @@ class datetime { return datetime(m_mjd, dso::cast_to(m_sec)); } - /** Overload equality operator. */ + /** @brief Overload equality operator. */ constexpr bool operator==(const datetime &d) const noexcept { return m_mjd == d.m_mjd && m_sec == d.m_sec; } - /** Overload in-equality operator. */ + /** @brief Overload in-equality operator. */ constexpr bool operator!=(const datetime &d) const noexcept { return !(this->operator==(d)); } - /** Overload ">" operator. */ + /** @brief Overload ">" operator. */ constexpr bool operator>(const datetime &d) const noexcept { return m_mjd > d.m_mjd || (m_mjd == d.m_mjd && m_sec > d.m_sec); } - /** Overload ">=" operator. */ + /** @brief Overload ">=" operator. */ constexpr bool operator>=(const datetime &d) const noexcept { return m_mjd > d.m_mjd || (m_mjd == d.m_mjd && m_sec >= d.m_sec); } - /** Overload "<" operator. */ + /** @brief Overload "<" operator. */ constexpr bool operator<(const datetime &d) const noexcept { return m_mjd < d.m_mjd || (m_mjd == d.m_mjd && m_sec < d.m_sec); } - /** Overload "<=" operator. */ + /** @brief Overload "<=" operator. */ constexpr bool operator<=(const datetime &d) const noexcept { return m_mjd < d.m_mjd || (m_mjd == d.m_mjd && m_sec <= d.m_sec); } @@ -526,7 +346,8 @@ class datetime { #endif } - /** Get the difference between two datetime instances + /** @brief Get the difference between two datetime instances as some kind of + * floating type time. * * The difference can be obtained as a fractional days or fractional seconds, * depending on the template parameter \p DT. @@ -553,10 +374,16 @@ class datetime { return m_sec.fractional_days() + jd; } - /** @brief Cast to year, month, day of month */ + /** @brief Cast to year, month, day of month + * + * Only the date part is considered. Time (of day) is ignored. + */ constexpr ymd_date as_ymd() const noexcept { return m_mjd.to_ymd(); } - /** @brief Cast to year, day_of_year */ + /** @brief Cast to year, day_of_year. + * + * Only the date part is considered. Time (of day) is ignored. + */ constexpr ydoy_date as_ydoy() const noexcept { return m_mjd.to_ydoy(); } /** @brief Convert to Julian Epoch */ @@ -573,6 +400,7 @@ class datetime { return w; } + /** @brief Add seconds of any type (T). */ #if __cplusplus >= 202002L template #else @@ -589,7 +417,7 @@ class datetime { * The two time scales are connected by the formula: * \f$ TT = TAI + ΔT \$ where \f$ ΔT = TT - TAI = 32.184 [sec] \f$ */ - constexpr datetime tai2tt() const noexcept { + [[nodiscard]] constexpr datetime tai2tt() const noexcept { constexpr const S dtat = dso::cast_to(nanoseconds(TT_MINUS_TAI_IN_NANOSEC)); return datetime(m_mjd, m_sec + dtat); @@ -600,7 +428,7 @@ class datetime { * The two time scales are connected by the formula: * \f$ TT = TAI + ΔT \$ where \f$ ΔT = TT - TAI = 32.184 [sec] \f$ */ - constexpr datetime tt2tai() const noexcept { + [[nodiscard]] constexpr datetime tt2tai() const noexcept { constexpr const SecIntType dtat = static_cast( TT_MINUS_TAI * S::template sec_factor()); return datetime(m_mjd, m_sec - dtat); @@ -676,6 +504,33 @@ class datetime { S m_sec; /** Time of day in S precision. */ }; /* datetime */ +/** @brief A generic, templatized Date/Time class to represent UTC dates. + * + * A datetimeUTC instance has two fundamental parts (members): + * - a date part (i.e. holding the year/month/day), and + * - a time part (i.e. holding hours/minutes/ *seconds) + * + * A datetime holds both a date and a time (i.e. of day). The date is stored + * as a Modified Julian Day (i.e. dso::modified_julian_day). The time of day + * can be stored via any class of 'second type', i.e. dso::seconds, + * dso::milliseconds, or dso::microseconds). Every method in the class will + * (including constructors) will take provisions such that the *seconds are + * in fact *seconds of day (i.e. do not surpass one day). + * + * Never use negative times; they actually have no physical meaning. Besides + * that, they can cause UB. + * + * @tparam S Any class of 'second type', i.e. any class S that has a (static) + * member variable S::is_of_sec_type set to true. This can be + * dso::seconds, dso::milliseconds, dso::microseconds. + * + * @note Constructors can be called with the time part being more than one day; + * (e.g. + * \code{.cpp} + * datetime d {year(2016), month(12), day_of_month(15), + * seconds(86401};) + * \endcode + */ #if __cplusplus >= 202002L template #else @@ -688,25 +543,27 @@ class datetimeUtc { /** Expose the underlying modified_julian_day int */ typedef modified_julian_day::underlying_type DaysIntType; - /** Maximum possible date (seconds are 0, modified_julian_day is max + /** @brief Maximum possible date (seconds are 0, modified_julian_day is max * possible. */ constexpr static datetimeUtc max() noexcept { return datetimeUtc(modified_julian_day::max(), S(0)); } - /** Minimum possible date (seconds are 0, modified_julian_day is min + /** @brief Minimum possible date (seconds are 0, modified_julian_day is min * possible. */ constexpr static datetimeUtc min() noexcept { return datetimeUtc(modified_julian_day::min(), S(0)); } - /** Default constructor. */ + /** @brief Default constructor. */ explicit constexpr datetimeUtc() noexcept : m_mjd(dso::J2000_MJD), m_sec(0) {}; - /** Constructor from year, month, day of month and sec type. + /** @brief Constructor from calendar date. + * + * Constructor from year, month, day of month and sec type. * If an invalid date is passed-in, the constructor will throw. */ constexpr datetimeUtc(year y, month m, day_of_month d, S s) @@ -714,7 +571,9 @@ class datetimeUtc { this->normalize(); }; - /** Constructor from year, month, day of month and fractional seconds. + /** @brief Constructor from calendar date. + * + * Constructor from year, month, day of month and fractional seconds. * 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, @@ -723,8 +582,10 @@ class datetimeUtc { this->normalize(); } - /** Constructor from year, day of year and fractional seconds. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from year, day of year. + * + * 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) @@ -732,10 +593,11 @@ class datetimeUtc { this->normalize(); } - /** Constructor from year, month, day of month, hours, minutes and second - * type S. + /** @brief Constructor from calndar date. * - * If an invalid date is passed-in, the constructor will throw. + * Constructor from year, month, day of month, hours, minutes and second + * type S. + * 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)) @@ -743,15 +605,20 @@ class datetimeUtc { this->normalize(); } - /** Constructor from ymd_date and hms_time. No validation performed. */ + /** @brief Constructor from calendar date. + * + * Constructor from ymd_date and hms_time. No validation performed. + */ datetimeUtc(const ymd_date &ymd, const hms_time &hms) noexcept : m_mjd(ymd.yr(), ymd.mn(), ymd.dm()), m_sec(hms.hr(), hms.mn(), hms.nsec()) { this->normalize(); } - /** Constructor from year, day of year, hours, minutes and second type S. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from year, day of year. + * + * 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)) @@ -759,15 +626,18 @@ class datetimeUtc { this->normalize(); } - /** Constructor from year, day of year, and second type S. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from year, day of year. + * + * Constructor from year, day of year, and second type S. + * If an invalid date is passed-in, the constructor will throw. */ datetimeUtc(year y, day_of_year d, S sec) : m_mjd(y, d), m_sec(sec) { this->normalize(); } - /** Constructor from modified julian day, hours, minutes and second type S. - * If an invalid date is passed-in, the constructor will throw. + /** @brief Constructor from MJD and time of day. + * + * Constructor from modified julian day, hours, minutes and second type S. */ constexpr datetimeUtc(modified_julian_day mjd, hours hr, minutes mn, S sec) noexcept @@ -775,18 +645,19 @@ class datetimeUtc { this->normalize(); } - /** Constructor from modified julian day, and second type S. */ + /** @brief Constructor from MJD and time of day. + * + * Constructor from modified julian day, and second type S. + */ constexpr datetimeUtc(modified_julian_day mjd, S sec = S(0)) noexcept : m_mjd(mjd), m_sec(sec) { this->normalize(); } - /** Get the Modified Julian Day (const). */ + /** @brief Get the Modified Julian Day (const). */ constexpr modified_julian_day imjd() const noexcept { return m_mjd; } - /** Get the number of *seconds (as type S) of the instance. - * @return The number of *seconds (as type S) of the instance. - */ + /** @brief Get the number of *seconds (as type S) of the instance. */ constexpr S sec() const noexcept { return m_sec; } /** Cast to any datetime instance, regardless of what T is @@ -803,32 +674,32 @@ class datetimeUtc { return datetimeUtc(m_mjd, dso::cast_to(m_sec)); } - /** Overload equality operator. */ + /** @brief Overload equality operator. */ constexpr bool operator==(const datetimeUtc &d) const noexcept { return m_mjd == d.m_mjd && m_sec == d.m_sec; } - /** Overload in-equality operator. */ + /** @brief Overload in-equality operator. */ constexpr bool operator!=(const datetimeUtc &d) const noexcept { return !(this->operator==(d)); } - /** Overload ">" operator. */ + /** @brief Overload ">" operator. */ constexpr bool operator>(const datetimeUtc &d) const noexcept { return m_mjd > d.m_mjd || (m_mjd == d.m_mjd && m_sec > d.m_sec); } - /** Overload ">=" operator. */ + /** @brief Overload ">=" operator. */ constexpr bool operator>=(const datetimeUtc &d) const noexcept { return m_mjd > d.m_mjd || (m_mjd == d.m_mjd && m_sec >= d.m_sec); } - /** Overload "<" operator. */ + /** @brief Overload "<" operator. */ constexpr bool operator<(const datetimeUtc &d) const noexcept { return m_mjd < d.m_mjd || (m_mjd == d.m_mjd && m_sec < d.m_sec); } - /** Overload "<=" operator. */ + /** @brief Overload "<=" operator. */ constexpr bool operator<=(const datetimeUtc &d) const noexcept { return m_mjd < d.m_mjd || (m_mjd == d.m_mjd && m_sec <= d.m_sec); } @@ -839,7 +710,12 @@ class datetimeUtc { /** @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 */ + /** @brief Overload '-' operator taking into account leap seconds. + * + * Operator '-' between two instances, produces a (signed) interval, i.e. + * the time lapsed between the two dates. Note that since the two epochs are + * in UTC, leap seconds must be taken into account. + */ constexpr datetime_interval operator-(const datetimeUtc &dt) const noexcept { /* big date at d1, small at d2 */ @@ -869,7 +745,7 @@ class datetimeUtc { days * sgn, S(std::copysign(secs + ddat, (days == 0) * sgn))); } - /** @brief Normalize a datetime instance. + /** @brief Normalize a datetimeUTC instance. * * Split the date and time parts such that the time part is always less * than one day (i.e. make it time-of-day) and positive (i.e.>=0). @@ -892,6 +768,7 @@ class datetimeUtc { } } + /** @brief Add seconds of any type (T). */ #if __cplusplus >= 202002L template #else diff --git a/include/fractional_types.hpp b/include/fractional_types.hpp index b4109c5..1e96b4c 100644 --- a/include/fractional_types.hpp +++ b/include/fractional_types.hpp @@ -1,6 +1,6 @@ /** @file * - * Fundamental (core) datetime types that are represented by floating point + * Fundamental (core) datetime types that are represented by floating point * arithmetic. * * These are merely there to enforce type safety. @@ -25,7 +25,8 @@ class FractionalSeconds { } constexpr underlying_type &__member_ref__() noexcept { return fsec; } - constexpr explicit FractionalSeconds(double _fsec = 0e0) noexcept : fsec(_fsec) {}; + constexpr explicit FractionalSeconds(double _fsec = 0e0) noexcept + : fsec(_fsec) {}; constexpr double seconds() const noexcept { return fsec; } constexpr double &seconds() noexcept { return fsec; } }; /* FractionalSeconds */ @@ -42,7 +43,8 @@ class FractionalDays { } constexpr underlying_type &__member_ref__() noexcept { return fdays; } - constexpr explicit FractionalDays(double _fdays = 0e0) noexcept : fdays(_fdays) {}; + constexpr explicit FractionalDays(double _fdays = 0e0) noexcept + : fdays(_fdays) {}; constexpr double days() const noexcept { return fdays; } constexpr double &days() noexcept { return fdays; } }; /* FractionalDays */ diff --git a/include/hms_time.hpp b/include/hms_time.hpp index c55b8b6..92b857a 100644 --- a/include/hms_time.hpp +++ b/include/hms_time.hpp @@ -108,7 +108,7 @@ class hms_time { 3600L * S::template sec_factor(); /* compute/remove hours */ const SecIntType hr = isecs / secInHour; - _hours = hr; + _hours = hours(hr); SecIntType remaining = isecs - _hours.as_underlying_type() * secInHour; #ifdef DEBUG assert(remaining < secInHour); @@ -118,13 +118,13 @@ class hms_time { 60L * S::template sec_factor(); /* compute/remove minutes */ const SecIntType mn = remaining / secInMin; - _minutes = mn; + _minutes = minutes(mn); remaining = remaining - _minutes.as_underlying_type() * secInMin; #ifdef DEBUG assert(remaining < secInMin); #endif /* remaining S seconds */ - _sec = remaining; + _sec = S(remaining); #ifdef DEBUG assert(_sec.as_underlying_type() + secInMin * _minutes.as_underlying_type() + diff --git a/include/time_int2flt.hpp b/include/time_int2flt.hpp index 5e44197..6476f20 100644 --- a/include/time_int2flt.hpp +++ b/include/time_int2flt.hpp @@ -33,8 +33,7 @@ template template > #endif FractionalSeconds to_fractional_seconds(S nsec) noexcept { - const double sec = - nsec.S::template cast_to() * S::sec_inv_factor(); + const double sec = nsec.S::template cast_to() * S::sec_inv_factor(); return FractionalSeconds(sec); } diff --git a/include/time_integral_types.hpp b/include/time_integral_types.hpp index 6132f0a..924329d 100644 --- a/include/time_integral_types.hpp +++ b/include/time_integral_types.hpp @@ -1,12 +1,19 @@ /** @file * + * The following table lists the classes defined here, along with their main + * (internal) member functions/vars used by generic template function to + * define their behavior. See core/fundamental_types_generic_utilities.hpp + * --------------------+----------------+-------------+--------------+ + * Class Name |is_dt_\ |__member_\ |__member_\ | + * |fundamental_type|const_ref__()|_ref__() | + * --------------------+----------------+-------------+--------------+ + * --------------------+----------------+-------------+--------------+ */ #ifndef __DSO_TIME_INTEGRAL_TYPES_HPP__ #define __DSO_TIME_INTEGRAL_TYPES_HPP__ #include "core/fundamental_types_generic_utilities.hpp" -#include #include #include @@ -32,13 +39,13 @@ class picoseconds; * hours -- normally hour of day, although no such restriction exists --. * In case a subdivision is needed (e.g. minutes, seconds, etc), then use * the corresponsing classes (ndso::minutes, dso::seconds, etc...). - * If the code is compiled with the switch USE_DATETIME_CHECKS, then the - * hours (constructor) can only have zero or positive values. * * This is a fundamental class, which means it only has one arithmetic member * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. + * + * @see core/fundamental_types_generic_utilities.hpp */ class hours { public: @@ -63,6 +70,7 @@ class hours { /** Constructor; default hours is 0, but any hour will do */ explicit constexpr hours(underlying_type i = 0) noexcept : m_hours(i) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -78,6 +86,7 @@ class hours { m_hours = i; return *this; } +#endif /** Get the hours as hours::underlying_type */ constexpr underlying_type as_underlying_type() const noexcept { @@ -101,13 +110,13 @@ class hours { * minutes -- normally min of hours, although no such restriction exists --. * In case a subdivision is needed (e.g. seconds, milliseconds etc), then use * the corresponsing classes (dso::seconds, dso::milliseconds, etc...). - * If the code is compiled with the switch USE_DATETIME_CHECKS, then the - * minutes (constructor) can only have zero or positive values. * * This is a fundamental class, which means it only has one arithmetic member * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template - * function overloadnig outside the class. + * function overloading outside the class. + * + * @see core/fundamental_types_generic_utilities.hpp */ class minutes { public: @@ -132,6 +141,7 @@ class minutes { /** Constructor; any integral number will do */ explicit constexpr minutes(underlying_type i = 0) noexcept : m_min(i) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -147,6 +157,7 @@ class minutes { m_min = i; return *this; } +#endif /** Get the minutes as minutes::underlying_type */ constexpr underlying_type as_underlying_type() const noexcept { @@ -160,11 +171,12 @@ class minutes { /** @brief A wrapper class for seconds. * - * seconds is just a wrapper class around long integer numbers, i.e. a second - * is just a long int and can be either positive or negative. Users are however + * seconds is just a wrapper class around integer numbers, i.e. a second is + * just a long int and can be either positive or negative. Users are however * restricted by integer overflow. The maximum number of days that can be * expressed in seconds without fear of overflow is given by the template * function dso::max_days_allowed. + * * Negative seconds are allowed (so that a user can perform basic operations * like e.g. addition), but some functions expect only positive seconds * (seconds::remove_days, seconds::to_days). @@ -178,6 +190,8 @@ class minutes { * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * + * @see core/fundamental_types_generic_utilities.hpp + * * @warning The maximum number of days that can be expressed in seconds without * fear of overflow is given by the template function * dso::max_days_allowed @@ -231,6 +245,7 @@ class seconds { static_cast(m.as_underlying_type()) * 60L + static_cast(h.as_underlying_type()) * 3600L} {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -246,6 +261,7 @@ class seconds { m_sec = i; return *this; } +#endif /** Get the seconds as seconds::underlying_type */ constexpr underlying_type as_underlying_type() const noexcept { @@ -283,14 +299,13 @@ class seconds { * dso::seconds, dso::microseconds, etc); quite a few methods should be * common to all of these classes, all of which have a member variable * milliseconds::is_of_sec_type which is set to true. - * If the code is compiled with the switch USE_DATETIME_CHECKS, then the - * milliseconds (constructor) can only have zero or positive values. * * This is a fundamental class, which means it only has one arithmetic member * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * + * @see core/fundamental_types_generic_utilities.hpp * @see dso::seconds * @see dso::microseconds */ @@ -347,6 +362,7 @@ class milliseconds { static_cast(h.as_underlying_type()) * 60L) * sec_factor() * 60L) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -362,6 +378,7 @@ class milliseconds { m_sec = i; return *this; } +#endif /** Get the milliseconds as milliseconds::underlying_type. */ constexpr underlying_type as_underlying_type() const noexcept { @@ -406,18 +423,13 @@ class milliseconds { * dso::seconds, dso::milliseconds, etc); quite a few methods should be * common to all of these classes, all of which have a member variable * microseconds::is_of_sec_type which is set to true. - * If the code is compiled with the switch USE_DATETIME_CHECKS, then the - * microseconds (constructor) can only have zero or positive values. * * This is a fundamental class, which means it only has one arithmetic member * variable. The classe's bollean operators (aka '==', '!=', '<', '<=', '>', * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * - * @note microseconds can be cast to dso::seconds and dso::milliseconds (via - * a static_cast or a C-type cast) but the opposite is not true; i.e. - * dso::seconds cannot be cast to microseconds.This is still an open question! - * + * @see core/fundamental_types_generic_utilities.hpp * @see dso::seconds * @see dso::milliseconds */ @@ -474,6 +486,7 @@ class microseconds { static_cast(h.as_underlying_type()) * 60L) * sec_factor() * 60L) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -489,6 +502,7 @@ class microseconds { m_sec = i; return *this; } +#endif /** Cast to microseconds::underlying_type. */ constexpr underlying_type as_underlying_type() const noexcept { @@ -534,11 +548,7 @@ class microseconds { * '>=') are going to be implemented using kinda reflection, using template * function overloadnig outside the class. * - * @note nanoseconds can be cast to dso::seconds, dso::milliseconds and - * dso::microseconds (via a static_cast or a C-type cast) but the opposite is - * not true; i.e. dso::seconds cannot be cast to nanoseconds.This is still - * an open question! - * + * @see core/fundamental_types_generic_utilities.hpp * @see dso::seconds * @see dso::milliseconds */ @@ -594,6 +604,7 @@ class nanoseconds { static_cast(h.as_underlying_type()) * 60L) * sec_factor() * 60L) {}; +#ifdef ALLOW_DT_INTEGRAL_MATH /** Overload operator '=' where the the right-hand-side is any integral type. * @tparam I any integral type, aka any type for which std::is_integral_v * is true @@ -609,6 +620,7 @@ class nanoseconds { m_sec = i; return *this; } +#endif /** Cast to nanoseconds::underlying_type. */ constexpr underlying_type as_underlying_type() const noexcept { @@ -640,9 +652,6 @@ class nanoseconds { }; /* class nanoseconds */ /** @brief A wrapper class for picoseconds (i.e 10**-12 sec.). - * - * Please revert from using this class if possible! It cannot hold negative - * numbers hence, arithmetic operations are dangerous! */ class picoseconds { public: @@ -694,6 +703,18 @@ class picoseconds { return m_sec; } +/** Cast to any arithmetic type. */ +#if __cplusplus >= 202002L + template + requires gconcepts::arithmetic +#else + template ::value>> +#endif + constexpr T cast_to() const noexcept { + return static_cast(m_sec); + } + private: /** Picoseconds as long ints. */ underlying_type m_sec; diff --git a/include/tpdate.hpp b/include/tpdate.hpp index 602b051..b0b10b1 100644 --- a/include/tpdate.hpp +++ b/include/tpdate.hpp @@ -27,7 +27,7 @@ class TwoPartDateUTC { */ int extra_seconds_in_day() const noexcept { int extra; - dat(_mjd, extra); + dat(modified_julian_day(_mjd), extra); return extra; } @@ -56,7 +56,7 @@ class TwoPartDateUTC { return _mjd; } - constexpr explicit TwoPartDateUTC(int mjd, FDOUBLE secday) noexcept + explicit TwoPartDateUTC(int mjd, FDOUBLE secday) noexcept : _mjd(mjd), _fsec(secday) { normalize(); } @@ -216,7 +216,7 @@ class TwoPartDateUTC { void normalize() noexcept { assert(_fsec >= 0e0); int extra_sec_in_day; - dat(_mjd, extra_sec_in_day); + dat(modified_julian_day(_mjd), extra_sec_in_day); /* for each MJD, remove integral days. Each MJD may have a different * number of seconds, since we are in UTC time scale. Hence, iteratively * remove whole days using the number of seconds for each day @@ -224,7 +224,7 @@ class TwoPartDateUTC { while (_fsec >= 86400e0 + extra_sec_in_day) { _fsec -= (86400e0 + extra_sec_in_day); ++_mjd; - dat(_mjd, extra_sec_in_day); + dat(modified_julian_day(_mjd), extra_sec_in_day); } #ifdef DEBUG if (_mjd) @@ -548,7 +548,7 @@ class TwoPartDate { if (utcsec < 0e0) { --utcmjd; int extrasec; - FDOUBLE secinday = SEC_PER_DAY + dat(utcmjd, extrasec); + FDOUBLE secinday = SEC_PER_DAY + dat(modified_julian_day(utcmjd), extrasec); utcsec = secinday + utcsec; } return TwoPartDateUTC(utcmjd, FractionalSeconds{utcsec}); diff --git a/src/bin/integral_datetime_limits.cpp b/src/bin/integral_datetime_limits.cpp new file mode 100644 index 0000000..e30621c --- /dev/null +++ b/src/bin/integral_datetime_limits.cpp @@ -0,0 +1,26 @@ +#include "dtdatetime.hpp" +#include +#include + +using namespace dso; + +int main() { + printf("Maximum number of days representable by each *sec type\n"); + printf("%-13s %17ld, size: %ld\n", seconds::unit_literal(), + std::numeric_limits::max() / + seconds::max_in_day, sizeof(seconds)); + printf("%-13s %17ld, size: %ld\n", milliseconds::unit_literal(), + std::numeric_limits::max() / + milliseconds::max_in_day, sizeof(milliseconds)); + printf("%-13s %17ld, size: %ld\n", microseconds::unit_literal(), + std::numeric_limits::max() / + microseconds::max_in_day, sizeof(microseconds)); + printf("%-13s %17ld, size: %ld\n", nanoseconds::unit_literal(), + std::numeric_limits::max() / + nanoseconds::max_in_day, sizeof(nanoseconds)); + printf("%-13s %17ld, size: %ld\n", picoseconds::unit_literal(), + std::numeric_limits::max() / + picoseconds::max_in_day, sizeof(picoseconds)); + + return 0; +} diff --git a/include/datetime_io_core.hpp b/src/core/datetime_io_core.hpp similarity index 87% rename from include/datetime_io_core.hpp rename to src/core/datetime_io_core.hpp index 60a283d..834fe31 100644 --- a/include/datetime_io_core.hpp +++ b/src/core/datetime_io_core.hpp @@ -1,5 +1,7 @@ /** @file - * Datetime input/output (i.e. read/write) core utilities and definitions. + * + * Utility function to assist datetime IO. They should not be used otside + * this scope; they are taylor-made for specific purposes. */ #ifndef __DSO_DATETIME_IO_CORE_HPP__ diff --git a/src/core/fundamental_calendar_utils.hpp b/src/core/fundamental_calendar_utils.hpp index 6c53418..73cc382 100644 --- a/src/core/fundamental_calendar_utils.hpp +++ b/src/core/fundamental_calendar_utils.hpp @@ -1,13 +1,16 @@ /** @file * - * Fundamental (core) datetime algorithms. - * Functions in here are core and do not include datetime types, hence - * provide no type-safety. They should be used with care and possibly as a - * hidden interface to more type-safe implementations. + * Fundamental (core) datetime algorithms. The functions in here are agnostic + * w.r.t the classes defined by the project (i.e. year, mnoth, etc). They act + * mostly on integral types, hence they should be used with care. + * + * Once the relevant datetime classes are available (in another hpp file), + * they should be wrapped around the "correct" implementation enabling type + * safety. */ -#ifndef __DSO_DT_FUNDAMENTAL_CALENDAR_UTILS_HPP__ -#define __DSO_DT_FUNDAMENTAL_CALENDAR_UTILS_HPP__ +#ifndef __DSO_NONTYPE_CALENDAR_UTILS_HPP__ +#define __DSO_NONTYPE_CALENDAR_UTILS_HPP__ #include #include "cdatetime.hpp" diff --git a/src/lib/datetime_io_core.cpp b/src/lib/datetime_io_core.cpp index cc5ec91..a3cda7e 100644 --- a/src/lib/datetime_io_core.cpp +++ b/src/lib/datetime_io_core.cpp @@ -1,4 +1,4 @@ -#include "datetime_io_core.hpp" +#include "core/datetime_io_core.hpp" #include #include #include diff --git a/test/should_not_compile/fund_type_op_plusequal.cpp b/test/should_not_compile/fund_type_op_plusequal.cpp index 3ebfd73..17d946d 100644 --- a/test/should_not_compile/fund_type_op_plusequal.cpp +++ b/test/should_not_compile/fund_type_op_plusequal.cpp @@ -8,5 +8,4 @@ int main() { mjd += 1; assert(mjd.as_underlying_type() == 124); return 0; - } diff --git a/test/unit_tests/CMakeLists.txt b/test/unit_tests/CMakeLists.txt index 7000213..8b68508 100644 --- a/test/unit_tests/CMakeLists.txt +++ b/test/unit_tests/CMakeLists.txt @@ -171,3 +171,13 @@ add_executable(sectype_casts sectype_casts.cpp) add_internal_includes(sectype_casts) target_link_libraries(sectype_casts PRIVATE datetime) add_test(NAME sectype_casts COMMAND sectype_casts) + +add_executable(datetime_static_ce_constructor datetime_static_ce_constructor.cpp) +add_internal_includes(datetime_static_ce_constructor) +target_link_libraries(datetime_static_ce_constructor PRIVATE datetime) +add_test(NAME datetime_static_ce_constructor COMMAND datetime_static_ce_constructor) + +add_executable(datetime_interval_constructor datetime_interval_constructor.cpp) +add_internal_includes(datetime_interval_constructor) +target_link_libraries(datetime_interval_constructor PRIVATE datetime) +add_test(NAME datetime_interval_constructor COMMAND datetime_interval_constructor) diff --git a/test/unit_tests/datetime_interval_constructor.cpp b/test/unit_tests/datetime_interval_constructor.cpp new file mode 100644 index 0000000..663f50b --- /dev/null +++ b/test/unit_tests/datetime_interval_constructor.cpp @@ -0,0 +1,144 @@ +#include "datetime_interval.hpp" + +using namespace dso; + +int main() { + + /* template parameter is seconds */ + { + constexpr const datetime_interval i1{0, seconds(2) }; + constexpr const datetime_interval i2{-0, seconds(2) }; + constexpr const datetime_interval i3{0, seconds(-2) }; + constexpr const datetime_interval i4{-0, seconds(-2)}; + + static_assert(i1 == i2); + static_assert(i3 == i4); + static_assert(i1.sec() == i2.sec()); + static_assert(i3.sec() == i4.sec()); + + /* sec() return positive seconds */ + static_assert(i1.sec() == i3.sec()); + static_assert(i1.sec().as_underlying_type() == + i3.sec().as_underlying_type()); + + /* signed_sec() return the seconds signed */ + static_assert(i1.signed_sec().as_underlying_type() == + i3.signed_sec().as_underlying_type() * -1); + + /* sign of the interval */ + static_assert(i1.sign() == -1 * i3.sign()); + + /* total seconds, ignoring the sign */ + static_assert(i1.unsigned_total_sec() == i3.unsigned_total_sec()); + + /* total seconds, with the correct sign */ + static_assert(i1.signed_total_sec().as_underlying_type() == + i3.signed_total_sec().as_underlying_type() * -1); + } + + /* template parameter is nanoseconds */ + { + constexpr const datetime_interval i1{0, nanoseconds(2) }; + constexpr const datetime_interval i2{-0, nanoseconds(2) }; + constexpr const datetime_interval i3{0, nanoseconds(-2)}; + constexpr const datetime_interval i4{-0, nanoseconds(-2)}; + + static_assert(i1 == i2); + static_assert(i3 == i4); + static_assert(i1.sec() == i2.sec()); + static_assert(i3.sec() == i4.sec()); + + /* sec() return positive seconds */ + static_assert(i1.sec() == i3.sec()); + static_assert(i1.sec().as_underlying_type() == + i3.sec().as_underlying_type()); + + /* signed_sec() return the seconds signed */ + static_assert(i1.signed_sec().as_underlying_type() == + i3.signed_sec().as_underlying_type() * -1); + + /* sign of the interval */ + static_assert(i1.sign() == -1 * i3.sign()); + + /* total seconds, ignoring the sign */ + static_assert(i1.unsigned_total_sec() == i3.unsigned_total_sec()); + + /* total seconds, with the correct sign */ + static_assert(i1.signed_total_sec().as_underlying_type() == + i3.signed_total_sec().as_underlying_type() * -1); + } + + /* template parameter is nanoseconds */ + { + constexpr const long NS = 86400L * 1'000'000'000 * 3 + 2; + constexpr const datetime_interval i1{3, nanoseconds(2) }; + constexpr const datetime_interval i2{nanoseconds(NS) }; + constexpr const datetime_interval i3{-3, nanoseconds(2)}; + constexpr const datetime_interval i4{nanoseconds(-NS) }; + + static_assert(i1 == i2); + static_assert(i3 == i4); + static_assert(i1.sec() == i2.sec()); + static_assert(i3.sec() == i4.sec()); + + /* sec() return positive seconds */ + static_assert(i1.sec() == i3.sec()); + static_assert(i1.sec().as_underlying_type() == + i3.sec().as_underlying_type()); + + /* signed_sec() return the seconds signed */ + static_assert(i1.signed_sec().as_underlying_type() == + i3.signed_sec().as_underlying_type() * -1); + + /* sign of the interval */ + static_assert(i1.sign() == -1 * i3.sign()); + + /* total seconds, ignoring the sign */ + static_assert(i1.unsigned_total_sec() == i3.unsigned_total_sec()); + + /* total seconds, with the correct sign */ + static_assert(i1.signed_total_sec().as_underlying_type() == + i3.signed_total_sec().as_underlying_type() * -1); + } + + /* template parameter is picoseconds */ + { + constexpr long PS = 86400L * 1'000'000'000'000L * 3 + 2; + constexpr datetime_interval i1{3, picoseconds(2) }; + constexpr datetime_interval i2{picoseconds(PS) }; + constexpr datetime_interval i3{-3, picoseconds(2)}; + constexpr datetime_interval i4{picoseconds(-PS) }; + + //printf("i1: %d days + %ldps = %ld\n", i1.days(), i1.signed_sec().as_underlying_type(), i1.signed_total_sec().as_underlying_type()); + //printf("i2: %d days + %ldps = %ld\n", i2.days(), i2.signed_sec().as_underlying_type(), i2.signed_total_sec().as_underlying_type()); + //printf("i3: %d days + %ldps = %ld\n", i3.days(), i3.signed_sec().as_underlying_type(), i3.signed_total_sec().as_underlying_type()); + //printf("i1: %d days + %ldps = %ld\n", i1.days(), i1.signed_sec().as_underlying_type(), i1.signed_total_sec().as_underlying_type()); + + + static_assert(i1 == i2); + static_assert(i3 == i4); + static_assert(i1.sec() == i2.sec()); + static_assert(i3.sec() == i4.sec()); + + /* sec() return positive seconds */ + static_assert(i1.sec() == i3.sec()); + static_assert(i1.sec().as_underlying_type() == + i3.sec().as_underlying_type()); + + /* signed_sec() return the seconds signed */ + static_assert(i1.signed_sec().as_underlying_type() == + i3.signed_sec().as_underlying_type() * -1); + + /* sign of the interval */ + static_assert(i1.sign() == -1 * i3.sign()); + + /* total seconds, ignoring the sign */ + static_assert(i1.unsigned_total_sec() == i3.unsigned_total_sec()); + + /* total seconds, with the correct sign */ + static_assert(i1.signed_total_sec().as_underlying_type() == + i3.signed_total_sec().as_underlying_type() * -1); + } + + return 0; +} diff --git a/test/unit_tests/datetime_static_ce_constructor.cpp b/test/unit_tests/datetime_static_ce_constructor.cpp new file mode 100644 index 0000000..154a21c --- /dev/null +++ b/test/unit_tests/datetime_static_ce_constructor.cpp @@ -0,0 +1,10 @@ +#include "calendar.hpp" + +int main() { + + constexpr const auto j2000 = dso::datetime::j2000_mjd(); + static_assert(j2000.imjd() == dso::modified_julian_day(51544)); + static_assert(j2000.sec() == dso::seconds(86400/2)); + return 0; + +} diff --git a/test/unit_tests/dom.cpp b/test/unit_tests/dom.cpp index 6998f71..f184b50 100644 --- a/test/unit_tests/dom.cpp +++ b/test/unit_tests/dom.cpp @@ -9,9 +9,8 @@ int main() { day_of_month y1(2023); assert(y1.as_underlying_type() == 2023); - y1 = 2024; - assert(y1.as_underlying_type() == 2024); - + /* not allowed */ + // y1 = 2024; /* not allowed */ // y1 == 2024; /* not allowed */ diff --git a/test/unit_tests/doy.cpp b/test/unit_tests/doy.cpp index 5d8b376..4e5c164 100644 --- a/test/unit_tests/doy.cpp +++ b/test/unit_tests/doy.cpp @@ -9,9 +9,8 @@ int main() { day_of_year y1(2023); assert(y1.as_underlying_type() == 2023); - y1 = 2024; - assert(y1.as_underlying_type() == 2024); - + /* not allowed */ + // y1 = 2024; /* not allowed */ // y1 == 2024; /* not allowed */ diff --git a/test/unit_tests/dread21.cpp b/test/unit_tests/dread21.cpp index 3da163d..95fe0a0 100644 --- a/test/unit_tests/dread21.cpp +++ b/test/unit_tests/dread21.cpp @@ -46,9 +46,9 @@ int main() { char buf1[64], buf2[64]; for (auto const &d : leap_insertion_dates) { - datetime tai(modified_julian_day(d).as_underlying_type()); + datetime tai{modified_julian_day(d)}; tai.add_seconds(seconds(86400 - 1)); - datetimeUtc utc(modified_julian_day(d).as_underlying_type()); + datetimeUtc utc{modified_julian_day(d)}; utc.add_seconds(seconds(86400 - 1)); /* we are now at 23:59:59 */ diff --git a/test/unit_tests/hours.cpp b/test/unit_tests/hours.cpp index a843b58..67cf2aa 100644 --- a/test/unit_tests/hours.cpp +++ b/test/unit_tests/hours.cpp @@ -9,12 +9,9 @@ int main() { hours h1(2023); assert(h1.as_underlying_type() == 2023); - h1 = 2024; - assert(h1.as_underlying_type() == 2024); - /* not allowed */ + // h1 = 2024; // h1 == 2024; - /* not allowed */ // h1 + 2024; assert(h1 >= hours(2024));