celestial_time/scales/conversions/
tt_tcg.rs1use super::{ToTCG, ToTT};
49use crate::constants::{TCG_RATE_LG, TCG_RATE_RATIO, TCG_REFERENCE_EPOCH};
50use crate::julian::JulianDate;
51use crate::scales::{TCG, TT};
52use crate::TimeResult;
53use celestial_core::constants::MJD_ZERO_POINT;
54
55impl ToTCG for TCG {
56 fn to_tcg(&self) -> TimeResult<TCG> {
58 Ok(*self)
59 }
60}
61
62impl ToTT for TCG {
63 fn to_tt(&self) -> TimeResult<TT> {
70 let tcg_jd = self.to_julian_date();
71
72 let (tt_jd1, tt_jd2) = if tcg_jd.jd1().abs() > tcg_jd.jd2().abs() {
73 let correction = ((tcg_jd.jd1() - MJD_ZERO_POINT)
74 + (tcg_jd.jd2() - TCG_REFERENCE_EPOCH))
75 * TCG_RATE_LG;
76 (tcg_jd.jd1(), tcg_jd.jd2() - correction)
77 } else {
78 let correction = ((tcg_jd.jd2() - MJD_ZERO_POINT)
79 + (tcg_jd.jd1() - TCG_REFERENCE_EPOCH))
80 * TCG_RATE_LG;
81 (tcg_jd.jd1() - correction, tcg_jd.jd2())
82 };
83
84 let tt_jd = JulianDate::new(tt_jd1, tt_jd2);
85 Ok(TT::from_julian_date(tt_jd))
86 }
87}
88
89impl ToTCG for TT {
90 fn to_tcg(&self) -> TimeResult<TCG> {
97 let tt_jd = self.to_julian_date();
98
99 let (tcg_jd1, tcg_jd2) = if tt_jd.jd1().abs() > tt_jd.jd2().abs() {
100 let correction = ((tt_jd.jd1() - MJD_ZERO_POINT) + (tt_jd.jd2() - TCG_REFERENCE_EPOCH))
101 * TCG_RATE_RATIO;
102 (tt_jd.jd1(), tt_jd.jd2() + correction)
103 } else {
104 let correction = ((tt_jd.jd2() - MJD_ZERO_POINT) + (tt_jd.jd1() - TCG_REFERENCE_EPOCH))
105 * TCG_RATE_RATIO;
106 (tt_jd.jd1() + correction, tt_jd.jd2())
107 };
108
109 let tcg_jd = JulianDate::new(tcg_jd1, tcg_jd2);
110 Ok(TCG::from_julian_date(tcg_jd))
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use celestial_core::constants::{J2000_JD, MJD_ZERO_POINT, SECONDS_PER_DAY_F64};
118
119 #[test]
120 fn test_tcg_identity_conversion() {
121 let tcg = TCG::from_julian_date(JulianDate::new(J2000_JD, 0.999999999999999));
122 let identity_tcg = tcg.to_tcg().unwrap();
123
124 assert_eq!(
125 tcg.to_julian_date().jd1(),
126 identity_tcg.to_julian_date().jd1(),
127 "TCG identity conversion should preserve JD1"
128 );
129 assert_eq!(
130 tcg.to_julian_date().jd2(),
131 identity_tcg.to_julian_date().jd2(),
132 "TCG identity conversion should preserve JD2"
133 );
134 }
135
136 #[test]
137 fn test_tt_tcg_offset() {
138 let test_cases = [
139 (J2000_JD, 0.5058332857, "J2000.0"),
140 (2455197.5, 0.7257673560, "2010-01-01"),
141 (2458849.5, 0.9456713190, "2020-01-01"),
142 (2469807.5, 1.6055036373, "2050-01-01"),
143 ];
144
145 let tolerance_seconds = 1e-6;
146
147 for (jd, expected_offset_seconds, description) in test_cases {
148 let tt = TT::from_julian_date(JulianDate::new(jd, 0.0));
149 let tcg = tt.to_tcg().unwrap();
150
151 let tt_jd = tt.to_julian_date();
152 let tcg_jd = tcg.to_julian_date();
153
154 let offset_days = (tcg_jd.jd1() - tt_jd.jd1()) + (tcg_jd.jd2() - tt_jd.jd2());
155 let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
156
157 let diff = (offset_seconds - expected_offset_seconds).abs();
158 assert!(
159 diff < tolerance_seconds,
160 "{}: TT->TCG offset should be {:.10}s, got {:.10}s (diff: {:.2e}s)",
161 description,
162 expected_offset_seconds,
163 offset_seconds,
164 diff
165 );
166
167 let tcg = TCG::from_julian_date(JulianDate::new(jd, 0.0));
168 let tt = tcg.to_tt().unwrap();
169
170 let tcg_jd = tcg.to_julian_date();
171 let tt_jd = tt.to_julian_date();
172
173 let offset_days = (tcg_jd.jd1() - tt_jd.jd1()) + (tcg_jd.jd2() - tt_jd.jd2());
174 let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
175
176 let diff = (offset_seconds - expected_offset_seconds).abs();
177 assert!(
178 diff < tolerance_seconds,
179 "{}: TCG->TT means TCG is {:.10}s ahead, got {:.10}s (diff: {:.2e}s)",
180 description,
181 expected_offset_seconds,
182 offset_seconds,
183 diff
184 );
185 }
186 }
187
188 #[test]
189 fn test_tt_tcg_at_reference_epoch() {
190 let reference_epoch_jd = MJD_ZERO_POINT + TCG_REFERENCE_EPOCH;
191
192 let tt = TT::from_julian_date(JulianDate::new(reference_epoch_jd, 0.0));
193 let tcg = tt.to_tcg().unwrap();
194
195 let tt_jd = tt.to_julian_date();
196 let tcg_jd = tcg.to_julian_date();
197
198 let offset_days = (tcg_jd.jd1() - tt_jd.jd1()) + (tcg_jd.jd2() - tt_jd.jd2());
199 let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
200
201 let tolerance_seconds = 1e-12;
202 assert!(
203 offset_seconds.abs() < tolerance_seconds,
204 "At reference epoch T0, TCG-TT should be 0, got {:.2e}s",
205 offset_seconds
206 );
207 }
208
209 #[test]
210 fn test_tt_tcg_round_trip_precision() {
211 const TOLERANCE_DAYS: f64 = 1e-14; let test_jd2_values = [0.0, 0.5, 0.123456789012345, -0.123456789012345, 0.987654321];
217
218 for jd2 in test_jd2_values {
219 let original_tt = TT::from_julian_date(JulianDate::new(J2000_JD, jd2));
220 let tcg = original_tt.to_tcg().unwrap();
221 let round_trip_tt = tcg.to_tt().unwrap();
222
223 assert_eq!(
224 original_tt.to_julian_date().jd1(),
225 round_trip_tt.to_julian_date().jd1(),
226 "TT->TCG->TT JD1 must be exact for jd2={}",
227 jd2
228 );
229 let jd2_diff =
230 (original_tt.to_julian_date().jd2() - round_trip_tt.to_julian_date().jd2()).abs();
231 assert!(
232 jd2_diff <= TOLERANCE_DAYS,
233 "TT->TCG->TT JD2 difference {} exceeds tolerance {} for jd2={}",
234 jd2_diff,
235 TOLERANCE_DAYS,
236 jd2
237 );
238
239 let original_tcg = TCG::from_julian_date(JulianDate::new(J2000_JD, jd2));
240 let tt = original_tcg.to_tt().unwrap();
241 let round_trip_tcg = tt.to_tcg().unwrap();
242
243 assert_eq!(
244 original_tcg.to_julian_date().jd1(),
245 round_trip_tcg.to_julian_date().jd1(),
246 "TCG->TT->TCG JD1 must be exact for jd2={}",
247 jd2
248 );
249 let jd2_diff =
250 (original_tcg.to_julian_date().jd2() - round_trip_tcg.to_julian_date().jd2()).abs();
251 assert!(
252 jd2_diff <= TOLERANCE_DAYS,
253 "TCG->TT->TCG JD2 difference {} exceeds tolerance {} for jd2={}",
254 jd2_diff,
255 TOLERANCE_DAYS,
256 jd2
257 );
258 }
259
260 let alt_tt = TT::from_julian_date(JulianDate::new(0.5, J2000_JD));
261 let alt_tcg = alt_tt.to_tcg().unwrap();
262 let alt_round_trip = alt_tcg.to_tt().unwrap();
263
264 assert_eq!(
265 alt_tt.to_julian_date().jd1(),
266 alt_round_trip.to_julian_date().jd1(),
267 "Alternate split TT->TCG->TT JD1 must be exact"
268 );
269 let jd2_diff =
270 (alt_tt.to_julian_date().jd2() - alt_round_trip.to_julian_date().jd2()).abs();
271 assert!(
272 jd2_diff <= TOLERANCE_DAYS,
273 "Alternate split TT->TCG->TT JD2 difference {} exceeds tolerance {}",
274 jd2_diff,
275 TOLERANCE_DAYS
276 );
277 }
278}