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,711 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Vallés Puig, Ramon
3
+
4
+ //! FFI bindings for tempoch time types: JulianDate, ModifiedJulianDate,
5
+ //! and UTC conversions.
6
+
7
+ use crate::catch_panic;
8
+ use crate::error::TempochStatus;
9
+ use chrono::{DateTime, NaiveDate, Utc};
10
+ use qtty::Days;
11
+ use qtty_ffi::{QttyQuantity, UnitId};
12
+ use tempoch::{
13
+ JulianDate, ModifiedJulianDate, Time, TimeInstant, UniversalTime, GPS, JD, JDE, MJD, TAI, TCB,
14
+ TCG, TDB, TT, UT,
15
+ };
16
+
17
+ // ═══════════════════════════════════════════════════════════════════════════
18
+ // C-repr types
19
+ // ═══════════════════════════════════════════════════════════════════════════
20
+
21
+ /// UTC date-time breakdown for C interop.
22
+ #[repr(C)]
23
+ #[derive(Debug, Clone, Copy)]
24
+ pub struct TempochUtc {
25
+ /// Calendar year (e.g. 2026).
26
+ pub year: i32,
27
+ /// Month of the year (1–12).
28
+ pub month: u8,
29
+ /// Day of the month (1–31).
30
+ pub day: u8,
31
+ /// Hour of the day (0–23).
32
+ pub hour: u8,
33
+ /// Minute of the hour (0–59).
34
+ pub minute: u8,
35
+ /// Second of the minute (0–59).
36
+ pub second: u8,
37
+ /// Sub-second component in nanoseconds (0–999_999_999).
38
+ pub nanosecond: u32,
39
+ }
40
+
41
+ impl TempochUtc {
42
+ fn into_chrono(self) -> Option<DateTime<Utc>> {
43
+ let date = NaiveDate::from_ymd_opt(self.year, self.month as u32, self.day as u32)?;
44
+ let time = date.and_hms_nano_opt(
45
+ self.hour.into(),
46
+ self.minute.into(),
47
+ self.second.into(),
48
+ self.nanosecond,
49
+ )?;
50
+ Some(DateTime::<Utc>::from_naive_utc_and_offset(time, Utc))
51
+ }
52
+
53
+ fn from_chrono(dt: &DateTime<Utc>) -> Self {
54
+ use chrono::{Datelike, Timelike};
55
+ Self {
56
+ year: dt.year(),
57
+ month: dt.month() as u8,
58
+ day: dt.day() as u8,
59
+ hour: dt.hour() as u8,
60
+ minute: dt.minute() as u8,
61
+ second: dt.second() as u8,
62
+ nanosecond: dt.nanosecond(),
63
+ }
64
+ }
65
+ }
66
+
67
+ // ═══════════════════════════════════════════════════════════════════════════
68
+ // Julian Date
69
+ // ═══════════════════════════════════════════════════════════════════════════
70
+
71
+ /// Create a Julian Date from a raw f64 value.
72
+ #[no_mangle]
73
+ pub extern "C" fn tempoch_jd_new(value: f64) -> f64 {
74
+ value // JD is just a f64 — identity, but provides a typed entry point
75
+ }
76
+
77
+ /// Return the J2000.0 epoch as a Julian Date (2451545.0).
78
+ #[no_mangle]
79
+ pub extern "C" fn tempoch_jd_j2000() -> f64 {
80
+ JulianDate::J2000.value()
81
+ }
82
+
83
+ /// Convert a Julian Date to a Modified Julian Date.
84
+ #[no_mangle]
85
+ pub extern "C" fn tempoch_jd_to_mjd(jd: f64) -> f64 {
86
+ JulianDate::new(jd).to::<MJD>().value()
87
+ }
88
+
89
+ /// Create a Julian Date from a UTC date-time.
90
+ ///
91
+ /// # Safety
92
+ /// `out` must be a valid, writable pointer to `f64`.
93
+ #[no_mangle]
94
+ pub unsafe extern "C" fn tempoch_jd_from_utc(utc: TempochUtc, out: *mut f64) -> TempochStatus {
95
+ catch_panic!(TempochStatus::UtcConversionFailed, {
96
+ if out.is_null() {
97
+ return TempochStatus::NullPointer;
98
+ }
99
+ match utc.into_chrono() {
100
+ Some(dt) => {
101
+ let jd = JulianDate::from_utc(dt);
102
+ unsafe { *out = jd.value() };
103
+ TempochStatus::Ok
104
+ }
105
+ None => TempochStatus::UtcConversionFailed,
106
+ }
107
+ })
108
+ }
109
+
110
+ /// Convert a Julian Date to UTC. Returns Ok on success,
111
+ /// UtcConversionFailed if the date is out of representable range.
112
+ ///
113
+ /// # Safety
114
+ /// `out` must be a valid, writable pointer to `TempochUtc`.
115
+ #[no_mangle]
116
+ pub unsafe extern "C" fn tempoch_jd_to_utc(jd: f64, out: *mut TempochUtc) -> TempochStatus {
117
+ catch_panic!(TempochStatus::UtcConversionFailed, {
118
+ if out.is_null() {
119
+ return TempochStatus::NullPointer;
120
+ }
121
+ match JulianDate::new(jd).to_utc() {
122
+ Some(dt) => {
123
+ unsafe { *out = TempochUtc::from_chrono(&dt) };
124
+ TempochStatus::Ok
125
+ }
126
+ None => TempochStatus::UtcConversionFailed,
127
+ }
128
+ })
129
+ }
130
+
131
+ // ═══════════════════════════════════════════════════════════════════════════
132
+ // Modified Julian Date
133
+ // ═══════════════════════════════════════════════════════════════════════════
134
+
135
+ /// Create a Modified Julian Date from a raw f64 value.
136
+ #[no_mangle]
137
+ pub extern "C" fn tempoch_mjd_new(value: f64) -> f64 {
138
+ value
139
+ }
140
+
141
+ /// Convert a Modified Julian Date to a Julian Date.
142
+ #[no_mangle]
143
+ pub extern "C" fn tempoch_mjd_to_jd(mjd: f64) -> f64 {
144
+ ModifiedJulianDate::new(mjd).to::<JD>().value()
145
+ }
146
+
147
+ /// Create a Modified Julian Date from a UTC date-time.
148
+ ///
149
+ /// # Safety
150
+ /// `out` must be a valid, writable pointer to `f64`.
151
+ #[no_mangle]
152
+ pub unsafe extern "C" fn tempoch_mjd_from_utc(utc: TempochUtc, out: *mut f64) -> TempochStatus {
153
+ catch_panic!(TempochStatus::UtcConversionFailed, {
154
+ if out.is_null() {
155
+ return TempochStatus::NullPointer;
156
+ }
157
+ match utc.into_chrono() {
158
+ Some(dt) => {
159
+ let mjd = ModifiedJulianDate::from_utc(dt);
160
+ unsafe { *out = mjd.value() };
161
+ TempochStatus::Ok
162
+ }
163
+ None => TempochStatus::UtcConversionFailed,
164
+ }
165
+ })
166
+ }
167
+
168
+ /// Convert a Modified Julian Date to UTC.
169
+ ///
170
+ /// # Safety
171
+ /// `out` must be a valid, writable pointer to `TempochUtc`.
172
+ #[no_mangle]
173
+ pub unsafe extern "C" fn tempoch_mjd_to_utc(mjd: f64, out: *mut TempochUtc) -> TempochStatus {
174
+ catch_panic!(TempochStatus::UtcConversionFailed, {
175
+ if out.is_null() {
176
+ return TempochStatus::NullPointer;
177
+ }
178
+ match ModifiedJulianDate::new(mjd).to_utc() {
179
+ Some(dt) => {
180
+ unsafe { *out = TempochUtc::from_chrono(&dt) };
181
+ TempochStatus::Ok
182
+ }
183
+ None => TempochStatus::UtcConversionFailed,
184
+ }
185
+ })
186
+ }
187
+
188
+ // ═══════════════════════════════════════════════════════════════════════════
189
+ // Duration / Difference (raw f64 — backward compatible)
190
+ // ═══════════════════════════════════════════════════════════════════════════
191
+
192
+ /// Compute the difference between two Julian Dates in days.
193
+ #[no_mangle]
194
+ pub extern "C" fn tempoch_jd_difference(jd1: f64, jd2: f64) -> f64 {
195
+ let t1 = JulianDate::new(jd1);
196
+ let t2 = JulianDate::new(jd2);
197
+ t1.difference(&t2).value()
198
+ }
199
+
200
+ /// Add a duration in days to a Julian Date.
201
+ #[no_mangle]
202
+ pub extern "C" fn tempoch_jd_add_days(jd: f64, days: f64) -> f64 {
203
+ JulianDate::new(jd).add_duration(Days::new(days)).value()
204
+ }
205
+
206
+ /// Compute the difference between two Modified Julian Dates in days.
207
+ #[no_mangle]
208
+ pub extern "C" fn tempoch_mjd_difference(mjd1: f64, mjd2: f64) -> f64 {
209
+ let t1 = ModifiedJulianDate::new(mjd1);
210
+ let t2 = ModifiedJulianDate::new(mjd2);
211
+ t1.difference(&t2).value()
212
+ }
213
+
214
+ /// Add a duration in days to a Modified Julian Date.
215
+ #[no_mangle]
216
+ pub extern "C" fn tempoch_mjd_add_days(mjd: f64, days: f64) -> f64 {
217
+ ModifiedJulianDate::new(mjd)
218
+ .add_duration(Days::new(days))
219
+ .value()
220
+ }
221
+
222
+ /// Compute Julian centuries since J2000 for a given Julian Date.
223
+ #[no_mangle]
224
+ pub extern "C" fn tempoch_jd_julian_centuries(jd: f64) -> f64 {
225
+ JulianDate::new(jd).julian_centuries().value()
226
+ }
227
+
228
+ // ═══════════════════════════════════════════════════════════════════════════
229
+ // Duration / Difference (QttyQuantity — typed)
230
+ // ═══════════════════════════════════════════════════════════════════════════
231
+ // These functions return `QttyQuantity` values with proper unit metadata,
232
+ // enabling type-safe conversions via the qtty-ffi API.
233
+
234
+ /// Compute the difference between two Julian Dates as a `QttyQuantity` in days.
235
+ #[no_mangle]
236
+ pub extern "C" fn tempoch_jd_difference_qty(jd1: f64, jd2: f64) -> QttyQuantity {
237
+ let t1 = JulianDate::new(jd1);
238
+ let t2 = JulianDate::new(jd2);
239
+ QttyQuantity::new(t1.difference(&t2).value(), UnitId::Day)
240
+ }
241
+
242
+ /// Add a `QttyQuantity` duration (must be time-compatible) to a Julian Date.
243
+ /// The quantity is converted to days internally.
244
+ ///
245
+ /// # Safety
246
+ /// `out` must be a valid, writable pointer to `f64`.
247
+ #[no_mangle]
248
+ pub unsafe extern "C" fn tempoch_jd_add_qty(
249
+ jd: f64,
250
+ duration: QttyQuantity,
251
+ out: *mut f64,
252
+ ) -> TempochStatus {
253
+ catch_panic!(TempochStatus::UtcConversionFailed, {
254
+ if out.is_null() {
255
+ return TempochStatus::NullPointer;
256
+ }
257
+ // Convert quantity to days via qtty-ffi registry
258
+ let days_val = match duration.convert_to(UnitId::Day) {
259
+ Some(q) => q.value,
260
+ None => return TempochStatus::UtcConversionFailed,
261
+ };
262
+ let result = JulianDate::new(jd)
263
+ .add_duration(Days::new(days_val))
264
+ .value();
265
+ unsafe { *out = result };
266
+ TempochStatus::Ok
267
+ })
268
+ }
269
+
270
+ /// Compute the difference between two Modified Julian Dates as a `QttyQuantity` in days.
271
+ #[no_mangle]
272
+ pub extern "C" fn tempoch_mjd_difference_qty(mjd1: f64, mjd2: f64) -> QttyQuantity {
273
+ let t1 = ModifiedJulianDate::new(mjd1);
274
+ let t2 = ModifiedJulianDate::new(mjd2);
275
+ QttyQuantity::new(t1.difference(&t2).value(), UnitId::Day)
276
+ }
277
+
278
+ /// Add a `QttyQuantity` duration (must be time-compatible) to a Modified Julian Date.
279
+ /// The quantity is converted to days internally.
280
+ ///
281
+ /// # Safety
282
+ /// `out` must be a valid, writable pointer to `f64`.
283
+ #[no_mangle]
284
+ pub unsafe extern "C" fn tempoch_mjd_add_qty(
285
+ mjd: f64,
286
+ duration: QttyQuantity,
287
+ out: *mut f64,
288
+ ) -> TempochStatus {
289
+ catch_panic!(TempochStatus::UtcConversionFailed, {
290
+ if out.is_null() {
291
+ return TempochStatus::NullPointer;
292
+ }
293
+ let days_val = match duration.convert_to(UnitId::Day) {
294
+ Some(q) => q.value,
295
+ None => return TempochStatus::UtcConversionFailed,
296
+ };
297
+ let result = ModifiedJulianDate::new(mjd)
298
+ .add_duration(Days::new(days_val))
299
+ .value();
300
+ unsafe { *out = result };
301
+ TempochStatus::Ok
302
+ })
303
+ }
304
+
305
+ /// Compute Julian centuries since J2000 as a `QttyQuantity`.
306
+ #[no_mangle]
307
+ pub extern "C" fn tempoch_jd_julian_centuries_qty(jd: f64) -> QttyQuantity {
308
+ QttyQuantity::new(
309
+ JulianDate::new(jd).julian_centuries().value(),
310
+ UnitId::JulianCentury,
311
+ )
312
+ }
313
+
314
+ /// Compute the duration of a period as a `QttyQuantity` in days.
315
+ #[no_mangle]
316
+ pub extern "C" fn tempoch_period_mjd_duration_qty(period: crate::TempochPeriodMjd) -> QttyQuantity {
317
+ QttyQuantity::new(period.end_mjd - period.start_mjd, UnitId::Day)
318
+ }
319
+
320
+ // ═══════════════════════════════════════════════════════════════════════════
321
+ // Time scale conversions (JD ↔ TDB, TT, TAI, TCG, TCB, GPS, UT, JDE, UnixTime)
322
+ // ═══════════════════════════════════════════════════════════════════════════
323
+
324
+ /// Convert a Julian Date (TT) to TDB (Barycentric Dynamical Time).
325
+ #[no_mangle]
326
+ pub extern "C" fn tempoch_jd_to_tdb(jd: f64) -> f64 {
327
+ JulianDate::new(jd).to::<TDB>().value()
328
+ }
329
+
330
+ /// Convert TDB back to Julian Date (TT).
331
+ #[no_mangle]
332
+ pub extern "C" fn tempoch_tdb_to_jd(tdb: f64) -> f64 {
333
+ Time::<TDB>::new(tdb).to::<JD>().value()
334
+ }
335
+
336
+ /// Convert a Julian Date (TT) to TT (Terrestrial Time). Identity—included for completeness.
337
+ #[no_mangle]
338
+ pub extern "C" fn tempoch_jd_to_tt(jd: f64) -> f64 {
339
+ JulianDate::new(jd).to::<TT>().value()
340
+ }
341
+
342
+ /// Convert TT back to Julian Date (TT). Identity.
343
+ #[no_mangle]
344
+ pub extern "C" fn tempoch_tt_to_jd(tt: f64) -> f64 {
345
+ Time::<TT>::new(tt).to::<JD>().value()
346
+ }
347
+
348
+ /// Convert a Julian Date (TT) to TAI (International Atomic Time).
349
+ #[no_mangle]
350
+ pub extern "C" fn tempoch_jd_to_tai(jd: f64) -> f64 {
351
+ JulianDate::new(jd).to::<TAI>().value()
352
+ }
353
+
354
+ /// Convert TAI back to Julian Date (TT).
355
+ #[no_mangle]
356
+ pub extern "C" fn tempoch_tai_to_jd(tai: f64) -> f64 {
357
+ Time::<TAI>::new(tai).to::<JD>().value()
358
+ }
359
+
360
+ /// Convert a Julian Date (TT) to TCG (Geocentric Coordinate Time).
361
+ #[no_mangle]
362
+ pub extern "C" fn tempoch_jd_to_tcg(jd: f64) -> f64 {
363
+ JulianDate::new(jd).to::<TCG>().value()
364
+ }
365
+
366
+ /// Convert TCG back to Julian Date (TT).
367
+ #[no_mangle]
368
+ pub extern "C" fn tempoch_tcg_to_jd(tcg: f64) -> f64 {
369
+ Time::<TCG>::new(tcg).to::<JD>().value()
370
+ }
371
+
372
+ /// Convert a Julian Date (TT) to TCB (Barycentric Coordinate Time).
373
+ #[no_mangle]
374
+ pub extern "C" fn tempoch_jd_to_tcb(jd: f64) -> f64 {
375
+ JulianDate::new(jd).to::<TCB>().value()
376
+ }
377
+
378
+ /// Convert TCB back to Julian Date (TT).
379
+ #[no_mangle]
380
+ pub extern "C" fn tempoch_tcb_to_jd(tcb: f64) -> f64 {
381
+ Time::<TCB>::new(tcb).to::<JD>().value()
382
+ }
383
+
384
+ /// Convert a Julian Date (TT) to GPS Time.
385
+ #[no_mangle]
386
+ pub extern "C" fn tempoch_jd_to_gps(jd: f64) -> f64 {
387
+ JulianDate::new(jd).to::<GPS>().value()
388
+ }
389
+
390
+ /// Convert GPS Time back to Julian Date (TT).
391
+ #[no_mangle]
392
+ pub extern "C" fn tempoch_gps_to_jd(gps: f64) -> f64 {
393
+ Time::<GPS>::new(gps).to::<JD>().value()
394
+ }
395
+
396
+ /// Convert a Julian Date (TT) to UT (Universal Time UT1).
397
+ #[no_mangle]
398
+ pub extern "C" fn tempoch_jd_to_ut(jd: f64) -> f64 {
399
+ JulianDate::new(jd).to::<UT>().value()
400
+ }
401
+
402
+ /// Convert UT back to Julian Date (TT).
403
+ #[no_mangle]
404
+ pub extern "C" fn tempoch_ut_to_jd(ut: f64) -> f64 {
405
+ Time::<UT>::new(ut).to::<JD>().value()
406
+ }
407
+
408
+ /// Convert a Julian Date (TT) to JDE (Julian Ephemeris Day — semantic alias of JD(TT)).
409
+ #[no_mangle]
410
+ pub extern "C" fn tempoch_jd_to_jde(jd: f64) -> f64 {
411
+ JulianDate::new(jd).to::<JDE>().value()
412
+ }
413
+
414
+ /// Convert JDE back to Julian Date (TT).
415
+ #[no_mangle]
416
+ pub extern "C" fn tempoch_jde_to_jd(jde: f64) -> f64 {
417
+ Time::<JDE>::new(jde).to::<JD>().value()
418
+ }
419
+
420
+ /// Convert a Julian Date (TT) to Unix Time (seconds since 1970-01-01T00:00:00 UTC, ignoring leap seconds).
421
+ #[no_mangle]
422
+ pub extern "C" fn tempoch_jd_to_unix(jd: f64) -> f64 {
423
+ JulianDate::new(jd).to::<tempoch::UnixTime>().value()
424
+ }
425
+
426
+ /// Convert Unix Time back to Julian Date (TT).
427
+ #[no_mangle]
428
+ pub extern "C" fn tempoch_unix_to_jd(unix: f64) -> f64 {
429
+ Time::<tempoch::UnixTime>::new(unix).to::<JD>().value()
430
+ }
431
+
432
+ /// Return ΔT = TT − UT1 in seconds for a given Julian Date.
433
+ ///
434
+ /// Uses the piecewise polynomial/tabular model from tempoch-core.
435
+ #[no_mangle]
436
+ pub extern "C" fn tempoch_delta_t_seconds(jd: f64) -> f64 {
437
+ let ut: UniversalTime = JulianDate::new(jd).to::<UT>();
438
+ ut.delta_t().value()
439
+ }
440
+
441
+ /// Scale label for the `tempoch_jd_to_scale()` / `tempoch_scale_to_jd()` dispatch.
442
+ ///
443
+ /// cbindgen:prefix-with-name
444
+ #[repr(i32)]
445
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
446
+ pub enum TempochScale {
447
+ /// Julian Date.
448
+ JD = 0,
449
+ /// Modified Julian Date.
450
+ MJD = 1,
451
+ /// Barycentric Dynamical Time.
452
+ TDB = 2,
453
+ /// Terrestrial Time.
454
+ TT = 3,
455
+ /// International Atomic Time.
456
+ TAI = 4,
457
+ /// Geocentric Coordinate Time.
458
+ TCG = 5,
459
+ /// Barycentric Coordinate Time.
460
+ TCB = 6,
461
+ /// GPS Time.
462
+ GPS = 7,
463
+ /// Universal Time.
464
+ UT = 8,
465
+ /// Julian Ephemeris Date.
466
+ JDE = 9,
467
+ /// Unix timestamp (seconds since 1970-01-01 00:00:00 UTC).
468
+ UnixTime = 10,
469
+ }
470
+
471
+ /// Generic JD → any scale dispatch.
472
+ ///
473
+ /// Returns the value in the target time scale. Prefer the individual functions
474
+ /// (`tempoch_jd_to_tdb`, etc.) when the target scale is known at compile time.
475
+ #[no_mangle]
476
+ pub extern "C" fn tempoch_jd_to_scale(jd: f64, scale: TempochScale) -> f64 {
477
+ match scale {
478
+ TempochScale::JD => jd,
479
+ TempochScale::MJD => tempoch_jd_to_mjd(jd),
480
+ TempochScale::TDB => tempoch_jd_to_tdb(jd),
481
+ TempochScale::TT => tempoch_jd_to_tt(jd),
482
+ TempochScale::TAI => tempoch_jd_to_tai(jd),
483
+ TempochScale::TCG => tempoch_jd_to_tcg(jd),
484
+ TempochScale::TCB => tempoch_jd_to_tcb(jd),
485
+ TempochScale::GPS => tempoch_jd_to_gps(jd),
486
+ TempochScale::UT => tempoch_jd_to_ut(jd),
487
+ TempochScale::JDE => tempoch_jd_to_jde(jd),
488
+ TempochScale::UnixTime => tempoch_jd_to_unix(jd),
489
+ }
490
+ }
491
+
492
+ /// Generic any scale → JD dispatch.
493
+ #[no_mangle]
494
+ pub extern "C" fn tempoch_scale_to_jd(value: f64, scale: TempochScale) -> f64 {
495
+ match scale {
496
+ TempochScale::JD => value,
497
+ TempochScale::MJD => tempoch_mjd_to_jd(value),
498
+ TempochScale::TDB => tempoch_tdb_to_jd(value),
499
+ TempochScale::TT => tempoch_tt_to_jd(value),
500
+ TempochScale::TAI => tempoch_tai_to_jd(value),
501
+ TempochScale::TCG => tempoch_tcg_to_jd(value),
502
+ TempochScale::TCB => tempoch_tcb_to_jd(value),
503
+ TempochScale::GPS => tempoch_gps_to_jd(value),
504
+ TempochScale::UT => tempoch_ut_to_jd(value),
505
+ TempochScale::JDE => tempoch_jde_to_jd(value),
506
+ TempochScale::UnixTime => tempoch_unix_to_jd(value),
507
+ }
508
+ }
509
+
510
+ #[cfg(test)]
511
+ mod tests {
512
+ use super::*;
513
+ use crate::error::TempochStatus;
514
+ use std::ptr;
515
+
516
+ fn utc_j2000() -> TempochUtc {
517
+ TempochUtc {
518
+ year: 2000,
519
+ month: 1,
520
+ day: 1,
521
+ hour: 12,
522
+ minute: 0,
523
+ second: 0,
524
+ nanosecond: 0,
525
+ }
526
+ }
527
+
528
+ // ── TempochUtc::into_chrono ───────────────────────────────────────
529
+
530
+ #[test]
531
+ fn into_chrono_invalid_nanoseconds_returns_none() {
532
+ // and_hms_nano_opt returns None when nanosecond >= 1_000_000_000.
533
+ let utc = TempochUtc {
534
+ year: 2000,
535
+ month: 1,
536
+ day: 1,
537
+ hour: 12,
538
+ minute: 0,
539
+ second: 0,
540
+ nanosecond: 1_500_000_000,
541
+ };
542
+ assert!(utc.into_chrono().is_none());
543
+ }
544
+
545
+ // ── tempoch_jd_new / tempoch_jd_j2000 ────────────────────────────
546
+
547
+ #[test]
548
+ fn jd_new_is_identity() {
549
+ assert_eq!(tempoch_jd_new(2_451_545.0), 2_451_545.0);
550
+ assert_eq!(tempoch_jd_new(0.0), 0.0);
551
+ }
552
+
553
+ #[test]
554
+ fn jd_j2000_value() {
555
+ assert_eq!(tempoch_jd_j2000(), 2_451_545.0);
556
+ }
557
+
558
+ // ── tempoch_jd_to_mjd ────────────────────────────────────────────
559
+
560
+ #[test]
561
+ fn jd_to_mjd_roundtrip() {
562
+ let mjd = tempoch_jd_to_mjd(2_451_545.0);
563
+ assert!((mjd - 51_544.5).abs() < 1e-10);
564
+ }
565
+
566
+ // ── tempoch_jd_from_utc ──────────────────────────────────────────
567
+
568
+ #[test]
569
+ fn jd_from_utc_null_pointer_returns_error() {
570
+ let status = unsafe { tempoch_jd_from_utc(utc_j2000(), ptr::null_mut()) };
571
+ assert_eq!(status, TempochStatus::NullPointer);
572
+ }
573
+
574
+ #[test]
575
+ fn jd_from_utc_invalid_date_returns_error() {
576
+ let bad = TempochUtc {
577
+ year: 2000,
578
+ month: 1,
579
+ day: 1,
580
+ hour: 12,
581
+ minute: 0,
582
+ second: 0,
583
+ nanosecond: 2_000_000_000,
584
+ };
585
+ let mut out: f64 = 0.0;
586
+ let status = unsafe { tempoch_jd_from_utc(bad, &mut out) };
587
+ assert_eq!(status, TempochStatus::UtcConversionFailed);
588
+ }
589
+
590
+ #[test]
591
+ fn jd_from_utc_success() {
592
+ let mut out: f64 = 0.0;
593
+ let status = unsafe { tempoch_jd_from_utc(utc_j2000(), &mut out) };
594
+ assert_eq!(status, TempochStatus::Ok);
595
+ // J2000.0 UTC → JD(TT) should be close to 2 451 545
596
+ assert!((out - 2_451_545.0).abs() < 0.01);
597
+ }
598
+
599
+ // ── tempoch_jd_to_utc ────────────────────────────────────────────
600
+
601
+ #[test]
602
+ fn jd_to_utc_null_pointer_returns_error() {
603
+ let status = unsafe { tempoch_jd_to_utc(2_451_545.0, ptr::null_mut()) };
604
+ assert_eq!(status, TempochStatus::NullPointer);
605
+ }
606
+
607
+ #[test]
608
+ fn jd_to_utc_success() {
609
+ let mut out = TempochUtc {
610
+ year: 0,
611
+ month: 0,
612
+ day: 0,
613
+ hour: 0,
614
+ minute: 0,
615
+ second: 0,
616
+ nanosecond: 0,
617
+ };
618
+ let status = unsafe { tempoch_jd_to_utc(2_451_545.0, &mut out) };
619
+ assert_eq!(status, TempochStatus::Ok);
620
+ assert_eq!(out.year, 2000);
621
+ assert_eq!(out.month, 1);
622
+ assert_eq!(out.day, 1);
623
+ }
624
+
625
+ // ── tempoch_mjd_new ──────────────────────────────────────────────
626
+
627
+ #[test]
628
+ fn mjd_new_is_identity() {
629
+ assert_eq!(tempoch_mjd_new(51_544.5), 51_544.5);
630
+ }
631
+
632
+ // ── tempoch_mjd_to_jd ────────────────────────────────────────────
633
+
634
+ #[test]
635
+ fn mjd_to_jd_roundtrip() {
636
+ let jd = tempoch_mjd_to_jd(51_544.5);
637
+ assert!((jd - 2_451_545.0).abs() < 1e-10);
638
+ }
639
+
640
+ // ── tempoch_mjd_from_utc ─────────────────────────────────────────
641
+
642
+ #[test]
643
+ fn mjd_from_utc_null_pointer_returns_error() {
644
+ let status = unsafe { tempoch_mjd_from_utc(utc_j2000(), ptr::null_mut()) };
645
+ assert_eq!(status, TempochStatus::NullPointer);
646
+ }
647
+
648
+ #[test]
649
+ fn mjd_from_utc_success() {
650
+ let mut out: f64 = 0.0;
651
+ let status = unsafe { tempoch_mjd_from_utc(utc_j2000(), &mut out) };
652
+ assert_eq!(status, TempochStatus::Ok);
653
+ assert!((out - 51_544.5).abs() < 0.01);
654
+ }
655
+
656
+ // ── tempoch_mjd_to_utc ───────────────────────────────────────────
657
+
658
+ #[test]
659
+ fn mjd_to_utc_null_pointer_returns_error() {
660
+ let status = unsafe { tempoch_mjd_to_utc(51_544.5, ptr::null_mut()) };
661
+ assert_eq!(status, TempochStatus::NullPointer);
662
+ }
663
+
664
+ #[test]
665
+ fn mjd_to_utc_success() {
666
+ let mut out = TempochUtc {
667
+ year: 0,
668
+ month: 0,
669
+ day: 0,
670
+ hour: 0,
671
+ minute: 0,
672
+ second: 0,
673
+ nanosecond: 0,
674
+ };
675
+ let status = unsafe { tempoch_mjd_to_utc(51_544.5, &mut out) };
676
+ assert_eq!(status, TempochStatus::Ok);
677
+ assert_eq!(out.year, 2000);
678
+ }
679
+
680
+ // ── arithmetic helpers ───────────────────────────────────────────
681
+
682
+ #[test]
683
+ fn jd_difference_returns_days() {
684
+ let diff = tempoch_jd_difference(2_451_546.0, 2_451_545.0);
685
+ assert!((diff - 1.0).abs() < 1e-10);
686
+ }
687
+
688
+ #[test]
689
+ fn jd_add_days_advances_epoch() {
690
+ let result = tempoch_jd_add_days(2_451_545.0, 1.5);
691
+ assert!((result - 2_451_546.5).abs() < 1e-10);
692
+ }
693
+
694
+ #[test]
695
+ fn mjd_difference_returns_days() {
696
+ let diff = tempoch_mjd_difference(59001.0, 59000.0);
697
+ assert!((diff - 1.0).abs() < 1e-10);
698
+ }
699
+
700
+ #[test]
701
+ fn mjd_add_days_advances_epoch() {
702
+ let result = tempoch_mjd_add_days(59000.0, 0.5);
703
+ assert!((result - 59000.5).abs() < 1e-10);
704
+ }
705
+
706
+ #[test]
707
+ fn jd_julian_centuries_at_j2000_is_zero() {
708
+ let c = tempoch_jd_julian_centuries(2_451_545.0);
709
+ assert!(c.abs() < 1e-10);
710
+ }
711
+ }