Skip to content

Commit

Permalink
major
Browse files Browse the repository at this point in the history
  • Loading branch information
xanthospap committed Oct 22, 2024
1 parent 5fab73c commit a04f127
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 11 deletions.
7 changes: 3 additions & 4 deletions doc/doxy.conf
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT = ../src \
../doc/readme.md
INPUT = ../include ../src/lib ../src/core ../doc

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
Expand All @@ -791,7 +790,7 @@ INPUT_ENCODING = UTF-8
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd,
# *.vhdl, *.ucf, *.qsf, *.as and *.js.

FILE_PATTERNS =
FILE_PATTERNS = *.cpp *.h *.hpp *.md *.txt

# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
Expand Down Expand Up @@ -1440,7 +1439,7 @@ FORMULA_TRANSPARENT = YES
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.

USE_MATHJAX = NO
USE_MATHJAX = YES

# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. See the MathJax site (see:
Expand Down
132 changes: 132 additions & 0 deletions doc/tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# DSO's Datetime Library for Satellite Geodesy
-----------------------------------------------

## Installation

Source code is ISO C++17. Compilation should be trivial using any C++ compiler
supporting the c++17 standard (option `-std=c++17` in [gcc](https://gcc.gnu.org/)
and [clang](https://clang.org/)).

The source code also makes use of several C++20 features, and is compliant with the
C++20 standard, however using this is optional.

Prerequisites:

* A modern C++ compiler,
* [cmake](https://cmake.org/) for building

Installation steps:

* clone the project: `git clone https://github.com/DSOlab/ggdatetime.git`,

* go to the root directory, ``cd ggdatetime`

* use [cmake](https://cmake.org/) to build the library and executables, e.g.
```
$> cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
$> cmake --build build --target all --config Release -- -j4
# optionally run tests
$> ctest --test-dir build
## Install, system-wide (needs root)
$> cd build && sudo make install
```

### Compilation Options

#### `ALLOW_DT_INTEGRAL_MATH`

This option will allow (some) selected operations between DateTime fundamental
types and integral numbers. For example, the following snippet would be valid:
```
Modified_julian_day mjd (123);
mjd += 1;
// Now mjd's internal member, will have a value of 124.
```

#### Build in DEBUG mode

You can easily change to building the DEBUG version, e.g.
```
$> cmake -S . -B build -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug
$> cmake --build build --target all --config Debug -- -j4
```

## Fundamental Date and Time Classes

To preserve type-safety, the library goes on to define a series of classes to
represent dates and time, i.e.

### Time Integral Types

* dso::hours
* dso::minutes
* dso::seconds
* dso::milliseconds, i.e. $$10^{-3} sec$$
* dso::microseconds, i.e. $$10^{-6} sec$$
* dso::nanoseconds, i.e. $$10^{-9} sec$$
* dso::picoseconds, i.e. $$10^{-12} sec$$

### Time Fractional Types

* dso::FractionalSeconds
* dso::FractionalDays
* dso::FractionalYears

### Date Integral Types

* dso::year
* dso::month
* dso::gps_week
* dso::day_of_month
* dso::day_of_year
* dso::modified_julian_day
* dso::ymd_date, i.e. a calendar date
* dso::ydoy_date, i.e. a date represented as Year and Day-of-Year

## Representing Datetime Instances

In order to represent datetime instances, i.e. **epochs**, we have to make a
distinction between **contunuous** and **non-continuous** time scales. For example,
UTC is a non-continuous time scale, since every now and then leap seconds are
introdiced.

### Epochs in Continuous Time-Scales

In continuous time scales (e.g. TT, TAI, etc, ...) an epoch can be represented
in two ways:

* using a dso::TwoPartDate (or its alias name dso::MjdEpoch); this way of representing
an epoch, includes storage of two numerics: an integral part, which is actually the
day as Modified Julian Day and a floating point part which represents the seconds of day.

* using the template class dso::datetime (or its alias name dso::Datetime) which
depends on a template parameter \p T. This parameter denotes the precision in which
the datetime is 'measured' and it can be any of the [Time Integral Types](#time-integral-types)
that are multiples of seconds. E.g., you can have a `dso::datetime<dso::milliseconds> t(...)`.
In this way, it is guaranteed that the specified accuracy will be preserved, since
no floating point math are involved when e.g. adding or subtracting second multiples.
Note however that precision is lost if you add a 'more accurate' subdivision of
seconds to a leeser one, e.g.
```
dso::datetime<dso::milliseconds> t(...);
t.add_seconds(picoseconds(1));
```

### Epochs in Continuous Time-Scales aka UTC

## Leap Seconds

Leap seconds are introduced as necessary to keep UT1-UTC in the $$\pm 0^{s}.9$$.
Because on the average the solar day is now 1-2 ms longer than the nominal
86,400 SI seconds, accumulating to 1s over a period of 18 months to a few years,
leap seconds are in practice always positive ([see SOFA](#sofa_ts)). Each time
a leap second is introduced, the offset $\Delta$AT = TAI - UTC changes by exactly 1s.
Insertion of leap seconds, make UTC a non-continuous time scale, which should be
handled-specially-(see-[Epochs-in-Continuous-Time-Scales-aka-UTC](#epochs-in-continuous-time-scales-aka-utc)).

> [!CAUTION]
> **Latest leap second considered in datetime library occured at: 2017/01/01**
## Bibliography

1. <a id="sofa_ts"></a> International Astronomical Union, Standards of Fundamental Astronomy, SOFA Time Scale and Calendar Tools, Document revision 1.63, 2023
28 changes: 26 additions & 2 deletions include/date_integral_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "core/fundamental_calendar_utils.hpp"
#include "core/fundamental_types_generic_utilities.hpp"
#include <array>

namespace dso {

Expand All @@ -39,6 +40,9 @@ class modified_julian_day;
class ymd_date;
class ydoy_date;

/** @brief Number of leap seconds up to now; latest: 2017/01/01 */
constexpr const int TOTAL_LEAP_SEC_INSERTION_DATES = 28;

/** @brief For a given UTC date, calculate delta(AT) = TAI-UTC.
*
* The day of month is actually not needed, since all leap second insertions
Expand Down Expand Up @@ -854,7 +858,7 @@ class modified_julian_day {
* @see "Remondi Date/Time Algorithms",
* http://www.ngs.noaa.gov/gps-toolbox/bwr-02.htm
*/
modified_julian_day(const ydoy_date &ydoy)
constexpr modified_julian_day(const ydoy_date &ydoy)
: m_mjd(core::ydoy2mjd(ydoy.yr().as_underlying_type(),
ydoy.dy().as_underlying_type())) {};

Expand Down Expand Up @@ -886,7 +890,7 @@ class modified_julian_day {
* @see "Remondi Date/Time Algorithms",
* http://www.ngs.noaa.gov/gps-toolbox/bwr-02.htm
*/
modified_julian_day(const ymd_date &ymd)
constexpr 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())) {};
Expand Down Expand Up @@ -949,6 +953,26 @@ class modified_julian_day {
*/
constexpr ymd_date to_ymd() const noexcept { return core::mjd2ymd(m_mjd); }

/** @brief Check if given MJDay is on a leap insertion day. */
constexpr int is_leap_insertion_day() const noexcept {
constexpr const std::array<int, 28> changes = {
/* MJDs when leap seconds change */
41317 - 1, 41499 - 1, 41683 - 1, 42048 - 1, 42413 - 1, 42778 - 1,
43144 - 1, 43509 - 1, 43874 - 1, 44239 - 1, 44786 - 1, 45151 - 1,
45516 - 1, 46247 - 1, 47161 - 1, 47892 - 1, 48257 - 1, 48804 - 1,
49169 - 1, 49534 - 1, 50083 - 1, 50630 - 1, 51179 - 1, 53736 - 1,
54832 - 1, 56109 - 1, 57204 - 1, 57754 - 1};
static_assert(changes.size() == TOTAL_LEAP_SEC_INSERTION_DATES,
"Invalid number of leap second insertion days!");
for (int i = changes.size() - 1; i >= 0; i--) {
if (m_mjd == changes[i])
return 1;
if (m_mjd > changes[i])
return 0;
}
return 0;
}

private:
/** The modified julian day as underlying type. */
underlying_type m_mjd;
Expand Down
9 changes: 4 additions & 5 deletions include/tpdate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ class TwoPartDateUTC {
* insertation day, return 1, otherwise return 0
*/
constexpr int extra_seconds_in_day() const noexcept {
int extra{0};
dat(modified_julian_day(_mjd), extra);
return extra;
// dat(modified_julian_day(_mjd), extra);
return modified_julian_day(_mjd).is_leap_insertion_day();
}

/** @brief Transform a UTC date to a TAI date.
Expand All @@ -61,7 +60,7 @@ class TwoPartDateUTC {
* 2. The seconds of day part (fractional)
* Note that the fractional seconds part may be over 86400.
*/
constexpr int utc2tai(FDOUBLE &taisec) const noexcept {
int utc2tai(FDOUBLE &taisec) const noexcept {
taisec = _fsec + dat(modified_julian_day(_mjd));
return _mjd;
}
Expand All @@ -73,7 +72,7 @@ class TwoPartDateUTC {
* 2. The seconds of day part (fractional)
* Note that the fractional seconds part may be over 86400.
*/
constexpr int utc2tt(FDOUBLE &taisec) const noexcept {
int utc2tt(FDOUBLE &taisec) const noexcept {
constexpr const FDOUBLE dtat = TT_MINUS_TAI;
taisec = _fsec + (dat(modified_julian_day(_mjd)) + dtat);
return _mjd;
Expand Down
File renamed without changes.
5 changes: 5 additions & 0 deletions src/lib/dat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,25 @@ constexpr const std::array<change, 28> changes = {
{1990, 1, 25}, {1991, 1, 26}, {1992, 7, 27}, {1993, 7, 28}, {1994, 7, 29},
{1996, 1, 30}, {1997, 7, 31}, {1999, 1, 32}, {2006, 1, 33}, {2009, 1, 34},
{2012, 7, 35}, {2015, 7, 36}, {2017, 1, 37}}};
static_assert(changes.size() == dso::TOTAL_LEAP_SEC_INSERTION_DATES,
"Invalid number of leap second insertion days!");
} /* namespace calendar_dat */

namespace mjd_dat {
/** Dates and Delta(AT)s */
struct change {
int mjd, delat;
};
/** @brief MJDs where the leap seconds have changed. */
constexpr const std::array<change, 28> changes = {
{{41317L, 10}, {41499L, 11}, {41683L, 12}, {42048L, 13}, {42413L, 14},
{42778L, 15}, {43144L, 16}, {43509L, 17}, {43874L, 18}, {44239L, 19},
{44786L, 20}, {45151L, 21}, {45516L, 22}, {46247L, 23}, {47161L, 24},
{47892L, 25}, {48257L, 26}, {48804L, 27}, {49169L, 28}, {49534L, 29},
{50083L, 30}, {50630L, 31}, {51179L, 32}, {53736L, 33}, {54832L, 34},
{56109L, 35}, {57204L, 36}, {57754L, 37}}};
static_assert(changes.size() == dso::TOTAL_LEAP_SEC_INSERTION_DATES,
"Invalid number of leap second insertion days!");
} /* namespace mjd_dat */

int dso::dat(const dso::ymd_date &ymd) noexcept {
Expand Down
5 changes: 5 additions & 0 deletions test/unit_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,8 @@ add_internal_includes(test_interval_overlap)
target_link_libraries(test_interval_overlap PRIVATE datetime)
add_test(NAME test_interval_overlap COMMAND test_interval_overlap)

add_executable(leap_insertion_dates_mjd leap_insertion_dates_mjd.cpp)
add_internal_includes(leap_insertion_dates_mjd)
target_link_libraries(leap_insertion_dates_mjd PRIVATE datetime)
add_test(NAME leap_insertion_dates_mjd COMMAND leap_insertion_dates_mjd)

57 changes: 57 additions & 0 deletions test/unit_tests/leap_insertion_dates_mjd.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "calendar.hpp"
#include <random>

using namespace dso;

constexpr const int MIN_MJD = 41317 - 5;
constexpr const int MAX_MJD = 57754 + 5;
constexpr const std::array<ymd_date, 28> changes = {{
ymd_date{year(1972), month(1), day_of_month(1)},
ymd_date{year(1972), month(7), day_of_month(1)},
ymd_date{year(1973), month(1), day_of_month(1)},
ymd_date{year(1974), month(1), day_of_month(1)},
ymd_date{year(1975), month(1), day_of_month(1)},
ymd_date{year(1976), month(1), day_of_month(1)},
ymd_date{year(1977), month(1), day_of_month(1)},
ymd_date{year(1978), month(1), day_of_month(1)},
ymd_date{year(1979), month(1), day_of_month(1)},
ymd_date{year(1980), month(1), day_of_month(1)},
ymd_date{year(1981), month(7), day_of_month(1)},
ymd_date{year(1982), month(7), day_of_month(1)},
ymd_date{year(1983), month(7), day_of_month(1)},
ymd_date{year(1985), month(7), day_of_month(1)},
ymd_date{year(1988), month(1), day_of_month(1)},
ymd_date{year(1990), month(1), day_of_month(1)},
ymd_date{year(1991), month(1), day_of_month(1)},
ymd_date{year(1992), month(7), day_of_month(1)},
ymd_date{year(1993), month(7), day_of_month(1)},
ymd_date{year(1994), month(7), day_of_month(1)},
ymd_date{year(1996), month(1), day_of_month(1)},
ymd_date{year(1997), month(7), day_of_month(1)},
ymd_date{year(1999), month(1), day_of_month(1)},
ymd_date{year(2006), month(1), day_of_month(1)},
ymd_date{year(2009), month(1), day_of_month(1)},
ymd_date{year(2012), month(7), day_of_month(1)},
ymd_date{year(2015), month(7), day_of_month(1)},
ymd_date{year(2017), month(1), day_of_month(1)},
}};

int main() {
for (const auto &d : changes) {
const modified_julian_day mjd(d);
assert(mjd.is_leap_insertion_day() == 0);
const modified_julian_day mjdm1 = mjd - modified_julian_day(1);
assert(mjdm1.is_leap_insertion_day() == 1);
const modified_julian_day mjdm2 = mjd - modified_julian_day(2);
assert(mjdm1.is_leap_insertion_day() == 0);
const modified_julian_day mjdp1 = mjd + modified_julian_day(1);
assert(mjdp1.is_leap_insertion_day() == 0);
}

constexpr const modified_julian_day mjd{std::get<0, ymd_date, 28>(changes)};
static_assert(mjd.is_leap_insertion_day() == 0);
constexpr const modified_julian_day mjdm1{mjd - modified_julian_day(1)};
static_assert(mjdm1.is_leap_insertion_day() == 1);

return 0;
}

0 comments on commit a04f127

Please sign in to comment.