celestial_time/scales/conversions/
gps_tai.rs1use super::{ToGPS, ToTAI};
34use crate::constants::GPS_TO_TAI_OFFSET_SECONDS;
35use crate::scales::{GPS, TAI};
36use crate::TimeResult;
37use celestial_core::constants::SECONDS_PER_DAY_F64;
38
39impl ToGPS for GPS {
41 fn to_gps(&self) -> TimeResult<GPS> {
43 Ok(*self)
44 }
45}
46
47impl ToTAI for GPS {
49 fn to_tai(&self) -> TimeResult<TAI> {
53 let gps_jd = self.to_julian_date();
54 let offset_days = GPS_TO_TAI_OFFSET_SECONDS / SECONDS_PER_DAY_F64;
55
56 let (tai_jd1, tai_jd2) = if gps_jd.jd1().abs() > gps_jd.jd2().abs() {
57 (gps_jd.jd1(), gps_jd.jd2() + offset_days)
58 } else {
59 (gps_jd.jd1() + offset_days, gps_jd.jd2())
60 };
61
62 Ok(TAI::from_julian_date_raw(tai_jd1, tai_jd2))
63 }
64}
65
66impl ToGPS for TAI {
68 fn to_gps(&self) -> TimeResult<GPS> {
72 let tai_jd = self.to_julian_date();
73 let offset_days = GPS_TO_TAI_OFFSET_SECONDS / SECONDS_PER_DAY_F64;
74
75 let (gps_jd1, gps_jd2) = if tai_jd.jd1().abs() > tai_jd.jd2().abs() {
76 (tai_jd.jd1(), tai_jd.jd2() - offset_days)
77 } else {
78 (tai_jd.jd1() - offset_days, tai_jd.jd2())
79 };
80
81 Ok(GPS::from_julian_date_raw(gps_jd1, gps_jd2))
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::constants::GPS_EPOCH_JD;
89 use crate::JulianDate;
90 use celestial_core::constants::J2000_JD;
91
92 #[test]
93 fn test_gps_identity_conversion() {
94 let gps = GPS::from_julian_date(JulianDate::new(J2000_JD, 0.999999999999999));
95 let identity_gps = gps.to_gps().unwrap();
96
97 assert_eq!(
98 gps.to_julian_date().jd1(),
99 identity_gps.to_julian_date().jd1(),
100 "GPS identity conversion should preserve JD1"
101 );
102 assert_eq!(
103 gps.to_julian_date().jd2(),
104 identity_gps.to_julian_date().jd2(),
105 "GPS identity conversion should preserve JD2"
106 );
107 }
108
109 #[test]
110 fn test_gps_tai_offset_19_seconds() {
111 let test_dates = [
112 (GPS_EPOCH_JD, "GPS epoch 1980-01-06"),
113 (J2000_JD, "J2000.0"),
114 (2455197.5, "2010-01-01"),
115 (2459580.5, "2022-01-01"),
116 ];
117
118 for (jd, description) in test_dates {
119 let gps = GPS::from_julian_date(JulianDate::new(jd, 0.0));
120 let tai = gps.to_tai().unwrap();
121
122 let gps_jd = gps.to_julian_date();
123 let tai_jd = tai.to_julian_date();
124
125 let offset_days = (tai_jd.jd1() - gps_jd.jd1()) + (tai_jd.jd2() - gps_jd.jd2());
126 let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
127
128 assert_eq!(
129 offset_seconds, 19.0,
130 "{}: GPS->TAI offset must be exactly 19 seconds",
131 description
132 );
133
134 let tai = TAI::from_julian_date(JulianDate::new(jd, 0.0));
135 let gps = tai.to_gps().unwrap();
136
137 let tai_jd = tai.to_julian_date();
138 let gps_jd = gps.to_julian_date();
139
140 let offset_days = (tai_jd.jd1() - gps_jd.jd1()) + (tai_jd.jd2() - gps_jd.jd2());
141 let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
142
143 assert_eq!(
144 offset_seconds, 19.0,
145 "{}: TAI->GPS means TAI is 19 seconds ahead",
146 description
147 );
148 }
149 }
150
151 #[test]
152 fn test_gps_tai_round_trip_precision() {
153 let test_jd2_values = [0.0, 0.5, 0.123456789012345, -0.123456789012345, 0.987654321];
154
155 for jd2 in test_jd2_values {
156 let original_gps = GPS::from_julian_date(JulianDate::new(J2000_JD, jd2));
157 let tai = original_gps.to_tai().unwrap();
158 let round_trip_gps = tai.to_gps().unwrap();
159
160 assert_eq!(
161 original_gps.to_julian_date().jd1(),
162 round_trip_gps.to_julian_date().jd1(),
163 "GPS->TAI->GPS JD1 must be exact for jd2={}",
164 jd2
165 );
166 assert_eq!(
167 original_gps.to_julian_date().jd2(),
168 round_trip_gps.to_julian_date().jd2(),
169 "GPS->TAI->GPS JD2 must be exact for jd2={}",
170 jd2
171 );
172
173 let original_tai = TAI::from_julian_date(JulianDate::new(J2000_JD, jd2));
174 let gps = original_tai.to_gps().unwrap();
175 let round_trip_tai = gps.to_tai().unwrap();
176
177 assert_eq!(
178 original_tai.to_julian_date().jd1(),
179 round_trip_tai.to_julian_date().jd1(),
180 "TAI->GPS->TAI JD1 must be exact for jd2={}",
181 jd2
182 );
183 assert_eq!(
184 original_tai.to_julian_date().jd2(),
185 round_trip_tai.to_julian_date().jd2(),
186 "TAI->GPS->TAI JD2 must be exact for jd2={}",
187 jd2
188 );
189 }
190
191 let alt_gps = GPS::from_julian_date(JulianDate::new(0.5, J2000_JD));
192 let alt_tai = alt_gps.to_tai().unwrap();
193 let alt_round_trip = alt_tai.to_gps().unwrap();
194
195 assert_eq!(
196 alt_gps.to_julian_date().jd1(),
197 alt_round_trip.to_julian_date().jd1(),
198 "Alternate split GPS->TAI->GPS JD1 must be exact"
199 );
200 assert_eq!(
201 alt_gps.to_julian_date().jd2(),
202 alt_round_trip.to_julian_date().jd2(),
203 "Alternate split GPS->TAI->GPS JD2 must be exact"
204 );
205 }
206}