lox_time/utc/
transformations.rs1use std::sync::OnceLock;
7
8use crate::deltas::TimeDelta;
9use crate::deltas::ToDelta;
10use crate::offsets::DefaultOffsetProvider;
11use crate::offsets::Offset;
12use crate::time::DynTime;
13use crate::time_of_day::CivilTime;
14use crate::time_of_day::TimeOfDay;
15use crate::time_scales::TimeScale;
16use crate::time_scales::{DynTimeScale, Tai};
17use crate::{time::Time, utc};
18
19use super::LeapSecondsProvider;
20use super::Utc;
21use super::leap_seconds::DefaultLeapSecondsProvider;
22
23mod before1972;
24
25impl Utc {
26 pub fn offset_tai(&self, provider: &impl LeapSecondsProvider) -> TimeDelta {
28 if self < utc_1972_01_01() {
29 before1972::delta_utc_tai(self)
30 } else {
31 provider.delta_utc_tai(*self)
32 }
33 }
34
35 pub fn to_time_with_provider(&self, provider: &impl LeapSecondsProvider) -> Time<Tai> {
37 let offset = self.offset_tai(provider);
38 Time::from_delta(Tai, self.to_delta() - offset)
39 }
40
41 pub fn to_time(&self) -> Time<Tai> {
43 self.to_time_with_provider(&DefaultLeapSecondsProvider)
44 }
45
46 pub fn to_dyn_time_with_provider(&self, provider: &impl LeapSecondsProvider) -> DynTime {
48 let offset = self.offset_tai(provider);
49 Time::from_delta(DynTimeScale::Tai, self.to_delta() - offset)
50 }
51
52 pub fn to_dyn_time(&self) -> DynTime {
54 self.to_dyn_time_with_provider(&DefaultLeapSecondsProvider)
55 }
56}
57
58pub trait ToUtc {
60 fn to_utc_with_provider(&self, provider: &impl LeapSecondsProvider) -> Utc;
62
63 fn to_utc(&self) -> Utc {
65 self.to_utc_with_provider(&DefaultLeapSecondsProvider)
66 }
67}
68
69impl ToUtc for Utc {
70 fn to_utc_with_provider(&self, _provider: &impl LeapSecondsProvider) -> Utc {
71 *self
72 }
73}
74
75impl<T> ToUtc for Time<T>
76where
77 T: TimeScale + Copy,
78 DefaultOffsetProvider: Offset<T, Tai>,
79{
80 fn to_utc_with_provider(&self, provider: &impl LeapSecondsProvider) -> Utc {
81 let tai = self.to_scale(Tai);
82 assert!(
83 tai.seconds().is_some(),
84 "NaN TimeDelta cannot be converted to UTC"
85 );
86 let delta = if &tai < tai_at_utc_1972_01_01() {
87 before1972::delta_tai_utc(&tai)
88 } else {
89 provider.delta_tai_utc(tai)
90 };
91 let mut utc = Utc::from_delta(tai.to_delta() - delta)
92 .expect("finite TAI time should produce valid UTC");
93 if provider.is_leap_second(tai) {
94 utc.time = TimeOfDay::new(utc.hour(), utc.minute(), 60)
95 .unwrap()
96 .with_subsecond(utc.time.subsecond());
97 }
98 utc
99 }
100}
101
102fn utc_1972_01_01() -> &'static Utc {
103 static UTC_1972: OnceLock<Utc> = OnceLock::new();
104 UTC_1972.get_or_init(|| utc!(1972, 1, 1).unwrap())
105}
106
107fn tai_at_utc_1972_01_01() -> &'static Time<Tai> {
108 const LEAP_SECONDS_1972: i64 = 10;
109 static TAI_AT_UTC_1972_01_01: OnceLock<Time<Tai>> = OnceLock::new();
110 TAI_AT_UTC_1972_01_01.get_or_init(|| {
111 let utc = utc_1972_01_01();
112 let base_time = utc.to_delta();
113 let leap_seconds = TimeDelta::from_seconds(LEAP_SECONDS_1972);
114 Time::from_delta(Tai, base_time + leap_seconds)
115 })
116}
117
118#[cfg(test)]
119mod test {
120 use crate::subsecond::Subsecond;
121 use crate::time;
122 use crate::time_scales::{Tcb, Tcg, Tdb, Tt};
123 use rstest::rstest;
124
125 use super::*;
126
127 #[test]
128 fn test_utc_to_utc() {
129 let utc0 = utc!(2000, 1, 1).unwrap();
130 let utc1 = utc0.to_utc();
131 assert_eq!(utc0, utc1);
132 }
133
134 #[rstest]
135 #[case::before_1972(utc_1971_01_01(), tai_at_utc_1971_01_01())]
136 #[case::before_leap_second(utc_1s_before_2016_leap_second(), tai_1s_before_2016_leap_second())]
137 #[case::during_leap_second(utc_during_2016_leap_second(), tai_during_2016_leap_second())]
138 #[case::after_leap_second(utc_1s_after_2016_leap_second(), tai_1s_after_2016_leap_second())]
139 fn test_utc_to_tai(#[case] utc: &Utc, #[case] expected: &Time<Tai>) {
140 let actual = utc.to_time();
141 assert_eq!(*expected, actual);
142 }
143
144 #[rstest]
145 #[case::before_utc_1972(tai_at_utc_1971_01_01(), *utc_1971_01_01())]
146 #[case::utc_1972(tai_at_utc_1972_01_01(), *utc_1972_01_01())]
147 #[case::before_leap_second(tai_1s_before_2016_leap_second(), *utc_1s_before_2016_leap_second())]
148 #[case::during_leap_second(tai_during_2016_leap_second(), *utc_during_2016_leap_second())]
149 #[case::after_leap_second(tai_1s_after_2016_leap_second(), *utc_1s_after_2016_leap_second())]
150 #[case::before_1960(tai_before_1960(), *utc_before_1960())]
151 fn test_tai_to_utc(#[case] tai: &Time<Tai>, #[case] expected: Utc) {
152 let actual = tai.to_utc();
153 assert_eq!(expected, actual);
154 }
155
156 #[test]
157 fn test_all_scales_to_utc() {
158 use lox_test_utils::assert_approx_eq;
159
160 let tai = time!(Tai, 2024, 5, 17, 12, 13, 14.0).unwrap();
161 let exp = tai.to_utc();
162 let tt = tai.to_scale(Tt);
163 let act = tt.to_utc();
164 assert_eq!(act, exp);
165 let tcg = tai.to_scale(Tcg);
166 let act = tcg.to_utc();
167 assert_eq!(act, exp);
168 let tcb = tai.to_scale(Tcb);
170 let act = tcb.to_utc();
171 assert_approx_eq!(act, exp);
172 let tdb = tai.to_scale(Tdb);
173 let act = tdb.to_utc();
174 assert_eq!(act, exp);
175 }
176
177 fn utc_1971_01_01() -> &'static Utc {
184 static UTC_1971: OnceLock<Utc> = OnceLock::new();
185 UTC_1971.get_or_init(|| utc!(1971, 1, 1).unwrap())
186 }
187
188 fn tai_at_utc_1971_01_01() -> &'static Time<Tai> {
189 const DELTA: TimeDelta = TimeDelta::from_seconds_and_subsecond_f64(8.0, 0.9461620000000011);
195
196 static TAI_AT_UTC_1971_01_01: OnceLock<Time<Tai>> = OnceLock::new();
197 TAI_AT_UTC_1971_01_01.get_or_init(|| {
198 let utc = utc_1971_01_01();
199 let base = utc.to_delta();
200 Time::from_delta(Tai, base + DELTA)
201 })
202 }
203
204 fn utc_1s_before_2016_leap_second() -> &'static Utc {
206 static BEFORE_LEAP_SECOND: OnceLock<Utc> = OnceLock::new();
207 BEFORE_LEAP_SECOND.get_or_init(|| utc!(2016, 12, 31, 23, 59, 59.0).unwrap())
208 }
209
210 fn tai_1s_before_2016_leap_second() -> &'static Time<Tai> {
212 static BEFORE_LEAP_SECOND: OnceLock<Time<Tai>> = OnceLock::new();
213 BEFORE_LEAP_SECOND.get_or_init(|| Time::new(Tai, 536500835, Subsecond::default()))
214 }
215
216 fn utc_during_2016_leap_second() -> &'static Utc {
218 static DURING_LEAP_SECOND: OnceLock<Utc> = OnceLock::new();
219 DURING_LEAP_SECOND.get_or_init(|| utc!(2016, 12, 31, 23, 59, 60.0).unwrap())
220 }
221
222 fn tai_during_2016_leap_second() -> &'static Time<Tai> {
224 static DURING_LEAP_SECOND: OnceLock<Time<Tai>> = OnceLock::new();
225 DURING_LEAP_SECOND.get_or_init(|| Time::new(Tai, 536500836, Subsecond::default()))
226 }
227
228 fn utc_1s_after_2016_leap_second() -> &'static Utc {
230 static AFTER_LEAP_SECOND: OnceLock<Utc> = OnceLock::new();
231 AFTER_LEAP_SECOND.get_or_init(|| utc!(2017, 1, 1).unwrap())
232 }
233
234 fn tai_1s_after_2016_leap_second() -> &'static Time<Tai> {
236 static AFTER_LEAP_SECOND: OnceLock<Time<Tai>> = OnceLock::new();
237 AFTER_LEAP_SECOND.get_or_init(|| Time::new(Tai, 536500837, Subsecond::default()))
238 }
239
240 fn utc_before_1960() -> &'static Utc {
241 static UTC_BEFORE_1960: OnceLock<Utc> = OnceLock::new();
242 UTC_BEFORE_1960.get_or_init(|| utc!(1959, 12, 31).unwrap())
243 }
244
245 fn tai_before_1960() -> &'static Time<Tai> {
247 static TAI_BEFORE_1960: OnceLock<Time<Tai>> = OnceLock::new();
248 TAI_BEFORE_1960.get_or_init(|| {
249 let utc = utc_before_1960();
250 utc.to_time()
251 })
252 }
253}