Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
# tempoch-cpp

tempoch-cpp is a C++17 library for astronomical time primitives. It provides a header-only C++ API backed by the Rust `tempoch-ffi` engine (via a C FFI), with typed time scales, UTC conversions, and period operations.
tempoch-cpp is a C++17 library for astronomical time primitives. It provides a header-only C++ API backed by the Rust `tempoch-ffi` engine (via a C FFI), with explicit time scales, explicit encodings, UTC civil conversion, and generic period operations.

[![C++17](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.cppreference.com/w/cpp/17)
[![CMake](https://img.shields.io/badge/CMake-3.15%2B-064F8C.svg)](https://cmake.org/)
[![License](https://img.shields.io/badge/License-See%20tempoch-green.svg)](tempoch/LICENSE)

## Features

- Strongly typed time values such as `JulianDate`, `MJD`, `UTC`, `TT`, `TAI`, `TDB`, `TCG`, `TCB`, `GPS`, `UT`, `JDE`, and `UnixTime`
- Split scale/format model:
`Time<scale::TT>`, `JulianDate<scale::TT>`, `JulianDate<scale::UTC>`,
`ModifiedJulianDate<scale::UT1>`, `UnixTime`, and `GpsTime`
- Civil UTC representation with nanosecond precision via `tempoch::CivilTime`
- Cross-scale conversion helpers such as `from_utc()`, `to_utc()`, `to_jd()`, and `to_mjd()`
- Explicit conversion flow:
`CivilTime -> Time<scale::UTC> -> Time<scale::TT> -> to<format::JD>()`
- `TimeContext` for UT1 and historical UTC routes
- Time arithmetic using `qtty-cpp` duration quantities such as `qtty::Day`, `qtty::Hour`, and `qtty::Minute`
- `UT::delta_t()` with checked UT1 horizon errors from the Rust FFI
- `Period<T>` intervals with typed start/end access, intersection, and duration conversion
- CMake target (`tempoch_cpp`) for straightforward integration

Expand All @@ -33,7 +36,7 @@ cd tempoch-cpp
cmake -S . -B build
cmake --build build --parallel
ctest --test-dir build --output-on-failure
./build/time_example
./build/01_quickstart
```

If you cloned without submodules:
Expand All @@ -49,22 +52,26 @@ git submodule update --init --recursive
#include <qtty/qtty.hpp>
#include <tempoch/tempoch.hpp>

using namespace tempoch;

int main() {
UTC utc(2026, 7, 15, 22, 0, 0);
using namespace tempoch;

CivilTime civil{2026, 7, 15, 22, 0, 0};
auto utc = Time<scale::UTC>::from_civil(civil);
auto tt = utc.to<scale::TT>();

auto jd = JulianDate::from_utc(utc);
auto mjd = MJD::from_jd(jd);
auto jd_tt = tt.to<format::JD>();
auto jd_utc = utc.to<format::JD>();
auto mjd_tt = tt.to<format::MJD>();

std::cout << "UTC: " << utc << "\n";
std::cout << "JD: " << jd << "\n";
std::cout << "MJD: " << mjd << "\n";
std::cout << "Civil UTC : " << civil << "\n";
std::cout << "JD(TT) : " << jd_tt << "\n";
std::cout << "JD(UTC) : " << jd_utc << "\n";
std::cout << "MJD(TT) : " << mjd_tt << "\n";

Period observing_window(MJD(60200.0), MJD(60200.5));
std::cout << "Duration: "
<< observing_window.duration<qtty::Hour>()
<< "\n";
Period<ModifiedJulianDate<scale::TT>> observing_window(
mjd_tt,
mjd_tt + qtty::Hour(12.0));
std::cout << "Duration: " << observing_window.duration<qtty::Hour>() << "\n";
}
```

Expand All @@ -73,18 +80,26 @@ int main() {
Time arithmetic uses `qtty-cpp` quantities. Subtracting two times returns a typed duration, and adding or subtracting a quantity shifts the time value.

```cpp
auto jd0 = JulianDate::J2000();
auto jd0 = tempoch::JulianDate<tempoch::scale::TT>::J2000();
auto jd1 = jd0 + qtty::Hour(12.0);
auto dt = jd1 - jd0;

std::cout << dt.to<qtty::Hour>() << "\n";
```

## Migration Notes

- Old `JulianDate` means `JulianDate<scale::TT>`.
- Old `MJD` means `ModifiedJulianDate<scale::TT>`.
- Old `TT` means `Time<scale::TT>`, not a JD-valued wrapper.
- Old `UTC` civil construction is now `CivilTime` plus `Time<scale::UTC>::from_civil(...)`.
- Legacy names remain available only through opt-in shim headers such as `tempoch/legacy_time.hpp`.

## Documentation

- `docs/mainpage.md` (API overview)
- `examples/time_example.cpp` (basic usage)
- `examples/timescales.cpp` (all supported scale conversions plus `ΔT`)
- `examples/01_quickstart.cpp` (civil UTC -> canonical time -> explicit encodings)
- `examples/02_scales.cpp` (all supported scale conversions)
- `include/tempoch/tempoch.hpp` (umbrella public header)

## Integration
Expand Down
64 changes: 29 additions & 35 deletions docs/mainpage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
`tempoch-cpp` is a **modern, header-only C++17 library** for astronomical
time primitives. It wraps the Rust-based
[`tempoch`](https://github.com/siderust/tempoch) through its C FFI layer
(`tempoch-ffi`), exposing `UTC`, `JulianDate`, `MJD`, and `Period` as
idiomatic C++ value types with arithmetic, comparisons, and exception-based
error reporting — no manual FFI plumbing required.
(`tempoch-ffi`), exposing the same two-axis model as Rust: physical
**scale** and external **format** are separate type parameters.

---

## Features

| Feature | Description |
|---------|-------------|
| **`UTC`** | Civil date-time struct (year/month/day/hour/min/sec) with nanosecond precision |
| **`JulianDate`** | Strongly-typed Julian Date with arithmetic and UTC conversion |
| **`MJD`** | Strongly-typed Modified Julian Date; interchangeable with `JulianDate` |
| **`Period`** | Inclusive `[start, end]` MJD interval with intersection, duration, and iteration helpers |
| **`CivilTime`** | Civil UTC date-time struct (year/month/day/hour/min/sec) with nanosecond precision |
| **`Time<scale::S>`** | Canonical instant on a physical timescale, stored as split J2000 seconds |
| **`EncodedTime<S, F>`** | Typed external encoding such as `JulianDate<scale::TT>` or `ModifiedJulianDate<scale::UTC>` |
| **`TimeContext`** | Explicit context for UT1 and historical UTC routes |
| **`Period<T>`** | Inclusive `[start, end]` interval over any supported time representation |
| **Exception hierarchy** | All FFI status codes map to typed C++ exceptions |
| **Header-only** | Drop into any project — no separate compilation step |

Expand All @@ -26,37 +26,23 @@ error reporting — no manual FFI plumbing required.

```cpp
#include <tempoch/tempoch.hpp>
#include <cstdio>
#include <iostream>

int main() {
using namespace tempoch;

// Construct a UTC civil time
UTC utc{2026, 7, 15, 22, 0, 0};
CivilTime civil{2026, 7, 15, 22, 0, 0};
auto utc = Time<scale::UTC>::from_civil(civil);
auto tt = utc.to<scale::TT>();

// Convert to Julian Date and MJD
JulianDate jd = JulianDate::from_utc(utc);
MJD mjd = MJD::from_jd(jd);
auto jd_tt = tt.to<format::JD>();
auto jd_utc = utc.to<format::JD>();
auto mjd_tt = tt.to<format::MJD>();

std::printf("JD = %.6f\n", jd.value());
std::printf("MJD = %.6f\n", mjd.value());

// Create a one-day window and check containment
MJD start = mjd;
MJD end = mjd + 1.0;
Period night{start, end};

std::printf("Duration: %.2f days\n", night.duration());
std::printf("Contains midpoint: %s\n",
night.contains(mjd + 0.5) ? "yes" : "no");

// Round-trip back to UTC
UTC back = jd.to_utc();
std::printf("UTC: %04d-%02d-%02d %02d:%02d:%02d\n",
back.year, back.month, back.day,
back.hour, back.minute, back.second);

return 0;
std::cout << "Civil UTC : " << civil << "\n";
std::cout << "JD(TT) : " << jd_tt << "\n";
std::cout << "JD(UTC) : " << jd_utc << "\n";
std::cout << "MJD(TT) : " << mjd_tt << "\n";
}
```

Expand All @@ -71,7 +57,7 @@ int main() {
└──────┬───────┘
│ header-only (inline)
┌──────▼───────┐
│ tempoch-cpp │ C++17 value types: UTC, JulianDate, MJD, Period
│ tempoch-cpp │ C++17 value types: CivilTime, Time<S>, EncodedTime<S,F>, Period<T>
│ (headers) │
└──────┬───────┘
│ extern "C" calls
Expand All @@ -90,10 +76,18 @@ int main() {
## Modules

- `tempoch/tempoch.hpp` — umbrella include for the full public API
- `tempoch/time.hpp` — `UTC`, `JulianDate`, `MJD` types
- `tempoch/time.hpp` — `CivilTime`, `Time<scale::S>`, `JulianDate<scale::S>`, `ModifiedJulianDate<scale::S>`
- `tempoch/period.hpp` — `Period` interval type
- `tempoch/ffi_core.hpp` — FFI helpers and exception hierarchy

## Migration Notes

- Old `JulianDate` means `JulianDate<scale::TT>`.
- Old `MJD` means `ModifiedJulianDate<scale::TT>`.
- Old `TT` means `Time<scale::TT>`.
- Old `UTC` civil construction is now `CivilTime` plus `Time<scale::UTC>::from_civil(...)`.
- Legacy names are available only through opt-in compatibility headers.

---

## Error Model
Expand Down Expand Up @@ -128,7 +122,7 @@ cmake -S . -B build
cmake --build build --parallel
ctest --test-dir build --output-on-failure

./build/time_example
./build/01_quickstart
```

---
Expand Down
23 changes: 14 additions & 9 deletions examples/01_quickstart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
/**
* @file 01_quickstart.cpp
* @example 01_quickstart.cpp
* @brief Quick start: a fixed UTC instant expressed as TT, JD, and MJD.
* @brief Quick start: civil UTC promoted to canonical time, then encoded as
* JD(TT), JD(UTC), and MJD(TT).
*
* Mirrors tempoch example 01_quickstart.rs. chrono system-time integration
* is not available through the C FFI, so a fixed reference date is used.
Expand All @@ -21,16 +22,20 @@
int main() {
using namespace tempoch;

UTC utc_now{2026, 7, 15, 22, 0, 0};
JulianDate jd = JulianDate::from_utc(utc_now);
TT tt = jd.to<scales::TT>();
MJD mjd = jd.to<scales::MJD>();
CivilTime civil{2026, 7, 15, 22, 0, 0};
auto utc = Time<scale::UTC>::from_civil(civil);
auto tt = utc.to<scale::TT>();
auto jd_tt = tt.to<format::JD>();
auto jd_utc = utc.to<format::JD>();
auto mjd_tt = tt.to<format::MJD>();

std::cout << std::fixed << std::setprecision(9);
std::cout << "UTC : " << utc_now << "\n";
std::cout << "TT JD : " << tt << "\n";
std::cout << "JD : " << jd << "\n";
std::cout << "MJD : " << mjd << "\n";
std::cout << "Civil UTC : " << civil << "\n";
std::cout << "UTC axis : " << utc << "\n";
std::cout << "TT axis : " << tt << "\n";
std::cout << "JD(TT) : " << jd_tt << "\n";
std::cout << "JD(UTC) : " << jd_utc << "\n";
std::cout << "MJD(TT) : " << mjd_tt << "\n";

return 0;
}
34 changes: 18 additions & 16 deletions examples/02_scales.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,29 @@
int main() {
using namespace tempoch;

JulianDate jd{constants::j2000_jd_tt()};
TT tt = jd.to<scales::TT>();
TAI tai = tt.to<scales::TAI>();
UTC utc = tt.to_utc();
UT ut1 = tt.to<scales::UT>();
TDB tdb = tt.to<scales::TDB>();
TCG tcg = tt.to<scales::TCG>();
TCB tcb = tt.to<scales::TCB>();
auto ctx = TimeContext::with_builtin_eop();

auto jd_tt = JulianDate<scale::TT>::J2000();
auto tt = jd_tt.to_time();
auto tai = tt.to<scale::TAI>();
auto utc = tt.to<scale::UTC>().to_civil();
auto ut1 = tt.to_with<scale::UT1>(ctx);
auto tdb = tt.to<scale::TDB>();
auto tcg = tt.to<scale::TCG>();
auto tcb = tt.to<scale::TCB>();

std::cout << std::fixed << std::setprecision(9);
std::cout << "TT JD : " << tt << "\n";
std::cout << "TAI JD : " << tai << "\n";
std::cout << "UT1 JD : " << ut1 << "\n";
std::cout << "TDB JD : " << tdb << "\n";
std::cout << "TCG JD : " << tcg << "\n";
std::cout << "TCB JD : " << tcb << "\n";
std::cout << "TT JD : " << tt.to<format::JD>() << "\n";
std::cout << "TAI JD : " << tai.to<format::JD>() << "\n";
std::cout << "UT1 JD : " << ut1.to<format::JD>() << "\n";
std::cout << "TDB JD : " << tdb.to<format::JD>() << "\n";
std::cout << "TCG JD : " << tcg.to<format::JD>() << "\n";
std::cout << "TCB JD : " << tcb.to<format::JD>() << "\n";
std::cout << "UTC : " << utc << "\n";

constexpr double SPD = 86'400.0;
const double tt_tai = (tt.value() - tai.value()) * SPD;
const double tt_ut1 = (tt.value() - ut1.value()) * SPD;
const double tt_tai = (tt.to<format::JD>().value() - tai.to<format::JD>().value()) * SPD;
const double tt_ut1 = (tt.to<format::JD>().value() - ut1.to<format::JD>().value()) * SPD;

std::cout << std::fixed << std::setprecision(6);
std::cout << "TT-TAI : " << tt_tai << " s\n";
Expand Down
22 changes: 11 additions & 11 deletions examples/03_formats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,32 @@ static constexpr double UNIX_EPOCH_MJD = 40'587.0; // 1970-01-01 MJD
int main() {
using namespace tempoch;

// J2000 epoch via direct JD construction.
TT j2000_tt{constants::j2000_jd_tt()};
// J2000 epoch as canonical TT, then viewed through encodings.
auto j2000_tt = JulianDate<scale::TT>::J2000().to_time();

// Sample TT instant: J2000 + 123 456.789 s.
TT sample_tt{constants::j2000_jd_tt() + 123'456.789 / 86'400.0};
auto sample_tt = j2000_tt + qtty::Second(123'456.789);

// J2000 from JulianDate constructor (mirrors JulianDate::<TT>::try_new(J2000_JD_TT)).
JulianDate j2000_from_jd{constants::j2000_jd_tt()};
JulianDate<scale::TT> j2000_from_jd{constants::j2000_jd_tt()};

// Unix epoch from JD value.
JulianDate unix_epoch_jd{UNIX_EPOCH_JD};
JulianDate<scale::TT> unix_epoch_jd{UNIX_EPOCH_JD};

// Half-day after J2000 via JD.
JulianDate half_day_jd{constants::j2000_jd_tt() + 0.5};
JulianDate<scale::TT> half_day_jd{constants::j2000_jd_tt() + 0.5};

// Unix epoch from MJD value.
MJD unix_epoch_mjd{UNIX_EPOCH_MJD};
ModifiedJulianDate<scale::TT> unix_epoch_mjd{UNIX_EPOCH_MJD};

// Unix time from seconds (mirrors UnixTime::try_new(Second::new(1_700_000_000.25))).
UnixTime ux{1'700'000'000.25};
UTC utc_from_ux = ux.to_utc();
auto utc_from_ux = ux.to_time().to_civil();

std::cout << std::fixed << std::setprecision(9);
std::cout << "J2000 TT JD : " << j2000_tt << "\n";
std::cout << "Sample TT JD : " << sample_tt << "\n";
std::cout << "Sample TT MJD : " << sample_tt.to<scales::MJD>() << "\n";
std::cout << "J2000 TT JD : " << j2000_tt.to<format::JD>() << "\n";
std::cout << "Sample TT JD : " << sample_tt.to<format::JD>() << "\n";
std::cout << "Sample TT MJD : " << sample_tt.to<format::MJD>() << "\n";
std::cout << "J2000 from JD : " << j2000_from_jd << "\n";
std::cout << "Unix epoch JD(TT) : " << unix_epoch_jd << "\n";
std::cout << "Half-day JD(TT) : " << half_day_jd << "\n";
Expand Down
15 changes: 9 additions & 6 deletions examples/04_periods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,25 @@
int main() {
using namespace tempoch;

auto mjd = [](double v) { return MJD{v}; };
using TTMjd = ModifiedJulianDate<scale::TT>;
using TTMjdPeriod = Period<TTMjd>;

MJDPeriod day{mjd(61'000.0), mjd(61'001.0)};
auto mjd = [](double v) { return TTMjd{v}; };

std::vector<MJDPeriod> a{
TTMjdPeriod day{mjd(61'000.0), mjd(61'001.0)};

std::vector<TTMjdPeriod> a{
{mjd(61'000.10), mjd(61'000.30)},
{mjd(61'000.60), mjd(61'000.85)},
};
std::vector<MJDPeriod> b{
std::vector<TTMjdPeriod> b{
{mjd(61'000.00), mjd(61'000.20)},
{mjd(61'000.70), mjd(61'001.00)},
};

auto overlaps = intersect_periods(a, b);
auto gaps = day.complement_of(a);
auto merged = normalize_periods<MJD>({a[0], a[1], overlaps[0]});
auto merged = normalize_periods<TTMjd>({a[0], a[1], overlaps[0]});

validate_periods(a); // no-throw = valid

Expand All @@ -43,7 +46,7 @@ int main() {
std::cout << "merged : " << merged.size() << "\n";
std::cout << "contains 61000.5? " << std::boolalpha << a[1].contains(mjd(61'000.5)) << "\n";

auto joined = day.union_with(MJDPeriod{mjd(60'999.0), mjd(61'001.5)});
auto joined = day.union_with(TTMjdPeriod{mjd(60'999.0), mjd(61'001.5)});
std::cout << "union : " << joined.size() << " period(s)\n";

return 0;
Expand Down
Loading
Loading