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.
Files changed (272) hide show
  1. package/.github/workflows/ci.yml +166 -0
  2. package/.gitmodules +9 -0
  3. package/CHANGELOG.md +26 -0
  4. package/LICENSE +661 -0
  5. package/README.md +138 -0
  6. package/package.json +12 -0
  7. package/qtty-js/.github/workflows/ci.yml +151 -0
  8. package/qtty-js/.gitmodules +3 -0
  9. package/qtty-js/CHANGELOG.md +31 -0
  10. package/qtty-js/LICENSE +661 -0
  11. package/qtty-js/README.md +132 -0
  12. package/qtty-js/package.json +20 -0
  13. package/qtty-js/qtty/.github/workflows/ci.yml +155 -0
  14. package/qtty-js/qtty/CHANGELOG.md +120 -0
  15. package/qtty-js/qtty/Cargo.lock +1462 -0
  16. package/qtty-js/qtty/Cargo.toml +12 -0
  17. package/qtty-js/qtty/LICENSE +661 -0
  18. package/qtty-js/qtty/README.md +9 -0
  19. package/qtty-js/qtty/qtty/Cargo.toml +41 -0
  20. package/qtty-js/qtty/qtty/README.md +8 -0
  21. package/qtty-js/qtty/qtty/examples/angles.rs +14 -0
  22. package/qtty-js/qtty/qtty/examples/astronomy.rs +17 -0
  23. package/qtty-js/qtty/qtty/examples/dimensional_arithmetic.rs +83 -0
  24. package/qtty-js/qtty/qtty/examples/python_integration.rs +61 -0
  25. package/qtty-js/qtty/qtty/examples/quickstart.rs +15 -0
  26. package/qtty-js/qtty/qtty/examples/ratios.rs +12 -0
  27. package/qtty-js/qtty/qtty/examples/serde_with_unit.rs +234 -0
  28. package/qtty-js/qtty/qtty/examples/serialization.rs +141 -0
  29. package/qtty-js/qtty/qtty/examples/serialization_advanced.rs +155 -0
  30. package/qtty-js/qtty/qtty/src/f32.rs +108 -0
  31. package/qtty-js/qtty/qtty/src/f64.rs +30 -0
  32. package/qtty-js/qtty/qtty/src/i128.rs +111 -0
  33. package/qtty-js/qtty/qtty/src/i16.rs +111 -0
  34. package/qtty-js/qtty/qtty/src/i32.rs +111 -0
  35. package/qtty-js/qtty/qtty/src/i64.rs +111 -0
  36. package/qtty-js/qtty/qtty/src/i8.rs +111 -0
  37. package/qtty-js/qtty/qtty/src/lib.rs +238 -0
  38. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.lock +83 -0
  39. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.toml +10 -0
  40. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/src/lib.rs +7 -0
  41. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.lock +83 -0
  42. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.toml +10 -0
  43. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/src/lib.rs +7 -0
  44. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.lock +83 -0
  45. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.toml +10 -0
  46. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/src/lib.rs +5 -0
  47. package/qtty-js/qtty/qtty/tests/integration_tests.rs +529 -0
  48. package/qtty-js/qtty/qtty/tests/qtty_vec_feature_matrix.rs +58 -0
  49. package/qtty-js/qtty/qtty-core/Cargo.toml +41 -0
  50. package/qtty-js/qtty/qtty-core/README.md +8 -0
  51. package/qtty-js/qtty/qtty-core/examples/diesel_integration.rs +145 -0
  52. package/qtty-js/qtty/qtty-core/examples/quantity_db_serde.rs +215 -0
  53. package/qtty-js/qtty/qtty-core/src/dimension.rs +249 -0
  54. package/qtty-js/qtty/qtty-core/src/feature_diesel.rs +318 -0
  55. package/qtty-js/qtty/qtty-core/src/feature_pyo3.rs +27 -0
  56. package/qtty-js/qtty/qtty-core/src/feature_serde.rs +203 -0
  57. package/qtty-js/qtty/qtty-core/src/feature_tiberius.rs +28 -0
  58. package/qtty-js/qtty/qtty-core/src/lib.rs +744 -0
  59. package/qtty-js/qtty/qtty-core/src/macros.rs +93 -0
  60. package/qtty-js/qtty/qtty-core/src/quantity.rs +810 -0
  61. package/qtty-js/qtty/qtty-core/src/scalar.rs +1742 -0
  62. package/qtty-js/qtty/qtty-core/src/unit.rs +332 -0
  63. package/qtty-js/qtty/qtty-core/src/units/angular.rs +1228 -0
  64. package/qtty-js/qtty/qtty-core/src/units/area.rs +243 -0
  65. package/qtty-js/qtty/qtty-core/src/units/frequency.rs +179 -0
  66. package/qtty-js/qtty/qtty-core/src/units/length.rs +1270 -0
  67. package/qtty-js/qtty/qtty-core/src/units/mass.rs +488 -0
  68. package/qtty-js/qtty/qtty-core/src/units/mod.rs +26 -0
  69. package/qtty-js/qtty/qtty-core/src/units/power.rs +324 -0
  70. package/qtty-js/qtty/qtty-core/src/units/time.rs +667 -0
  71. package/qtty-js/qtty/qtty-core/src/units/unitless.rs +212 -0
  72. package/qtty-js/qtty/qtty-core/src/units/velocity.rs +210 -0
  73. package/qtty-js/qtty/qtty-core/src/units/volume.rs +269 -0
  74. package/qtty-js/qtty/qtty-core/tests/core.rs +628 -0
  75. package/qtty-js/qtty/qtty-core/tests/diesel.rs +461 -0
  76. package/qtty-js/qtty/qtty-core/tests/integers.rs +632 -0
  77. package/qtty-js/qtty/qtty-core/tests/no_cross_unit_ops.rs +35 -0
  78. package/qtty-js/qtty/qtty-core/tests/pyo3.rs +334 -0
  79. package/qtty-js/qtty/qtty-core/tests/quantity_f32.rs +276 -0
  80. package/qtty-js/qtty/qtty-core/tests/scalar_decimal.rs +258 -0
  81. package/qtty-js/qtty/qtty-core/tests/scalar_f32.rs +286 -0
  82. package/qtty-js/qtty/qtty-core/tests/scalar_f64_real.rs +287 -0
  83. package/qtty-js/qtty/qtty-core/tests/scalar_rational.rs +260 -0
  84. package/qtty-js/qtty/qtty-core/tests/serde.rs +256 -0
  85. package/qtty-js/qtty/qtty-core/tests/tiberius.rs +208 -0
  86. package/qtty-js/qtty/qtty-derive/Cargo.toml +23 -0
  87. package/qtty-js/qtty/qtty-derive/README.md +8 -0
  88. package/qtty-js/qtty/qtty-derive/src/lib.rs +340 -0
  89. package/qtty-js/qtty/qtty-ffi/ARCHITECTURE.md +3 -0
  90. package/qtty-js/qtty/qtty-ffi/Cargo.toml +31 -0
  91. package/qtty-js/qtty/qtty-ffi/README.md +9 -0
  92. package/qtty-js/qtty/qtty-ffi/build.rs +326 -0
  93. package/qtty-js/qtty/qtty-ffi/cbindgen.toml +105 -0
  94. package/qtty-js/qtty/qtty-ffi/include/qtty_ffi.h +1126 -0
  95. package/qtty-js/qtty/qtty-ffi/src/ffi.rs +1251 -0
  96. package/qtty-js/qtty/qtty-ffi/src/ffi_serde.rs +294 -0
  97. package/qtty-js/qtty/qtty-ffi/src/helpers.rs +310 -0
  98. package/qtty-js/qtty/qtty-ffi/src/lib.rs +229 -0
  99. package/qtty-js/qtty/qtty-ffi/src/macros.rs +121 -0
  100. package/qtty-js/qtty/qtty-ffi/src/registry.rs +274 -0
  101. package/qtty-js/qtty/qtty-ffi/src/types.rs +620 -0
  102. package/qtty-js/qtty/qtty-ffi/tests/integration_tests.rs +842 -0
  103. package/qtty-js/qtty/qtty-ffi/units.csv +156 -0
  104. package/qtty-js/qtty/qtty-ffi/units.csv.md +3 -0
  105. package/qtty-js/qtty-node/.prettierignore +6 -0
  106. package/qtty-js/qtty-node/.prettierrc.json +6 -0
  107. package/qtty-js/qtty-node/README.md +250 -0
  108. package/qtty-js/qtty-node/c8.config.json +11 -0
  109. package/qtty-js/qtty-node/eslint.config.js +31 -0
  110. package/qtty-js/qtty-node/examples/arithmetic.mjs +64 -0
  111. package/qtty-js/qtty-node/examples/astronomy.mjs +90 -0
  112. package/qtty-js/qtty-node/examples/quickstart.mjs +36 -0
  113. package/qtty-js/qtty-node/examples/serialization.mjs +125 -0
  114. package/qtty-js/qtty-node/examples/unit_factories.mjs +74 -0
  115. package/qtty-js/qtty-node/index.d.ts +219 -0
  116. package/qtty-js/qtty-node/index.js +323 -0
  117. package/qtty-js/qtty-node/lib/DerivedQuantity.js +122 -0
  118. package/qtty-js/qtty-node/lib/Quantity.js +151 -0
  119. package/qtty-js/qtty-node/lib/backend.js +25 -0
  120. package/qtty-js/qtty-node/native.cjs +306 -0
  121. package/qtty-js/qtty-node/package-lock.json +3223 -0
  122. package/qtty-js/qtty-node/package.json +70 -0
  123. package/qtty-js/qtty-node/units.d.ts +299 -0
  124. package/qtty-js/qtty-node/units.js +210 -0
  125. package/qtty-js/qtty-web/Cargo.lock +767 -0
  126. package/qtty-js/qtty-web/Cargo.toml +21 -0
  127. package/qtty-js/qtty-web/index.d.ts +140 -0
  128. package/qtty-js/qtty-web/index.js +20 -0
  129. package/qtty-js/qtty-web/lib/DerivedQuantity.js +58 -0
  130. package/qtty-js/qtty-web/lib/Quantity.js +75 -0
  131. package/qtty-js/qtty-web/lib/backend.js +80 -0
  132. package/qtty-js/qtty-web/package.json +45 -0
  133. package/qtty-js/qtty-web/src/lib.rs +111 -0
  134. package/qtty-js/scripts/ci.sh +73 -0
  135. package/scripts/ci.sh +123 -0
  136. package/siderust-core/Cargo.lock +787 -0
  137. package/siderust-core/Cargo.toml +18 -0
  138. package/siderust-core/DEDUPLICATION.md +124 -0
  139. package/siderust-core/src/body.rs +120 -0
  140. package/siderust-core/src/events.rs +184 -0
  141. package/siderust-core/src/lib.rs +20 -0
  142. package/siderust-core/src/observer.rs +55 -0
  143. package/siderust-core/src/position.rs +213 -0
  144. package/siderust-node/.prettierignore +7 -0
  145. package/siderust-node/.prettierrc.json +6 -0
  146. package/siderust-node/Cargo.lock +906 -0
  147. package/siderust-node/Cargo.toml +29 -0
  148. package/siderust-node/README.md +109 -0
  149. package/siderust-node/__test__/index.test.mjs +248 -0
  150. package/siderust-node/build.rs +5 -0
  151. package/siderust-node/c8.config.json +3 -0
  152. package/siderust-node/eslint.config.js +31 -0
  153. package/siderust-node/examples/01_basic_coordinates.mjs +24 -0
  154. package/siderust-node/examples/02_coordinate_transformations.mjs +25 -0
  155. package/siderust-node/examples/03_all_frames_conversions.mjs +26 -0
  156. package/siderust-node/examples/04_all_center_conversions.mjs +24 -0
  157. package/siderust-node/examples/05_target_tracking.mjs +22 -0
  158. package/siderust-node/examples/06_night_events.mjs +18 -0
  159. package/siderust-node/examples/07_moon_properties.mjs +21 -0
  160. package/siderust-node/examples/08_solar_system.mjs +19 -0
  161. package/siderust-node/examples/09_star_observability.mjs +22 -0
  162. package/siderust-node/examples/10_time_periods.mjs +9 -0
  163. package/siderust-node/examples/11_serialization.mjs +31 -0
  164. package/siderust-node/examples/12_runtime_ephemeris.mjs +27 -0
  165. package/siderust-node/examples/13_coordinate_operations.mjs +20 -0
  166. package/siderust-node/index.d.ts +623 -0
  167. package/siderust-node/index.js +79 -0
  168. package/siderust-node/lib/Observer.js +112 -0
  169. package/siderust-node/lib/Star.js +118 -0
  170. package/siderust-node/lib/backend.js +63 -0
  171. package/siderust-node/lib/wrappers.js +566 -0
  172. package/siderust-node/main.js +20 -0
  173. package/siderust-node/native.cjs +360 -0
  174. package/siderust-node/package-lock.json +3261 -0
  175. package/siderust-node/package.json +71 -0
  176. package/siderust-node/src/body.rs +74 -0
  177. package/siderust-node/src/coordinates.rs +372 -0
  178. package/siderust-node/src/ephemeris.rs +462 -0
  179. package/siderust-node/src/events.rs +577 -0
  180. package/siderust-node/src/lib.rs +43 -0
  181. package/siderust-node/src/observer.rs +132 -0
  182. package/siderust-node/src/phase.rs +218 -0
  183. package/siderust-node/src/position.rs +292 -0
  184. package/siderust-node/src/star.rs +200 -0
  185. package/siderust-web/Cargo.lock +855 -0
  186. package/siderust-web/Cargo.toml +34 -0
  187. package/siderust-web/README.md +100 -0
  188. package/siderust-web/__test__/index.test.mjs +118 -0
  189. package/siderust-web/examples/github-pages/README.md +31 -0
  190. package/siderust-web/examples/github-pages/index.html +135 -0
  191. package/siderust-web/index.d.ts +311 -0
  192. package/siderust-web/index.js +66 -0
  193. package/siderust-web/lib/Observer.js +103 -0
  194. package/siderust-web/lib/Star.js +116 -0
  195. package/siderust-web/lib/backend.js +400 -0
  196. package/siderust-web/lib/wrappers.js +512 -0
  197. package/siderust-web/package.json +55 -0
  198. package/siderust-web/src/body.rs +69 -0
  199. package/siderust-web/src/coordinates.rs +302 -0
  200. package/siderust-web/src/ephemeris.rs +456 -0
  201. package/siderust-web/src/events.rs +520 -0
  202. package/siderust-web/src/lib.rs +51 -0
  203. package/siderust-web/src/observer.rs +117 -0
  204. package/siderust-web/src/phase.rs +190 -0
  205. package/siderust-web/src/position.rs +291 -0
  206. package/siderust-web/src/star.rs +178 -0
  207. package/tempoch-js/.github/workflows/ci.yml +142 -0
  208. package/tempoch-js/.gitmodules +3 -0
  209. package/tempoch-js/CHANGELOG.md +25 -0
  210. package/tempoch-js/LICENSE +661 -0
  211. package/tempoch-js/README.md +126 -0
  212. package/tempoch-js/package.json +20 -0
  213. package/tempoch-js/scripts/ci.sh +73 -0
  214. package/tempoch-js/tempoch/.github/workflows/ci.yml +113 -0
  215. package/tempoch-js/tempoch/CHANGELOG.md +82 -0
  216. package/tempoch-js/tempoch/Cargo.lock +947 -0
  217. package/tempoch-js/tempoch/Cargo.toml +3 -0
  218. package/tempoch-js/tempoch/LICENSE +661 -0
  219. package/tempoch-js/tempoch/README.md +76 -0
  220. package/tempoch-js/tempoch/tempoch/Cargo.toml +27 -0
  221. package/tempoch-js/tempoch/tempoch/examples/periods.rs +45 -0
  222. package/tempoch-js/tempoch/tempoch/examples/quickstart.rs +13 -0
  223. package/tempoch-js/tempoch/tempoch/src/lib.rs +49 -0
  224. package/tempoch-js/tempoch/tempoch/tests/integration.rs +57 -0
  225. package/tempoch-js/tempoch/tempoch-core/Cargo.toml +24 -0
  226. package/tempoch-js/tempoch/tempoch-core/src/delta_t.rs +345 -0
  227. package/tempoch-js/tempoch/tempoch-core/src/instant.rs +811 -0
  228. package/tempoch-js/tempoch/tempoch-core/src/julian_date_ext.rs +142 -0
  229. package/tempoch-js/tempoch/tempoch-core/src/lib.rs +81 -0
  230. package/tempoch-js/tempoch/tempoch-core/src/period.rs +1168 -0
  231. package/tempoch-js/tempoch/tempoch-core/src/scales.rs +779 -0
  232. package/tempoch-js/tempoch/tempoch-ffi/Cargo.lock +889 -0
  233. package/tempoch-js/tempoch/tempoch-ffi/Cargo.toml +26 -0
  234. package/tempoch-js/tempoch/tempoch-ffi/build.rs +24 -0
  235. package/tempoch-js/tempoch/tempoch-ffi/cbindgen.toml +30 -0
  236. package/tempoch-js/tempoch/tempoch-ffi/src/error.rs +19 -0
  237. package/tempoch-js/tempoch/tempoch-ffi/src/lib.rs +82 -0
  238. package/tempoch-js/tempoch/tempoch-ffi/src/period.rs +101 -0
  239. package/tempoch-js/tempoch/tempoch-ffi/src/time.rs +711 -0
  240. package/tempoch-js/tempoch/tempoch-ffi/tests/ffi.rs +265 -0
  241. package/tempoch-js/tempoch-node/.prettierignore +6 -0
  242. package/tempoch-js/tempoch-node/.prettierrc.json +6 -0
  243. package/tempoch-js/tempoch-node/Cargo.lock +496 -0
  244. package/tempoch-js/tempoch-node/Cargo.toml +29 -0
  245. package/tempoch-js/tempoch-node/README.md +265 -0
  246. package/tempoch-js/tempoch-node/__test__/index.test.mjs +598 -0
  247. package/tempoch-js/tempoch-node/build.rs +5 -0
  248. package/tempoch-js/tempoch-node/c8.config.json +3 -0
  249. package/tempoch-js/tempoch-node/eslint.config.js +31 -0
  250. package/tempoch-js/tempoch-node/examples/periods.mjs +79 -0
  251. package/tempoch-js/tempoch-node/examples/quickstart.mjs +71 -0
  252. package/tempoch-js/tempoch-node/examples/timescales.mjs +92 -0
  253. package/tempoch-js/tempoch-node/index.d.ts +280 -0
  254. package/tempoch-js/tempoch-node/index.js +32 -0
  255. package/tempoch-js/tempoch-node/lib/JulianDate.js +176 -0
  256. package/tempoch-js/tempoch-node/lib/ModifiedJulianDate.js +156 -0
  257. package/tempoch-js/tempoch-node/lib/Period.js +133 -0
  258. package/tempoch-js/tempoch-node/lib/backend.js +38 -0
  259. package/tempoch-js/tempoch-node/lib/qttyCompat.js +92 -0
  260. package/tempoch-js/tempoch-node/native.cjs +317 -0
  261. package/tempoch-js/tempoch-node/package-lock.json +3223 -0
  262. package/tempoch-js/tempoch-node/package.json +56 -0
  263. package/tempoch-js/tempoch-node/src/lib.rs +573 -0
  264. package/tempoch-js/tempoch-web/Cargo.toml +23 -0
  265. package/tempoch-js/tempoch-web/index.d.ts +95 -0
  266. package/tempoch-js/tempoch-web/index.js +27 -0
  267. package/tempoch-js/tempoch-web/lib/JulianDate.js +170 -0
  268. package/tempoch-js/tempoch-web/lib/ModifiedJulianDate.js +145 -0
  269. package/tempoch-js/tempoch-web/lib/Period.js +121 -0
  270. package/tempoch-js/tempoch-web/lib/backend.js +118 -0
  271. package/tempoch-js/tempoch-web/package.json +46 -0
  272. package/tempoch-js/tempoch-web/src/lib.rs +184 -0
@@ -0,0 +1,76 @@
1
+ # tempoch
2
+
3
+ [![Crates.io](https://img.shields.io/crates/v/tempoch.svg)](https://crates.io/crates/tempoch)
4
+ [![Docs](https://docs.rs/tempoch/badge.svg)](https://docs.rs/tempoch)
5
+ [![Code Quality](https://github.com/Siderust/tempoch/actions/workflows/ci.yml/badge.svg?branch=main)](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
+ }