siderust-js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +166 -0
- package/.gitmodules +9 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +661 -0
- package/README.md +138 -0
- package/package.json +12 -0
- package/qtty-js/.github/workflows/ci.yml +151 -0
- package/qtty-js/.gitmodules +3 -0
- package/qtty-js/CHANGELOG.md +31 -0
- package/qtty-js/LICENSE +661 -0
- package/qtty-js/README.md +132 -0
- package/qtty-js/package.json +20 -0
- package/qtty-js/qtty/.github/workflows/ci.yml +155 -0
- package/qtty-js/qtty/CHANGELOG.md +120 -0
- package/qtty-js/qtty/Cargo.lock +1462 -0
- package/qtty-js/qtty/Cargo.toml +12 -0
- package/qtty-js/qtty/LICENSE +661 -0
- package/qtty-js/qtty/README.md +9 -0
- package/qtty-js/qtty/qtty/Cargo.toml +41 -0
- package/qtty-js/qtty/qtty/README.md +8 -0
- package/qtty-js/qtty/qtty/examples/angles.rs +14 -0
- package/qtty-js/qtty/qtty/examples/astronomy.rs +17 -0
- package/qtty-js/qtty/qtty/examples/dimensional_arithmetic.rs +83 -0
- package/qtty-js/qtty/qtty/examples/python_integration.rs +61 -0
- package/qtty-js/qtty/qtty/examples/quickstart.rs +15 -0
- package/qtty-js/qtty/qtty/examples/ratios.rs +12 -0
- package/qtty-js/qtty/qtty/examples/serde_with_unit.rs +234 -0
- package/qtty-js/qtty/qtty/examples/serialization.rs +141 -0
- package/qtty-js/qtty/qtty/examples/serialization_advanced.rs +155 -0
- package/qtty-js/qtty/qtty/src/f32.rs +108 -0
- package/qtty-js/qtty/qtty/src/f64.rs +30 -0
- package/qtty-js/qtty/qtty/src/i128.rs +111 -0
- package/qtty-js/qtty/qtty/src/i16.rs +111 -0
- package/qtty-js/qtty/qtty/src/i32.rs +111 -0
- package/qtty-js/qtty/qtty/src/i64.rs +111 -0
- package/qtty-js/qtty/qtty/src/i8.rs +111 -0
- package/qtty-js/qtty/qtty/src/lib.rs +238 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/src/lib.rs +7 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/src/lib.rs +7 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/src/lib.rs +5 -0
- package/qtty-js/qtty/qtty/tests/integration_tests.rs +529 -0
- package/qtty-js/qtty/qtty/tests/qtty_vec_feature_matrix.rs +58 -0
- package/qtty-js/qtty/qtty-core/Cargo.toml +41 -0
- package/qtty-js/qtty/qtty-core/README.md +8 -0
- package/qtty-js/qtty/qtty-core/examples/diesel_integration.rs +145 -0
- package/qtty-js/qtty/qtty-core/examples/quantity_db_serde.rs +215 -0
- package/qtty-js/qtty/qtty-core/src/dimension.rs +249 -0
- package/qtty-js/qtty/qtty-core/src/feature_diesel.rs +318 -0
- package/qtty-js/qtty/qtty-core/src/feature_pyo3.rs +27 -0
- package/qtty-js/qtty/qtty-core/src/feature_serde.rs +203 -0
- package/qtty-js/qtty/qtty-core/src/feature_tiberius.rs +28 -0
- package/qtty-js/qtty/qtty-core/src/lib.rs +744 -0
- package/qtty-js/qtty/qtty-core/src/macros.rs +93 -0
- package/qtty-js/qtty/qtty-core/src/quantity.rs +810 -0
- package/qtty-js/qtty/qtty-core/src/scalar.rs +1742 -0
- package/qtty-js/qtty/qtty-core/src/unit.rs +332 -0
- package/qtty-js/qtty/qtty-core/src/units/angular.rs +1228 -0
- package/qtty-js/qtty/qtty-core/src/units/area.rs +243 -0
- package/qtty-js/qtty/qtty-core/src/units/frequency.rs +179 -0
- package/qtty-js/qtty/qtty-core/src/units/length.rs +1270 -0
- package/qtty-js/qtty/qtty-core/src/units/mass.rs +488 -0
- package/qtty-js/qtty/qtty-core/src/units/mod.rs +26 -0
- package/qtty-js/qtty/qtty-core/src/units/power.rs +324 -0
- package/qtty-js/qtty/qtty-core/src/units/time.rs +667 -0
- package/qtty-js/qtty/qtty-core/src/units/unitless.rs +212 -0
- package/qtty-js/qtty/qtty-core/src/units/velocity.rs +210 -0
- package/qtty-js/qtty/qtty-core/src/units/volume.rs +269 -0
- package/qtty-js/qtty/qtty-core/tests/core.rs +628 -0
- package/qtty-js/qtty/qtty-core/tests/diesel.rs +461 -0
- package/qtty-js/qtty/qtty-core/tests/integers.rs +632 -0
- package/qtty-js/qtty/qtty-core/tests/no_cross_unit_ops.rs +35 -0
- package/qtty-js/qtty/qtty-core/tests/pyo3.rs +334 -0
- package/qtty-js/qtty/qtty-core/tests/quantity_f32.rs +276 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_decimal.rs +258 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_f32.rs +286 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_f64_real.rs +287 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_rational.rs +260 -0
- package/qtty-js/qtty/qtty-core/tests/serde.rs +256 -0
- package/qtty-js/qtty/qtty-core/tests/tiberius.rs +208 -0
- package/qtty-js/qtty/qtty-derive/Cargo.toml +23 -0
- package/qtty-js/qtty/qtty-derive/README.md +8 -0
- package/qtty-js/qtty/qtty-derive/src/lib.rs +340 -0
- package/qtty-js/qtty/qtty-ffi/ARCHITECTURE.md +3 -0
- package/qtty-js/qtty/qtty-ffi/Cargo.toml +31 -0
- package/qtty-js/qtty/qtty-ffi/README.md +9 -0
- package/qtty-js/qtty/qtty-ffi/build.rs +326 -0
- package/qtty-js/qtty/qtty-ffi/cbindgen.toml +105 -0
- package/qtty-js/qtty/qtty-ffi/include/qtty_ffi.h +1126 -0
- package/qtty-js/qtty/qtty-ffi/src/ffi.rs +1251 -0
- package/qtty-js/qtty/qtty-ffi/src/ffi_serde.rs +294 -0
- package/qtty-js/qtty/qtty-ffi/src/helpers.rs +310 -0
- package/qtty-js/qtty/qtty-ffi/src/lib.rs +229 -0
- package/qtty-js/qtty/qtty-ffi/src/macros.rs +121 -0
- package/qtty-js/qtty/qtty-ffi/src/registry.rs +274 -0
- package/qtty-js/qtty/qtty-ffi/src/types.rs +620 -0
- package/qtty-js/qtty/qtty-ffi/tests/integration_tests.rs +842 -0
- package/qtty-js/qtty/qtty-ffi/units.csv +156 -0
- package/qtty-js/qtty/qtty-ffi/units.csv.md +3 -0
- package/qtty-js/qtty-node/.prettierignore +6 -0
- package/qtty-js/qtty-node/.prettierrc.json +6 -0
- package/qtty-js/qtty-node/README.md +250 -0
- package/qtty-js/qtty-node/c8.config.json +11 -0
- package/qtty-js/qtty-node/eslint.config.js +31 -0
- package/qtty-js/qtty-node/examples/arithmetic.mjs +64 -0
- package/qtty-js/qtty-node/examples/astronomy.mjs +90 -0
- package/qtty-js/qtty-node/examples/quickstart.mjs +36 -0
- package/qtty-js/qtty-node/examples/serialization.mjs +125 -0
- package/qtty-js/qtty-node/examples/unit_factories.mjs +74 -0
- package/qtty-js/qtty-node/index.d.ts +219 -0
- package/qtty-js/qtty-node/index.js +323 -0
- package/qtty-js/qtty-node/lib/DerivedQuantity.js +122 -0
- package/qtty-js/qtty-node/lib/Quantity.js +151 -0
- package/qtty-js/qtty-node/lib/backend.js +25 -0
- package/qtty-js/qtty-node/native.cjs +306 -0
- package/qtty-js/qtty-node/package-lock.json +3223 -0
- package/qtty-js/qtty-node/package.json +70 -0
- package/qtty-js/qtty-node/units.d.ts +299 -0
- package/qtty-js/qtty-node/units.js +210 -0
- package/qtty-js/qtty-web/Cargo.lock +767 -0
- package/qtty-js/qtty-web/Cargo.toml +21 -0
- package/qtty-js/qtty-web/index.d.ts +140 -0
- package/qtty-js/qtty-web/index.js +20 -0
- package/qtty-js/qtty-web/lib/DerivedQuantity.js +58 -0
- package/qtty-js/qtty-web/lib/Quantity.js +75 -0
- package/qtty-js/qtty-web/lib/backend.js +80 -0
- package/qtty-js/qtty-web/package.json +45 -0
- package/qtty-js/qtty-web/src/lib.rs +111 -0
- package/qtty-js/scripts/ci.sh +73 -0
- package/scripts/ci.sh +123 -0
- package/siderust-core/Cargo.lock +787 -0
- package/siderust-core/Cargo.toml +18 -0
- package/siderust-core/DEDUPLICATION.md +124 -0
- package/siderust-core/src/body.rs +120 -0
- package/siderust-core/src/events.rs +184 -0
- package/siderust-core/src/lib.rs +20 -0
- package/siderust-core/src/observer.rs +55 -0
- package/siderust-core/src/position.rs +213 -0
- package/siderust-node/.prettierignore +7 -0
- package/siderust-node/.prettierrc.json +6 -0
- package/siderust-node/Cargo.lock +906 -0
- package/siderust-node/Cargo.toml +29 -0
- package/siderust-node/README.md +109 -0
- package/siderust-node/__test__/index.test.mjs +248 -0
- package/siderust-node/build.rs +5 -0
- package/siderust-node/c8.config.json +3 -0
- package/siderust-node/eslint.config.js +31 -0
- package/siderust-node/examples/01_basic_coordinates.mjs +24 -0
- package/siderust-node/examples/02_coordinate_transformations.mjs +25 -0
- package/siderust-node/examples/03_all_frames_conversions.mjs +26 -0
- package/siderust-node/examples/04_all_center_conversions.mjs +24 -0
- package/siderust-node/examples/05_target_tracking.mjs +22 -0
- package/siderust-node/examples/06_night_events.mjs +18 -0
- package/siderust-node/examples/07_moon_properties.mjs +21 -0
- package/siderust-node/examples/08_solar_system.mjs +19 -0
- package/siderust-node/examples/09_star_observability.mjs +22 -0
- package/siderust-node/examples/10_time_periods.mjs +9 -0
- package/siderust-node/examples/11_serialization.mjs +31 -0
- package/siderust-node/examples/12_runtime_ephemeris.mjs +27 -0
- package/siderust-node/examples/13_coordinate_operations.mjs +20 -0
- package/siderust-node/index.d.ts +623 -0
- package/siderust-node/index.js +79 -0
- package/siderust-node/lib/Observer.js +112 -0
- package/siderust-node/lib/Star.js +118 -0
- package/siderust-node/lib/backend.js +63 -0
- package/siderust-node/lib/wrappers.js +566 -0
- package/siderust-node/main.js +20 -0
- package/siderust-node/native.cjs +360 -0
- package/siderust-node/package-lock.json +3261 -0
- package/siderust-node/package.json +71 -0
- package/siderust-node/src/body.rs +74 -0
- package/siderust-node/src/coordinates.rs +372 -0
- package/siderust-node/src/ephemeris.rs +462 -0
- package/siderust-node/src/events.rs +577 -0
- package/siderust-node/src/lib.rs +43 -0
- package/siderust-node/src/observer.rs +132 -0
- package/siderust-node/src/phase.rs +218 -0
- package/siderust-node/src/position.rs +292 -0
- package/siderust-node/src/star.rs +200 -0
- package/siderust-web/Cargo.lock +855 -0
- package/siderust-web/Cargo.toml +34 -0
- package/siderust-web/README.md +100 -0
- package/siderust-web/__test__/index.test.mjs +118 -0
- package/siderust-web/examples/github-pages/README.md +31 -0
- package/siderust-web/examples/github-pages/index.html +135 -0
- package/siderust-web/index.d.ts +311 -0
- package/siderust-web/index.js +66 -0
- package/siderust-web/lib/Observer.js +103 -0
- package/siderust-web/lib/Star.js +116 -0
- package/siderust-web/lib/backend.js +400 -0
- package/siderust-web/lib/wrappers.js +512 -0
- package/siderust-web/package.json +55 -0
- package/siderust-web/src/body.rs +69 -0
- package/siderust-web/src/coordinates.rs +302 -0
- package/siderust-web/src/ephemeris.rs +456 -0
- package/siderust-web/src/events.rs +520 -0
- package/siderust-web/src/lib.rs +51 -0
- package/siderust-web/src/observer.rs +117 -0
- package/siderust-web/src/phase.rs +190 -0
- package/siderust-web/src/position.rs +291 -0
- package/siderust-web/src/star.rs +178 -0
- package/tempoch-js/.github/workflows/ci.yml +142 -0
- package/tempoch-js/.gitmodules +3 -0
- package/tempoch-js/CHANGELOG.md +25 -0
- package/tempoch-js/LICENSE +661 -0
- package/tempoch-js/README.md +126 -0
- package/tempoch-js/package.json +20 -0
- package/tempoch-js/scripts/ci.sh +73 -0
- package/tempoch-js/tempoch/.github/workflows/ci.yml +113 -0
- package/tempoch-js/tempoch/CHANGELOG.md +82 -0
- package/tempoch-js/tempoch/Cargo.lock +947 -0
- package/tempoch-js/tempoch/Cargo.toml +3 -0
- package/tempoch-js/tempoch/LICENSE +661 -0
- package/tempoch-js/tempoch/README.md +76 -0
- package/tempoch-js/tempoch/tempoch/Cargo.toml +27 -0
- package/tempoch-js/tempoch/tempoch/examples/periods.rs +45 -0
- package/tempoch-js/tempoch/tempoch/examples/quickstart.rs +13 -0
- package/tempoch-js/tempoch/tempoch/src/lib.rs +49 -0
- package/tempoch-js/tempoch/tempoch/tests/integration.rs +57 -0
- package/tempoch-js/tempoch/tempoch-core/Cargo.toml +24 -0
- package/tempoch-js/tempoch/tempoch-core/src/delta_t.rs +345 -0
- package/tempoch-js/tempoch/tempoch-core/src/instant.rs +811 -0
- package/tempoch-js/tempoch/tempoch-core/src/julian_date_ext.rs +142 -0
- package/tempoch-js/tempoch/tempoch-core/src/lib.rs +81 -0
- package/tempoch-js/tempoch/tempoch-core/src/period.rs +1168 -0
- package/tempoch-js/tempoch/tempoch-core/src/scales.rs +779 -0
- package/tempoch-js/tempoch/tempoch-ffi/Cargo.lock +889 -0
- package/tempoch-js/tempoch/tempoch-ffi/Cargo.toml +26 -0
- package/tempoch-js/tempoch/tempoch-ffi/build.rs +24 -0
- package/tempoch-js/tempoch/tempoch-ffi/cbindgen.toml +30 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/error.rs +19 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/lib.rs +82 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/period.rs +101 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/time.rs +711 -0
- package/tempoch-js/tempoch/tempoch-ffi/tests/ffi.rs +265 -0
- package/tempoch-js/tempoch-node/.prettierignore +6 -0
- package/tempoch-js/tempoch-node/.prettierrc.json +6 -0
- package/tempoch-js/tempoch-node/Cargo.lock +496 -0
- package/tempoch-js/tempoch-node/Cargo.toml +29 -0
- package/tempoch-js/tempoch-node/README.md +265 -0
- package/tempoch-js/tempoch-node/__test__/index.test.mjs +598 -0
- package/tempoch-js/tempoch-node/build.rs +5 -0
- package/tempoch-js/tempoch-node/c8.config.json +3 -0
- package/tempoch-js/tempoch-node/eslint.config.js +31 -0
- package/tempoch-js/tempoch-node/examples/periods.mjs +79 -0
- package/tempoch-js/tempoch-node/examples/quickstart.mjs +71 -0
- package/tempoch-js/tempoch-node/examples/timescales.mjs +92 -0
- package/tempoch-js/tempoch-node/index.d.ts +280 -0
- package/tempoch-js/tempoch-node/index.js +32 -0
- package/tempoch-js/tempoch-node/lib/JulianDate.js +176 -0
- package/tempoch-js/tempoch-node/lib/ModifiedJulianDate.js +156 -0
- package/tempoch-js/tempoch-node/lib/Period.js +133 -0
- package/tempoch-js/tempoch-node/lib/backend.js +38 -0
- package/tempoch-js/tempoch-node/lib/qttyCompat.js +92 -0
- package/tempoch-js/tempoch-node/native.cjs +317 -0
- package/tempoch-js/tempoch-node/package-lock.json +3223 -0
- package/tempoch-js/tempoch-node/package.json +56 -0
- package/tempoch-js/tempoch-node/src/lib.rs +573 -0
- package/tempoch-js/tempoch-web/Cargo.toml +23 -0
- package/tempoch-js/tempoch-web/index.d.ts +95 -0
- package/tempoch-js/tempoch-web/index.js +27 -0
- package/tempoch-js/tempoch-web/lib/JulianDate.js +170 -0
- package/tempoch-js/tempoch-web/lib/ModifiedJulianDate.js +145 -0
- package/tempoch-js/tempoch-web/lib/Period.js +121 -0
- package/tempoch-js/tempoch-web/lib/backend.js +118 -0
- package/tempoch-js/tempoch-web/package.json +46 -0
- package/tempoch-js/tempoch-web/src/lib.rs +184 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# tempoch
|
|
2
|
+
|
|
3
|
+
[](https://crates.io/crates/tempoch)
|
|
4
|
+
[](https://docs.rs/tempoch)
|
|
5
|
+
[](https://github.com/Siderust/tempoch/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
Typed astronomical time primitives for Rust.
|
|
8
|
+
|
|
9
|
+
`tempoch` provides:
|
|
10
|
+
|
|
11
|
+
- Generic `Time<S>` instants parameterized by time-scale markers (`JD`, `MJD`, `TT`, `UT`, `TAI`, `GPS`, `UnixTime`, ...).
|
|
12
|
+
- Built-in UTC conversion through `chrono`.
|
|
13
|
+
- Automatic `ΔT = TT - UT` handling for the `UT` scale.
|
|
14
|
+
- Generic intervals with `Interval<T>` and scale-aware alias `Period<S>`.
|
|
15
|
+
- Utility operations like period intersection and complement.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```toml
|
|
20
|
+
[dependencies]
|
|
21
|
+
tempoch = "0.3"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```rust
|
|
27
|
+
use chrono::Utc;
|
|
28
|
+
use tempoch::{JulianDate, MJD, Time};
|
|
29
|
+
|
|
30
|
+
let now_jd = JulianDate::from_utc(Utc::now());
|
|
31
|
+
let now_mjd: Time<MJD> = now_jd.to::<MJD>();
|
|
32
|
+
|
|
33
|
+
println!("JD(TT): {now_jd}");
|
|
34
|
+
println!("MJD(TT): {now_mjd}");
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Period Operations
|
|
38
|
+
|
|
39
|
+
```rust
|
|
40
|
+
use tempoch::{complement_within, intersect_periods, ModifiedJulianDate, Period};
|
|
41
|
+
|
|
42
|
+
let outer = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(10.0));
|
|
43
|
+
let a = vec![
|
|
44
|
+
Period::new(ModifiedJulianDate::new(1.0), ModifiedJulianDate::new(4.0)),
|
|
45
|
+
Period::new(ModifiedJulianDate::new(6.0), ModifiedJulianDate::new(9.0)),
|
|
46
|
+
];
|
|
47
|
+
let b = vec![
|
|
48
|
+
Period::new(ModifiedJulianDate::new(2.0), ModifiedJulianDate::new(3.0)),
|
|
49
|
+
Period::new(ModifiedJulianDate::new(7.0), ModifiedJulianDate::new(8.0)),
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
let overlap = intersect_periods(&a, &b);
|
|
53
|
+
let gaps = complement_within(outer, &a);
|
|
54
|
+
|
|
55
|
+
assert_eq!(overlap.len(), 2);
|
|
56
|
+
assert_eq!(gaps.len(), 3);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
- `cargo run --example quickstart`
|
|
62
|
+
- `cargo run --example periods`
|
|
63
|
+
|
|
64
|
+
## Tests and Coverage
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cargo test --all-targets
|
|
68
|
+
cargo test --doc
|
|
69
|
+
cargo +nightly llvm-cov --workspace --all-features --doctests --summary-only
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Coverage is gated in CI at **>= 90% line coverage**.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
AGPL-3.0-only
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tempoch"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
authors = ["VPRamon <vallespuigramon@gmail.com>"]
|
|
6
|
+
description = "Astronomical time primitives: typed time scales, Julian dates, UTC conversion, and interval operations."
|
|
7
|
+
license = "AGPL-3.0-only"
|
|
8
|
+
readme = "../README.md"
|
|
9
|
+
repository = "https://github.com/Siderust/tempoch"
|
|
10
|
+
keywords = ["astronomy", "time", "julian-date", "ephemeris", "science"]
|
|
11
|
+
categories = ["science", "date-and-time"]
|
|
12
|
+
|
|
13
|
+
[lib]
|
|
14
|
+
name = "tempoch"
|
|
15
|
+
path = "src/lib.rs"
|
|
16
|
+
|
|
17
|
+
[features]
|
|
18
|
+
default = []
|
|
19
|
+
serde = ["tempoch-core/serde"]
|
|
20
|
+
|
|
21
|
+
[dependencies]
|
|
22
|
+
tempoch-core = { path = "../tempoch-core", version = "0.3.0" }
|
|
23
|
+
|
|
24
|
+
[dev-dependencies]
|
|
25
|
+
chrono = "0.4.44"
|
|
26
|
+
qtty = "0.4.0"
|
|
27
|
+
serde_json = "1.0"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
use chrono::{DateTime, Utc};
|
|
2
|
+
use tempoch::{
|
|
3
|
+
complement_within, intersect_periods, Interval, ModifiedJulianDate, Period, UtcPeriod,
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
fn main() {
|
|
7
|
+
let day = Period::new(
|
|
8
|
+
ModifiedJulianDate::new(61_000.0),
|
|
9
|
+
ModifiedJulianDate::new(61_001.0),
|
|
10
|
+
);
|
|
11
|
+
let windows = vec![
|
|
12
|
+
Period::new(
|
|
13
|
+
ModifiedJulianDate::new(61_000.10),
|
|
14
|
+
ModifiedJulianDate::new(61_000.30),
|
|
15
|
+
),
|
|
16
|
+
Period::new(
|
|
17
|
+
ModifiedJulianDate::new(61_000.60),
|
|
18
|
+
ModifiedJulianDate::new(61_000.85),
|
|
19
|
+
),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
let gaps = complement_within(day, &windows);
|
|
23
|
+
println!("Visible windows: {}", windows.len());
|
|
24
|
+
println!("Gaps: {}", gaps.len());
|
|
25
|
+
|
|
26
|
+
let constraints = vec![
|
|
27
|
+
Period::new(
|
|
28
|
+
ModifiedJulianDate::new(61_000.00),
|
|
29
|
+
ModifiedJulianDate::new(61_000.20),
|
|
30
|
+
),
|
|
31
|
+
Period::new(
|
|
32
|
+
ModifiedJulianDate::new(61_000.70),
|
|
33
|
+
ModifiedJulianDate::new(61_001.00),
|
|
34
|
+
),
|
|
35
|
+
];
|
|
36
|
+
let intersection = intersect_periods(&windows, &constraints);
|
|
37
|
+
println!("Intersection windows: {}", intersection.len());
|
|
38
|
+
|
|
39
|
+
let utc_day: UtcPeriod = day.to::<DateTime<Utc>>().unwrap();
|
|
40
|
+
let roundtrip: Interval<ModifiedJulianDate> = utc_day.to::<ModifiedJulianDate>();
|
|
41
|
+
println!(
|
|
42
|
+
"Roundtrip drift (days): {:.3e}",
|
|
43
|
+
(roundtrip.start.value() - day.start.value()).abs()
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
use chrono::Utc;
|
|
2
|
+
use tempoch::{JulianDate, Time, MJD, UT};
|
|
3
|
+
|
|
4
|
+
fn main() {
|
|
5
|
+
let now_jd = JulianDate::from_utc(Utc::now());
|
|
6
|
+
let now_mjd: Time<MJD> = now_jd.to::<MJD>();
|
|
7
|
+
let now_ut: Time<UT> = now_jd.to::<UT>();
|
|
8
|
+
|
|
9
|
+
println!("JD(TT): {now_jd}");
|
|
10
|
+
println!("MJD(TT): {now_mjd}");
|
|
11
|
+
println!("UT: {now_ut}");
|
|
12
|
+
println!("ΔT: {}", now_ut.delta_t());
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Vallés Puig, Ramon
|
|
3
|
+
|
|
4
|
+
//! Time Module
|
|
5
|
+
//!
|
|
6
|
+
//! This crate is a façade over `tempoch-core` and re-exports its public API.
|
|
7
|
+
//!
|
|
8
|
+
//! # Core types
|
|
9
|
+
//!
|
|
10
|
+
//! - [`Time<S>`] — generic instant parameterised by a [`TimeScale`] marker.
|
|
11
|
+
//! - [`TimeScale`] — trait that defines a time scale (epoch offset + conversions).
|
|
12
|
+
//! - [`JulianDate`] — type alias for `Time<JD>`.
|
|
13
|
+
//! - [`JulianEphemerisDay`] — type alias for `Time<JDE>`.
|
|
14
|
+
//! - [`ModifiedJulianDate`] — type alias for `Time<MJD>`.
|
|
15
|
+
//! - [`Period<S>`] — a time interval parameterised by a [`TimeScale`] marker.
|
|
16
|
+
//! - [`Interval<T>`] — a generic interval over any [`TimeInstant`].
|
|
17
|
+
//! - [`TimeInstant`] — trait for points in time usable with [`Interval`].
|
|
18
|
+
//!
|
|
19
|
+
//! # Time scales
|
|
20
|
+
//!
|
|
21
|
+
//! The following markers implement [`TimeScale`]:
|
|
22
|
+
//!
|
|
23
|
+
//! | Marker | Scale |
|
|
24
|
+
//! |--------|-------|
|
|
25
|
+
//! | [`JD`] | Julian Date |
|
|
26
|
+
//! | [`JDE`] | Julian Ephemeris Day |
|
|
27
|
+
//! | [`MJD`] | Modified Julian Date |
|
|
28
|
+
//! | [`TDB`] | Barycentric Dynamical Time |
|
|
29
|
+
//! | [`TT`] | Terrestrial Time |
|
|
30
|
+
//! | [`TAI`] | International Atomic Time |
|
|
31
|
+
//! | [`TCG`] | Geocentric Coordinate Time |
|
|
32
|
+
//! | [`TCB`] | Barycentric Coordinate Time |
|
|
33
|
+
//! | [`GPS`] | GPS Time |
|
|
34
|
+
//! | [`UnixTime`] | Unix / POSIX time |
|
|
35
|
+
//! | [`UT`] | Universal Time (Earth rotation) |
|
|
36
|
+
//!
|
|
37
|
+
//! # ΔT (Delta T)
|
|
38
|
+
//!
|
|
39
|
+
//! The difference **ΔT = TT − UT** is applied automatically by the
|
|
40
|
+
//! [`UT`] time scale. Use `Time::<UT>::new(jd_ut)` for UT-based values,
|
|
41
|
+
//! or construct any scale via `from_utc()` which routes through `UT` internally.
|
|
42
|
+
//! The raw ΔT value (in seconds) is available via [`Time::<UT>::delta_t()`](Time::delta_t).
|
|
43
|
+
|
|
44
|
+
pub use tempoch_core::{
|
|
45
|
+
complement_within, intersect_periods, normalize_periods, tai_minus_utc, validate_period_list,
|
|
46
|
+
ConversionError, Interval, InvalidIntervalError, JulianDate, JulianEphemerisDay,
|
|
47
|
+
ModifiedJulianDate, NonFiniteTimeError, Period, PeriodListError, Time, TimeInstant, TimeScale,
|
|
48
|
+
UniversalTime, UnixTime, UtcPeriod, GPS, JD, JDE, MJD, TAI, TCB, TCG, TDB, TT, UT,
|
|
49
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
use chrono::DateTime;
|
|
2
|
+
use qtty::{Day, Days, Seconds};
|
|
3
|
+
use tempoch::{complement_within, intersect_periods, JulianDate, ModifiedJulianDate, Period, UT};
|
|
4
|
+
|
|
5
|
+
#[test]
|
|
6
|
+
fn utc_roundtrip_j2000_is_stable() {
|
|
7
|
+
let datetime = DateTime::from_timestamp(946_728_000, 0).unwrap();
|
|
8
|
+
let jd = JulianDate::from_utc(datetime);
|
|
9
|
+
let back = jd.to_utc().expect("to_utc");
|
|
10
|
+
let delta_ns = back.timestamp_nanos_opt().unwrap() - datetime.timestamp_nanos_opt().unwrap();
|
|
11
|
+
assert!(delta_ns.abs() < 1_000);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#[test]
|
|
15
|
+
fn ut_applies_delta_t_near_j2000() {
|
|
16
|
+
let ut = tempoch::Time::<UT>::new(2_451_545.0);
|
|
17
|
+
let jd: JulianDate = ut.to::<tempoch::JD>();
|
|
18
|
+
let offset = (jd.quantity() - ut.quantity()).to::<Day>();
|
|
19
|
+
let offset_s = offset.to::<qtty::Second>();
|
|
20
|
+
assert!((offset_s - Seconds::new(63.83)).abs() < Seconds::new(1.0));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn period_set_ops_match_expected_intervals() {
|
|
25
|
+
let outer = Period::new(ModifiedJulianDate::new(0.0), ModifiedJulianDate::new(10.0));
|
|
26
|
+
let a = vec![
|
|
27
|
+
Period::new(ModifiedJulianDate::new(1.0), ModifiedJulianDate::new(3.0)),
|
|
28
|
+
Period::new(ModifiedJulianDate::new(5.0), ModifiedJulianDate::new(9.0)),
|
|
29
|
+
];
|
|
30
|
+
let b = vec![
|
|
31
|
+
Period::new(ModifiedJulianDate::new(2.0), ModifiedJulianDate::new(4.0)),
|
|
32
|
+
Period::new(ModifiedJulianDate::new(7.0), ModifiedJulianDate::new(8.0)),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
let below_b = complement_within(outer, &b);
|
|
36
|
+
let between = intersect_periods(&a, &below_b);
|
|
37
|
+
|
|
38
|
+
assert_eq!(between.len(), 3);
|
|
39
|
+
assert_eq!(between[0].start.quantity(), Days::new(1.0));
|
|
40
|
+
assert_eq!(between[0].end.quantity(), Days::new(2.0));
|
|
41
|
+
assert_eq!(between[1].start.quantity(), Days::new(5.0));
|
|
42
|
+
assert_eq!(between[1].end.quantity(), Days::new(7.0));
|
|
43
|
+
assert_eq!(between[2].start.quantity(), Days::new(8.0));
|
|
44
|
+
assert_eq!(between[2].end.quantity(), Days::new(9.0));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#[cfg(feature = "serde")]
|
|
48
|
+
#[test]
|
|
49
|
+
fn serde_period_mjd_uses_legacy_field_names() {
|
|
50
|
+
let period = Period::new(
|
|
51
|
+
ModifiedJulianDate::new(59_000.25),
|
|
52
|
+
ModifiedJulianDate::new(59_000.75),
|
|
53
|
+
);
|
|
54
|
+
let json = serde_json::to_string(&period).unwrap();
|
|
55
|
+
assert!(json.contains("start_mjd"));
|
|
56
|
+
assert!(json.contains("end_mjd"));
|
|
57
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tempoch-core"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
authors = ["VPRamon <vallespuigramon@gmail.com>"]
|
|
6
|
+
description = "Core astronomical time primitives for tempoch."
|
|
7
|
+
license = "AGPL-3.0-only"
|
|
8
|
+
repository = "https://github.com/Siderust/tempoch"
|
|
9
|
+
|
|
10
|
+
[features]
|
|
11
|
+
default = []
|
|
12
|
+
serde = ["dep:serde"]
|
|
13
|
+
|
|
14
|
+
[lib]
|
|
15
|
+
name = "tempoch_core"
|
|
16
|
+
path = "src/lib.rs"
|
|
17
|
+
|
|
18
|
+
[dependencies]
|
|
19
|
+
chrono = "0.4.43"
|
|
20
|
+
qtty = "0.4.0"
|
|
21
|
+
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
|
22
|
+
|
|
23
|
+
[dev-dependencies]
|
|
24
|
+
serde_json = "1.0"
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Vallés Puig, Ramon
|
|
3
|
+
|
|
4
|
+
//! # ΔT (Delta T) — UT↔TT Correction Layer
|
|
5
|
+
//!
|
|
6
|
+
//! This module implements a piecewise model for **ΔT = TT − UT** combining:
|
|
7
|
+
//!
|
|
8
|
+
//! * **Pre-1620**: Stephenson & Houlden (1986) quadratic approximations.
|
|
9
|
+
//! * **1620–1992**: Biennial interpolation table (Meeus ch. 9).
|
|
10
|
+
//! * **1992–2025**: Annual observed ΔT values from IERS/USNO (Bulletin A).
|
|
11
|
+
//! * **Post-2025**: Linear extrapolation at the current observed rate
|
|
12
|
+
//! (~+0.1 s/yr), far more accurate than the Meeus quadratic formula
|
|
13
|
+
//! which diverges to ~120 s by 2020. The IERS-observed value for 2025
|
|
14
|
+
//! is ~69.36 s.
|
|
15
|
+
//!
|
|
16
|
+
//! ## Integration with Time Scales
|
|
17
|
+
//!
|
|
18
|
+
//! The correction is applied **automatically** by the [`UT`](super::UT) time
|
|
19
|
+
//! scale marker. When you convert from `Time<UT>` to any TT-based scale
|
|
20
|
+
//! (`.to::<JD>()`, `.to::<MJD>()`, etc.), `UT::to_jd_tt` adds ΔT.
|
|
21
|
+
//! The inverse (`UT::from_jd_tt`) uses a three-iteration fixed-point solver.
|
|
22
|
+
//!
|
|
23
|
+
//! [`Time::from_utc`](super::Time::from_utc) creates a `Time<UT>` internally
|
|
24
|
+
//! and then converts to the target scale, so external callers get the ΔT
|
|
25
|
+
//! correction without calling any function from this module.
|
|
26
|
+
//!
|
|
27
|
+
//! ## Quick Example
|
|
28
|
+
//! ```rust
|
|
29
|
+
//! # use tempoch_core as tempoch;
|
|
30
|
+
//! use tempoch::{UT, JD, Time};
|
|
31
|
+
//!
|
|
32
|
+
//! // UT-based Julian Day -> JD(TT) with ΔT applied
|
|
33
|
+
//! let ut = Time::<UT>::new(2_451_545.0);
|
|
34
|
+
//! let jd_tt = ut.to::<JD>();
|
|
35
|
+
//! println!("JD(TT) = {jd_tt}");
|
|
36
|
+
//!
|
|
37
|
+
//! // Query the raw ΔT value
|
|
38
|
+
//! let dt = ut.delta_t();
|
|
39
|
+
//! println!("ΔT = {dt}");
|
|
40
|
+
//! ```
|
|
41
|
+
//!
|
|
42
|
+
//! ## Scientific References
|
|
43
|
+
//! * Stephenson & Houlden (1986): *Atlas of Historical Eclipse Maps*.
|
|
44
|
+
//! * Morrison & Stephenson (2004): "Historical values of the Earth's clock error".
|
|
45
|
+
//! * IERS Conventions (2020): official ΔT data tables.
|
|
46
|
+
//! * IERS Bulletin A (2025): observed ΔT values.
|
|
47
|
+
//!
|
|
48
|
+
//! ## Valid Time Range
|
|
49
|
+
//! The algorithm is valid from ancient times through approximately 2035, with
|
|
50
|
+
//! typical uncertainties ≤ ±2 s before 1800 CE, ≤ ±0.5 s since 1900, and
|
|
51
|
+
//! ≤ ±0.1 s for 2000–2025 (observed data).
|
|
52
|
+
|
|
53
|
+
use super::instant::Time;
|
|
54
|
+
use super::scales::UT;
|
|
55
|
+
use super::JulianDate;
|
|
56
|
+
use qtty::{Days, Seconds, Simplify};
|
|
57
|
+
|
|
58
|
+
/// Total number of tabulated terms (biennial 1620–1992).
|
|
59
|
+
const TERMS: usize = 187;
|
|
60
|
+
|
|
61
|
+
/// Biennial ΔT table from 1620 to 1992 (in seconds), compiled by J. Meeus.
|
|
62
|
+
#[rustfmt::skip]
|
|
63
|
+
const DELTA_T: [Seconds; TERMS] = qtty::qtty_vec!(
|
|
64
|
+
Seconds;
|
|
65
|
+
124.0,115.0,106.0, 98.0, 91.0, 85.0, 79.0, 74.0, 70.0, 65.0,
|
|
66
|
+
62.0, 58.0, 55.0, 53.0, 50.0, 48.0, 46.0, 44.0, 42.0, 40.0,
|
|
67
|
+
37.0, 35.0, 33.0, 31.0, 28.0, 26.0, 24.0, 22.0, 20.0, 18.0,
|
|
68
|
+
16.0, 14.0, 13.0, 12.0, 11.0, 10.0, 9.0, 9.0, 9.0, 9.0,
|
|
69
|
+
9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 10.0, 10.0, 10.0, 11.0,
|
|
70
|
+
11.0, 11.0, 11.0, 11.0, 11.0, 11.0, 12.0, 12.0, 12.0, 12.0,
|
|
71
|
+
12.0, 12.0, 13.0, 13.0, 13.0, 13.0, 14.0, 14.0, 14.0, 15.0,
|
|
72
|
+
15.0, 15.0, 15.0, 16.0, 16.0, 16.0, 16.0, 16.0, 17.0, 17.0,
|
|
73
|
+
17.0, 17.0, 17.0, 17.0, 17.0, 17.0, 16.0, 16.0, 15.0, 14.0,
|
|
74
|
+
13.7, 13.1, 12.7, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.3,
|
|
75
|
+
12.0, 11.4, 10.6, 9.6, 8.6, 7.5, 6.6, 6.0, 5.7, 5.6,
|
|
76
|
+
5.7, 5.9, 6.2, 6.5, 6.8, 7.1, 7.3, 7.5, 7.7, 7.8,
|
|
77
|
+
7.9, 7.5, 6.4, 5.4, 2.9, 1.6, -1.0, -2.7, -3.6, -4.7,
|
|
78
|
+
-5.4, -5.2, -5.5, -5.6, -5.8, -5.9, -6.2, -6.4, -6.1, -4.7,
|
|
79
|
+
-2.7, 0.0, 2.6, 5.4, 7.7, 10.5, 13.4, 16.0, 18.2, 20.2,
|
|
80
|
+
21.2, 22.4, 23.5, 23.9, 24.3, 24.0, 23.9, 23.9, 23.7, 24.0,
|
|
81
|
+
24.3, 25.3, 26.2, 27.3, 28.2, 29.1, 30.0, 30.7, 31.4, 32.2,
|
|
82
|
+
33.1, 34.0, 35.0, 36.5, 38.3, 40.2, 42.2, 44.5, 46.5, 48.5,
|
|
83
|
+
50.5, 52.2, 53.8, 54.9, 55.8, 56.9, 58.3,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// ------------------------------------------------------------------------------------
|
|
87
|
+
// Annual observed ΔT table 1992–2025 (IERS/USNO Bulletin A)
|
|
88
|
+
// ------------------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
/// Annual ΔT values (seconds) from IERS/USNO observations, 1992.0–2025.0.
|
|
91
|
+
/// Index 0 = year 1992, index 33 = year 2025.
|
|
92
|
+
/// Source: IERS Bulletin A, USNO finals2000A data.
|
|
93
|
+
const OBSERVED_TERMS: usize = 34;
|
|
94
|
+
const OBSERVED_START_YEAR: f64 = 1992.0;
|
|
95
|
+
|
|
96
|
+
#[rustfmt::skip]
|
|
97
|
+
const OBSERVED_DT: [Seconds; OBSERVED_TERMS] = qtty::qtty_vec!(
|
|
98
|
+
Seconds;
|
|
99
|
+
// 1992 1993 1994 1995 1996 1997 1998 1999
|
|
100
|
+
58.31, 59.12, 59.98, 60.78, 61.63, 62.30, 62.97, 63.47,
|
|
101
|
+
// 2000 2001 2002 2003 2004 2005 2006 2007
|
|
102
|
+
63.83, 64.09, 64.30, 64.47, 64.57, 64.69, 64.85, 65.15,
|
|
103
|
+
// 2008 2009 2010 2011 2012 2013 2014 2015
|
|
104
|
+
65.46, 65.78, 66.07, 66.32, 66.60, 66.91, 67.28, 67.64,
|
|
105
|
+
// 2016 2017 2018 2019 2020 2021 2022 2023
|
|
106
|
+
68.10, 68.59, 68.97, 69.22, 69.36, 69.36, 69.29, 69.18,
|
|
107
|
+
// 2024 2025
|
|
108
|
+
69.09, 69.36,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/// The year after the last observed data point. Beyond this we extrapolate.
|
|
112
|
+
const OBSERVED_END_YEAR: f64 = OBSERVED_START_YEAR + OBSERVED_TERMS as f64;
|
|
113
|
+
|
|
114
|
+
/// Last observed ΔT rate (seconds/year). Computed from the last 5 years of
|
|
115
|
+
/// observed data. The rate has been nearly flat 2019–2025 (~+0.02 s/yr).
|
|
116
|
+
const EXTRAPOLATION_RATE: f64 = 0.02;
|
|
117
|
+
|
|
118
|
+
// ------------------------------------------------------------------------------------
|
|
119
|
+
// ΔT Approximation Sections by Time Interval
|
|
120
|
+
// ------------------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
/// **Years < 948 CE**
|
|
123
|
+
/// Quadratic formula from Stephenson & Houlden (1986).
|
|
124
|
+
#[inline]
|
|
125
|
+
fn delta_t_ancient(jd: JulianDate) -> Seconds {
|
|
126
|
+
const DT_A0_S: Seconds = Seconds::new(1_830.0);
|
|
127
|
+
const DT_A1_S: Seconds = Seconds::new(-405.0);
|
|
128
|
+
const DT_A2_S: Seconds = Seconds::new(46.5);
|
|
129
|
+
const JD_EPOCH_948_UT: JulianDate = JulianDate::new(2_067_314.5);
|
|
130
|
+
let c = days_ratio(jd - JD_EPOCH_948_UT, JulianDate::JULIAN_CENTURY);
|
|
131
|
+
DT_A0_S + DT_A1_S * c + DT_A2_S * c * c
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// **Years 948–1600 CE**
|
|
135
|
+
/// Second polynomial from Stephenson & Houlden (1986).
|
|
136
|
+
#[inline]
|
|
137
|
+
fn delta_t_medieval(jd: JulianDate) -> Seconds {
|
|
138
|
+
const JD_EPOCH_1850_UT: JulianDate = JulianDate::new(2_396_758.5);
|
|
139
|
+
const DT_A2_S: Seconds = Seconds::new(22.5);
|
|
140
|
+
|
|
141
|
+
let c = days_ratio(jd - JD_EPOCH_1850_UT, JulianDate::JULIAN_CENTURY);
|
|
142
|
+
DT_A2_S * c * c
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// **Years 1600–1992**
|
|
146
|
+
/// Bicubic interpolation from the biennial `DELTA_T` table.
|
|
147
|
+
#[inline]
|
|
148
|
+
fn delta_t_table(jd: JulianDate) -> Seconds {
|
|
149
|
+
const JD_TABLE_START_1620: JulianDate = JulianDate::new(2_312_752.5);
|
|
150
|
+
const BIENNIAL_STEP_D: Days = Days::new(730.5);
|
|
151
|
+
|
|
152
|
+
let mut i = days_ratio(jd - JD_TABLE_START_1620, BIENNIAL_STEP_D) as usize;
|
|
153
|
+
if i > TERMS - 3 {
|
|
154
|
+
i = TERMS - 3;
|
|
155
|
+
}
|
|
156
|
+
let a: Seconds = DELTA_T[i + 1] - DELTA_T[i];
|
|
157
|
+
let b: Seconds = DELTA_T[i + 2] - DELTA_T[i + 1];
|
|
158
|
+
let c: Seconds = a - b;
|
|
159
|
+
let n = days_ratio(
|
|
160
|
+
jd - (JD_TABLE_START_1620 + BIENNIAL_STEP_D * i as f64),
|
|
161
|
+
BIENNIAL_STEP_D,
|
|
162
|
+
);
|
|
163
|
+
DELTA_T[i + 1] + n / 2.0 * (a + b + n * c)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// **Years 1992–2026**
|
|
167
|
+
/// Linear interpolation from annual IERS/USNO observed ΔT values.
|
|
168
|
+
#[inline]
|
|
169
|
+
fn delta_t_observed(jd: JulianDate) -> Seconds {
|
|
170
|
+
// Convert JD to fractional year
|
|
171
|
+
let year = 2000.0 + (jd - JulianDate::J2000).value() / 365.25;
|
|
172
|
+
let idx_f = year - OBSERVED_START_YEAR;
|
|
173
|
+
let idx = idx_f as usize;
|
|
174
|
+
|
|
175
|
+
if idx + 1 >= OBSERVED_TERMS {
|
|
176
|
+
// At the very end of the table, return the last value
|
|
177
|
+
return OBSERVED_DT[OBSERVED_TERMS - 1];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Linear interpolation between annual values
|
|
181
|
+
let frac = idx_f - idx as f64;
|
|
182
|
+
OBSERVED_DT[idx] + frac * (OBSERVED_DT[idx + 1] - OBSERVED_DT[idx])
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// **Years > 2026**
|
|
186
|
+
/// Linear extrapolation from the last observed value at the current rate.
|
|
187
|
+
///
|
|
188
|
+
/// The observed ΔT trend 2019–2025 is nearly flat (~+0.02 s/yr), which is
|
|
189
|
+
/// far more accurate than the Meeus quadratic that predicted ~121 s for 2020
|
|
190
|
+
/// vs the observed ~69.36 s.
|
|
191
|
+
#[inline]
|
|
192
|
+
fn delta_t_extrapolated(jd: JulianDate) -> Seconds {
|
|
193
|
+
let year = 2000.0 + (jd - JulianDate::J2000).value() / 365.25;
|
|
194
|
+
let dt_last = OBSERVED_DT[OBSERVED_TERMS - 1];
|
|
195
|
+
let years_past = year - OBSERVED_END_YEAR;
|
|
196
|
+
dt_last + Seconds::new(EXTRAPOLATION_RATE * years_past)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[inline]
|
|
200
|
+
fn days_ratio(num: Days, den: Days) -> f64 {
|
|
201
|
+
(num / den).simplify().value()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// JD boundary: start of year 1992.0
|
|
205
|
+
const JD_1992: JulianDate = JulianDate::new(2_448_622.5);
|
|
206
|
+
|
|
207
|
+
/// JD boundary: start of year 2026.0
|
|
208
|
+
const JD_2026: JulianDate = JulianDate::new(2_461_041.5);
|
|
209
|
+
|
|
210
|
+
/// Returns **ΔT** in seconds for a Julian Day on the **UT** axis.
|
|
211
|
+
#[inline]
|
|
212
|
+
pub(crate) fn delta_t_seconds_from_ut(jd_ut: JulianDate) -> Seconds {
|
|
213
|
+
match jd_ut {
|
|
214
|
+
jd if jd < JulianDate::new(2_067_314.5) => delta_t_ancient(jd),
|
|
215
|
+
jd if jd < JulianDate::new(2_305_447.5) => delta_t_medieval(jd),
|
|
216
|
+
jd if jd < JD_1992 => delta_t_table(jd),
|
|
217
|
+
jd if jd < JD_2026 => delta_t_observed(jd),
|
|
218
|
+
_ => delta_t_extrapolated(jd_ut),
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Time<UT> convenience method ───────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
impl Time<UT> {
|
|
225
|
+
/// Returns **ΔT = TT − UT** in seconds for this UT epoch.
|
|
226
|
+
///
|
|
227
|
+
/// This is a convenience accessor; the same correction is applied
|
|
228
|
+
/// automatically when converting to any TT-based scale (`.to::<JD>()`).
|
|
229
|
+
#[inline]
|
|
230
|
+
pub fn delta_t(&self) -> Seconds {
|
|
231
|
+
delta_t_seconds_from_ut(JulianDate::from_days(self.quantity()))
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#[cfg(test)]
|
|
236
|
+
mod tests {
|
|
237
|
+
use super::*;
|
|
238
|
+
use qtty::{Day, Days};
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn delta_t_ancient_sample() {
|
|
242
|
+
let dt = delta_t_seconds_from_ut(JulianDate::new(2_000_000.0));
|
|
243
|
+
assert!((dt - Seconds::new(2_734.342_214_024_879_5)).abs() < Seconds::new(1e-6));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn delta_t_medieval_sample() {
|
|
248
|
+
let dt = delta_t_seconds_from_ut(JulianDate::new(2_100_000.0));
|
|
249
|
+
assert!((dt - Seconds::new(1_485.280_240_204_242_3)).abs() < Seconds::new(1e-6));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[test]
|
|
253
|
+
fn delta_t_table_sample() {
|
|
254
|
+
let dt = delta_t_seconds_from_ut(JulianDate::new(2_312_752.5));
|
|
255
|
+
assert!((dt - Seconds::new(115.0)).abs() < Seconds::new(1e-6));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#[test]
|
|
259
|
+
fn delta_t_table_upper_clip() {
|
|
260
|
+
let dt = delta_t_table(JulianDate::new(2_449_356.0));
|
|
261
|
+
assert!((dt - Seconds::new(59.3)).abs() < Seconds::new(1e-6));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[test]
|
|
265
|
+
fn delta_t_2000() {
|
|
266
|
+
// IERS observed value: 63.83 s
|
|
267
|
+
let dt = delta_t_seconds_from_ut(JulianDate::J2000);
|
|
268
|
+
assert!(
|
|
269
|
+
(dt - Seconds::new(63.83)).abs() < Seconds::new(0.1),
|
|
270
|
+
"ΔT at J2000 = {dt}, expected 63.83 s"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[test]
|
|
275
|
+
fn delta_t_2010() {
|
|
276
|
+
// IERS observed value for 2010.0: ~66.07 s
|
|
277
|
+
// JD 2455197.5 ≈ 2010-01-01
|
|
278
|
+
let dt = delta_t_seconds_from_ut(JulianDate::new(2_455_197.5));
|
|
279
|
+
assert!(
|
|
280
|
+
(dt - Seconds::new(66.07)).abs() < Seconds::new(0.5),
|
|
281
|
+
"ΔT at 2010. = {dt}, expected ~66.07 s"
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#[test]
|
|
286
|
+
fn delta_t_2020() {
|
|
287
|
+
// IERS observed value for 2020.0: ~69.36 s
|
|
288
|
+
// The old Meeus extrapolation gave ~121 s here — way off.
|
|
289
|
+
// JD for 2020-01-01 ≈ 2458849.5
|
|
290
|
+
let dt = delta_t_seconds_from_ut(JulianDate::new(2_458_849.5));
|
|
291
|
+
assert!(
|
|
292
|
+
(dt - Seconds::new(69.36)).abs() < Seconds::new(0.5),
|
|
293
|
+
"ΔT at 2020.0 = {dt}, expected ~69.36 s"
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#[test]
|
|
298
|
+
fn delta_t_2025() {
|
|
299
|
+
// IERS observed value for 2025.0: ~69.36 s
|
|
300
|
+
// JD for 2025-01-01 ≈ 2460676.5
|
|
301
|
+
let dt = delta_t_seconds_from_ut(JulianDate::new(2_460_676.5));
|
|
302
|
+
assert!(
|
|
303
|
+
(dt - Seconds::new(69.36)).abs() < Seconds::new(0.5),
|
|
304
|
+
"ΔT at 2025.0 = {dt}, expected ~69.36 s"
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#[test]
|
|
309
|
+
fn delta_t_extrapolated_near_future() {
|
|
310
|
+
// Beyond 2026, linear extrapolation at ~0.02 s/yr
|
|
311
|
+
// At 2030.0 (4 yr past end), ΔT ≈ 69.36 + 0.02*4 ≈ 69.44
|
|
312
|
+
let jd_2030 = JulianDate::new(2_462_502.5);
|
|
313
|
+
let dt = delta_t_seconds_from_ut(jd_2030);
|
|
314
|
+
assert!(
|
|
315
|
+
(dt - Seconds::new(69.44)).abs() < Seconds::new(1.0),
|
|
316
|
+
"ΔT at 2030. = {dt}, expected ~69.44 s"
|
|
317
|
+
);
|
|
318
|
+
// Must NOT be the old ~135+ s value
|
|
319
|
+
assert!(dt < Seconds::new(75.0), "ΔT at 2030 is too large: {dt}");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#[test]
|
|
323
|
+
fn ut_scale_applies_delta_t() {
|
|
324
|
+
let ut = Time::<UT>::new(2_451_545.0);
|
|
325
|
+
let jd_tt = ut.to::<crate::JD>();
|
|
326
|
+
let offset = jd_tt - JulianDate::new(2_451_545.0);
|
|
327
|
+
let expected = delta_t_seconds_from_ut(JulianDate::new(2_451_545.0)).to::<Day>();
|
|
328
|
+
assert!((offset - expected).abs() < Days::new(1e-9));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
#[test]
|
|
332
|
+
fn ut_scale_roundtrip() {
|
|
333
|
+
let jd_tt = JulianDate::new(2_451_545.0);
|
|
334
|
+
let ut: Time<UT> = jd_tt.to::<UT>();
|
|
335
|
+
let back: JulianDate = ut.to::<crate::JD>();
|
|
336
|
+
assert!((back - jd_tt).abs() < Days::new(1e-12));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[test]
|
|
340
|
+
fn delta_t_convenience_method() {
|
|
341
|
+
let ut = Time::<UT>::new(2_451_545.0);
|
|
342
|
+
let dt = ut.delta_t();
|
|
343
|
+
assert!((dt - Seconds::new(63.83)).abs() < Seconds::new(0.5));
|
|
344
|
+
}
|
|
345
|
+
}
|