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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.1] - 2026-06-01

### Changed

- Refreshed the vendored `qtty-cpp` submodule to the latest patch release,
including the new angular trig helpers and the additional `Ratio` FFI unit
support.
- Expanded the C++ time adapter with the new GNSS scale and week helpers
introduced on the Rust side.

## [0.5.0] - 2026-05-18

### Breaking
Expand Down
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.15)
project(tempoch_cpp VERSION 0.5.0 LANGUAGES CXX)
project(tempoch_cpp VERSION 0.5.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down Expand Up @@ -162,6 +162,8 @@ foreach(_ex
04_periods # Period list operations: complement, intersect, normalize
06_runtime_tables # EOP table access, UT1 conversion, constants
07_conversions # Full chain: Unix → UTC → TAI → TT → TDB / UT1 / GPS
08_gnss_scales # GNSS and SPICE-compatibility scales (GPST, GST, QZSST, BDT, ET)
09_gnss_week # GNSS week-number decomposition (GnssWeek)
)
add_executable(${_ex} examples/${_ex}.cpp)
target_link_libraries(${_ex} PRIVATE tempoch_cpp)
Expand All @@ -179,6 +181,10 @@ set(TEST_SOURCES
tests/test_headers.cpp
tests/test_time.cpp
tests/test_period.cpp
tests/test_new_scales.cpp
tests/test_constants.cpp
tests/test_data_status.cpp
tests/test_gnss_week.cpp
)

add_executable(test_tempoch ${TEST_SOURCES})
Expand Down
51 changes: 51 additions & 0 deletions examples/08_gnss_scales.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Vallés Puig, Ramon

/**
* @file 08_gnss_scales.cpp
* @example 08_gnss_scales.cpp
* @brief GNSS and SPICE-compatibility time scales (GPST, GST, QZSST, BDT, ET).
*
* These scales were added to mirror the full tempoch scale set. GPST, GST and
* QZSST share the nominal `TAI - 19 s` offset; BDT is `TAI - 33 s`; ET is a
* SPICE-compatibility marker that tracks TDB.
*
* Build and run:
* cmake -B build && cmake --build build
* ./build/08_gnss_scales
*/

#include <cassert>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <tempoch/tempoch.hpp>

int main() {
using namespace tempoch;

auto tai = Time<scale::TAI>::from_split_seconds(qtty::Second(1.0e9), qtty::Second(0.0));

const double tai_s = tai.to<format::J2000s>().value();
const double gpst_s = tai.to<scale::GPST>().to<format::J2000s>().value();
const double gst_s = tai.to<scale::GST>().to<format::J2000s>().value();
const double qzsst_s = tai.to<scale::QZSST>().to<format::J2000s>().value();
const double bdt_s = tai.to<scale::BDT>().to<format::J2000s>().value();
const double et_s = tai.to<scale::ET>().to<format::J2000s>().value();
const double tdb_s = tai.to<scale::TDB>().to<format::J2000s>().value();

std::cout << std::fixed << std::setprecision(6);
std::cout << "TAI - GPST : " << (tai_s - gpst_s) << " s\n";
std::cout << "TAI - GST : " << (tai_s - gst_s) << " s\n";
std::cout << "TAI - QZSST : " << (tai_s - qzsst_s) << " s\n";
std::cout << "TAI - BDT : " << (tai_s - bdt_s) << " s\n";
std::cout << "GPST - BDT : " << (gpst_s - bdt_s) << " s\n";
std::cout << "ET - TDB : " << (et_s - tdb_s) << " s\n";

assert(std::abs((tai_s - gpst_s) - 19.0) < 1e-6);
assert(std::abs((tai_s - bdt_s) - 33.0) < 1e-6);
assert(std::abs((gpst_s - bdt_s) - 14.0) < 1e-6);
assert(std::abs(et_s - tdb_s) < 1e-9);

return 0;
}
50 changes: 50 additions & 0 deletions examples/09_gnss_week.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Vallés Puig, Ramon

/**
* @file 09_gnss_week.cpp
* @example 09_gnss_week.cpp
* @brief GNSS week-number decomposition (GnssWeek) mirroring tempoch.
*
* Decomposes a GNSS-scale instant into `(week, seconds_of_week,
* subsecond_nanos)` since the constellation's defined epoch, and rebuilds the
* instant from that decomposition. Defined for GPST, GST, BDT and QZSST.
*
* Build and run:
* cmake -B build && cmake --build build
* ./build/09_gnss_week
*/

#include <cassert>
#include <iostream>
#include <tempoch/tempoch.hpp>

int main() {
using namespace tempoch;

// GPST week-0/second-0 epoch (1980-01-06 UTC) expressed in J2000 seconds.
constexpr double kGpstEpochJ2000Seconds = -630'763'200.0;

auto epoch = Time<scale::GPST>::from_raw_j2000_seconds(qtty::Second(kGpstEpochJ2000Seconds));
GnssWeek gw0 = to_gnss_week(epoch);
std::cout << "GPST epoch -> week=" << gw0.week << " sow=" << gw0.seconds_of_week
<< " ns=" << gw0.subsecond_nanos << '\n';
assert(gw0.week == 0u && gw0.seconds_of_week == 0u && gw0.subsecond_nanos == 0u);

// Build an instant two weeks + 1 hour past the epoch, then round-trip it.
GnssWeek target{2u, 3'600u, 0u};
auto t = from_gnss_week<scale::GPST>(target);
GnssWeek back = to_gnss_week(t);
std::cout << "round-trip -> week=" << back.week << " sow=" << back.seconds_of_week << '\n';
assert(back.week == target.week);
assert(back.seconds_of_week == target.seconds_of_week);

// QZSST shares the GPST week numbering.
auto qz = Time<scale::QZSST>::from_raw_j2000_seconds(qtty::Second(kGpstEpochJ2000Seconds));
GnssWeek qzw = to_gnss_week(qz);
std::cout << "QZSST epoch -> week=" << qzw.week << " sow=" << qzw.seconds_of_week << '\n';
assert(qzw.week == gw0.week && qzw.seconds_of_week == gw0.seconds_of_week);

std::cout << "GNSS week-number decomposition OK\n";
return 0;
}
30 changes: 30 additions & 0 deletions include/tempoch/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,35 @@ inline double modern_delta_t_observed_end_mjd() noexcept {
return tempoch_const_modern_delta_t_observed_end_mjd();
}

/// Constant TT − TAI offset in seconds (32.184 s).
inline double tt_minus_tai_seconds() noexcept { return tempoch_const_tt_minus_tai_seconds(); }

/// Number of nanoseconds in one SI second (1e9).
inline double nanos_per_second() noexcept { return tempoch_const_nanos_per_second(); }

/// IAU time-scale epoch T0 as a Julian Date on the TT axis (1977-01-01 TAI).
inline double iau_time_epoch_t0_jd() noexcept { return tempoch_const_iau_time_epoch_t0_jd(); }

/// First JD(TT) of the high-accuracy TDB−TT model validity window.
inline double tdb_tt_model_high_accuracy_start_jd() noexcept {
return tempoch_const_tdb_tt_model_high_accuracy_start_jd();
}

/// Last JD(TT) of the high-accuracy TDB−TT model validity window.
inline double tdb_tt_model_high_accuracy_end_jd() noexcept {
return tempoch_const_tdb_tt_model_high_accuracy_end_jd();
}

} // namespace constants

/// ΔT = TT − UT1 in seconds for a UT1 Julian Day, using the compiled USNO
/// model. Returns NaN when the requested epoch is outside the model domain.
inline double delta_t_seconds(double jd_ut1) noexcept { return tempoch_delta_t_seconds(jd_ut1); }

/// ΔT = TT − UT1 in seconds for a UT1 Julian Day, extrapolating beyond the
/// tabulated range with the long-term parabola (always finite).
inline double delta_t_seconds_extrapolated(double jd_ut1) noexcept {
return tempoch_delta_t_seconds_extrapolated(jd_ut1);
}

} // namespace tempoch
81 changes: 81 additions & 0 deletions include/tempoch/data_status.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#pragma once

/**
* @file data_status.hpp
* @brief Active time-data status mirroring `tempoch::time_data_status`.
*
* Exposes the validity horizons of the currently active time-data bundle
* (EOP and ΔT MJD horizons) together with the source the bundle was loaded
* from. Optional EOP horizons surface as NaN when no EOP data is loaded,
* matching the `Option<f64>` fields on the Rust side.
*/

#include "ffi_core.hpp"

#include <cmath>
#include <optional>

namespace tempoch {

/// Origin of the currently active time-data bundle.
///
/// Mirrors `tempoch::ActiveTimeDataSource`.
enum class TimeDataSource : int {
/// Compiled archive snapshot bundled at build time.
Bundled = 0,
/// Bundle loaded through the runtime fetch/cache path.
RuntimeCache = 1,
/// Test or caller-provided override is active.
Override = 2,
};

/// Documented validity horizons of the active time-data bundle, in MJD (UTC).
///
/// Mirrors `tempoch::DataHorizons`: EOP horizons are absent (`std::nullopt`)
/// when no EOP data is loaded; the ΔT horizons are always present.
struct DataHorizons {
/// First MJD covered by the EOP series, or `std::nullopt` when unloaded.
std::optional<double> eop_start_mjd;
/// Last observed (non-predicted) EOP MJD, or `std::nullopt` when unloaded.
std::optional<double> eop_observed_end_mjd;
/// Last EOP MJD including predictions, or `std::nullopt` when unloaded.
std::optional<double> eop_end_mjd;
/// Last MJD with observed ΔT in the archive-provided modern table.
double modern_delta_t_observed_end_mjd;
/// Last MJD covered by the ΔT prediction table.
double delta_t_prediction_horizon_mjd;
};

/// Active time-data status: validity horizons plus the active bundle source.
///
/// Mirrors the numeric and provenance-source portions of
/// `tempoch::TimeDataStatus`.
struct TimeDataStatus {
/// Validity horizons of the active bundle.
DataHorizons horizons;
/// Source the active bundle was loaded from.
TimeDataSource source;
};

/// Capture the status of the currently active time-data bundle.
///
/// Mirrors `tempoch::time_data_status()`.
inline TimeDataStatus time_data_status() {
TempochDataHorizons raw{};
check_status(tempoch_time_data_status(&raw), "tempoch::time_data_status");

auto opt = [](double v) -> std::optional<double> {
return std::isnan(v) ? std::nullopt : std::optional<double>(v);
};

TimeDataStatus status;
status.horizons.eop_start_mjd = opt(raw.eop_start_mjd);
status.horizons.eop_observed_end_mjd = opt(raw.eop_observed_end_mjd);
status.horizons.eop_end_mjd = opt(raw.eop_end_mjd);
status.horizons.modern_delta_t_observed_end_mjd = raw.modern_delta_t_observed_end_mjd;
status.horizons.delta_t_prediction_horizon_mjd = raw.delta_t_prediction_horizon_mjd;
status.source = static_cast<TimeDataSource>(raw.source);
return status;
}

} // namespace tempoch
64 changes: 64 additions & 0 deletions include/tempoch/gnss_week.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once

/**
* @file gnss_week.hpp
* @brief GNSS week-number decomposition mirroring `tempoch::GnssWeek`.
*
* Provides the `GnssWeek` value type plus `to_gnss_week` / `from_gnss_week`
* free functions, defined only for the GNSS coordinate scales (`GPST`, `GST`,
* `BDT`, `QZSST`), matching the `Time::<S>::to_gnss_week` /
* `Time::<S>::from_gnss_week` conversions on the Rust side.
*/

#include "scales/scales.hpp"
#include "time_base.hpp"

#include <cstdint>
#include <type_traits>

namespace tempoch {

/// Trait marking the GNSS coordinate scales that support week decomposition.
template <typename S> struct is_gnss_scale : std::false_type {};
template <> struct is_gnss_scale<scale::GPST> : std::true_type {};
template <> struct is_gnss_scale<scale::GST> : std::true_type {};
template <> struct is_gnss_scale<scale::BDT> : std::true_type {};
template <> struct is_gnss_scale<scale::QZSST> : std::true_type {};

template <typename S> inline constexpr bool is_gnss_scale_v = is_gnss_scale<S>::value;

/// Decomposed GNSS week-number form since the constellation's defined epoch.
///
/// Mirrors `tempoch::GnssWeek`. `week` is full (no rollover applied),
/// `seconds_of_week` lies in `[0, 604'800)` and `subsecond_nanos` in
/// `[0, 1'000'000'000)`.
struct GnssWeek {
/// Full week number since the constellation's epoch (no rollover applied).
std::uint32_t week;
/// Seconds since the start of `week`, in `[0, 604'800)`.
std::uint32_t seconds_of_week;
/// Subsecond nanoseconds remainder, in `[0, 1'000'000'000)`.
std::uint32_t subsecond_nanos;
};

/// Decompose a GNSS-scale instant into its week-number form.
template <typename S, std::enable_if_t<is_gnss_scale_v<S>, int> = 0>
inline GnssWeek to_gnss_week(const Time<S> &time) {
TempochGnssWeek raw{};
check_status(
tempoch_time_to_gnss_week(time.c_inner(), static_cast<int32_t>(scale_tag_v<S>), &raw),
"tempoch::to_gnss_week");
return GnssWeek{raw.week, raw.seconds_of_week, raw.subsecond_nanos};
}

/// Build a GNSS-scale instant from a week-number decomposition.
template <typename S, std::enable_if_t<is_gnss_scale_v<S>, int> = 0>
inline Time<S> from_gnss_week(const GnssWeek &gw) {
TempochGnssWeek raw{gw.week, gw.seconds_of_week, gw.subsecond_nanos};
tempoch_time_t out{};
check_status(tempoch_time_from_gnss_week(raw, static_cast<int32_t>(scale_tag_v<S>), &out),
"tempoch::from_gnss_week");
return Time<S>::from_split_seconds(qtty::Second(out.hi_seconds), qtty::Second(out.lo_seconds));
}

} // namespace tempoch
18 changes: 18 additions & 0 deletions include/tempoch/scales/bdt.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "base.hpp"

namespace tempoch {

namespace scale {
struct BDT {};
} // namespace scale

template <> struct is_scale<scale::BDT> : std::true_type {};

template <> struct ScaleTraits<scale::BDT> {
static constexpr tempoch_scale_tag_t ffi_tag = TEMPOCH_SCALE_TAG_T_BDT;
static constexpr const char *name() { return "BDT"; }
};

} // namespace tempoch
18 changes: 18 additions & 0 deletions include/tempoch/scales/et.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "base.hpp"

namespace tempoch {

namespace scale {
struct ET {};
} // namespace scale

template <> struct is_scale<scale::ET> : std::true_type {};

template <> struct ScaleTraits<scale::ET> {
static constexpr tempoch_scale_tag_t ffi_tag = TEMPOCH_SCALE_TAG_T_ET;
static constexpr const char *name() { return "ET"; }
};

} // namespace tempoch
18 changes: 18 additions & 0 deletions include/tempoch/scales/gpst.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "base.hpp"

namespace tempoch {

namespace scale {
struct GPST {};
} // namespace scale

template <> struct is_scale<scale::GPST> : std::true_type {};

template <> struct ScaleTraits<scale::GPST> {
static constexpr tempoch_scale_tag_t ffi_tag = TEMPOCH_SCALE_TAG_T_GPST;
static constexpr const char *name() { return "GPST"; }
};

} // namespace tempoch
Loading
Loading