1use core::fmt;
7use core::marker::PhantomData;
8use core::ops::{Add, AddAssign, Sub, SubAssign};
9
10use crate::earth::context::TimeContext;
11use crate::encoding::jd_to_julian_centuries;
12use crate::format::{J2000s, TimeFormat};
13use crate::foundation::error::ConversionError;
14use crate::model::scale::conversion::{ContextScaleConvert, InfallibleScaleConvert};
15use crate::model::scale::{CoordinateScale, Scale, TT, UTC};
16use crate::model::target::{ContextConversionTarget, ConversionTarget, InfallibleConversionTarget};
17use crate::{FormatForScale, InfallibleFormatForScale};
18use affn::algebra::{Space, SplitPoint1, SplitQuantity};
19use qtty::time::TimeUnit;
20use qtty::unit::Second as SecondUnit;
21use qtty::{Quantity, Second};
22
23#[inline]
25fn coordinate_pair_ok(hi: f64, lo: f64) -> bool {
26 !hi.is_nan() && !lo.is_nan()
27}
28
29#[derive(Copy, Clone)]
30pub(crate) struct ScaleAxis<S: Scale>(PhantomData<fn() -> S>);
31
32impl<S: Scale> fmt::Debug for ScaleAxis<S> {
33 #[inline]
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_tuple("ScaleAxis").field(&S::NAME).finish()
36 }
37}
38
39impl<S: Scale> Space for ScaleAxis<S> {}
40
41pub struct Time<S: Scale, F: TimeFormat = J2000s> {
55 instant: SplitPoint1<ScaleAxis<S>, SecondUnit>,
56 _fmt: PhantomData<fn() -> F>,
57}
58
59impl<S: Scale, F: TimeFormat> Copy for Time<S, F> {}
60
61impl<S: Scale, F: TimeFormat> Clone for Time<S, F> {
62 #[inline]
63 fn clone(&self) -> Self {
64 *self
65 }
66}
67
68impl<S: Scale, F: TimeFormat> PartialEq for Time<S, F> {
69 #[inline]
70 fn eq(&self, other: &Self) -> bool {
71 self.split_seconds() == other.split_seconds()
72 }
73}
74
75impl<S: Scale, F: TimeFormat> PartialOrd for Time<S, F> {
76 #[inline]
77 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
78 let (self_hi, self_lo) = self.split_seconds();
79 let (other_hi, other_lo) = other.split_seconds();
80 match self_hi.partial_cmp(&other_hi) {
81 Some(core::cmp::Ordering::Equal) => self_lo.partial_cmp(&other_lo),
82 ordering => ordering,
83 }
84 }
85}
86
87impl<S: Scale, F: TimeFormat> fmt::Debug for Time<S, F> {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 let (hi, lo) = self.split_seconds();
90 f.debug_struct("Time")
91 .field("scale", &S::NAME)
92 .field("format", &F::NAME)
93 .field("hi_s", &hi)
94 .field("lo_s", &lo)
95 .finish()
96 }
97}
98
99impl<S: CoordinateScale, F> fmt::Display for Time<S, F>
100where
101 F: InfallibleFormatForScale<S>,
102 qtty::Quantity<F::Unit>: fmt::Display,
103{
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 if F::NAME == J2000s::NAME {
106 write!(f, "{} {:.9}", S::NAME, self.total_seconds().value())
107 } else {
108 fmt::Display::fmt(&F::from_time(*self), f)
109 }
110 }
111}
112
113impl fmt::Display for Time<UTC, crate::format::Unix> {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self.try_raw_with(&TimeContext::new()) {
116 Ok(q) => fmt::Display::fmt(&q, f),
117 Err(_) => f.write_str("Unix(<invalid for display>)"),
118 }
119 }
120}
121
122impl<S: CoordinateScale, F> fmt::LowerExp for Time<S, F>
123where
124 F: InfallibleFormatForScale<S>,
125 qtty::Quantity<F::Unit>: fmt::LowerExp,
126{
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 fmt::LowerExp::fmt(&F::from_time(*self), f)
129 }
130}
131
132impl<S: CoordinateScale, F> fmt::UpperExp for Time<S, F>
133where
134 F: InfallibleFormatForScale<S>,
135 qtty::Quantity<F::Unit>: fmt::UpperExp,
136{
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 fmt::UpperExp::fmt(&F::from_time(*self), f)
139 }
140}
141
142impl<S: Scale, F: TimeFormat> Time<S, F> {
143 #[inline]
144 pub(crate) fn from_split(hi: Second, lo: Second) -> Self {
145 debug_assert!(
146 coordinate_pair_ok(hi.value(), lo.value()),
147 "time split pair must not contain NaN"
148 );
149 let instant = SplitPoint1::new(hi, lo);
150 let (hi, lo) = instant.coordinate().pair();
151 debug_assert!(
152 coordinate_pair_ok(hi.value(), lo.value()),
153 "time split pair must not contain NaN"
154 );
155 Self {
156 instant,
157 _fmt: PhantomData,
158 }
159 }
160
161 #[inline]
162 pub(crate) fn try_from_split(hi: Second, lo: Second) -> Result<Self, ConversionError> {
163 if coordinate_pair_ok(hi.value(), lo.value()) {
164 Ok(Self::from_split(hi, lo))
165 } else {
166 Err(ConversionError::NonFinite)
167 }
168 }
169
170 #[inline]
172 pub fn reinterpret<G: TimeFormat>(self) -> Time<S, G> {
173 Time {
174 instant: self.instant,
175 _fmt: PhantomData,
176 }
177 }
178
179 #[inline]
181 pub fn to_j2000s(self) -> Time<S, J2000s> {
182 self.reinterpret()
183 }
184
185 #[inline]
186 pub(crate) fn split_seconds(self) -> (Second, Second) {
187 self.instant.coordinate().pair()
188 }
189
190 #[inline]
191 pub(crate) fn total_seconds(self) -> Second {
192 self.instant.coordinate().total()
193 }
194
195 #[inline]
197 pub fn raw_seconds_pair(self) -> (Second, Second) {
198 self.split_seconds()
199 }
200}
201
202impl<S: CoordinateScale> Time<S, J2000s> {
203 #[inline]
205 pub fn from_raw_j2000_seconds(seconds: Second) -> Result<Self, ConversionError> {
206 Self::try_from_split(seconds, Second::new(0.0))
207 }
208
209 #[inline]
211 pub fn try_from_raw_j2000_seconds_split(
212 hi: Second,
213 lo: Second,
214 ) -> Result<Self, ConversionError> {
215 Self::try_from_split(hi, lo)
216 }
217
218 #[inline]
219 pub(crate) fn raw_j2000_seconds(self) -> Second {
220 self.total_seconds()
221 }
222
223 #[inline]
225 pub fn shifted_by<U>(self, delta: qtty::Quantity<U>) -> Self
226 where
227 U: TimeUnit,
228 {
229 self + delta
230 }
231
232 #[inline]
234 pub fn shifted_back_by<U>(self, delta: qtty::Quantity<U>) -> Self
235 where
236 U: TimeUnit,
237 {
238 self - delta
239 }
240
241 #[inline]
243 pub fn duration_since(self, other: Self) -> Second {
244 self - other
245 }
246
247 #[inline]
249 pub fn duration_until(self, other: Self) -> Second {
250 other - self
251 }
252}
253
254impl<S: CoordinateScale, F: InfallibleFormatForScale<S>> Time<S, F> {
255 #[inline]
257 pub fn raw(self) -> Quantity<F::Unit> {
258 F::from_time(self)
259 }
260
261 #[inline]
263 pub fn quantity(self) -> Quantity<F::Unit> {
264 F::from_time(self)
265 }
266}
267
268impl<S: CoordinateScale, F> Time<S, F>
269where
270 F: FormatForScale<S>,
271{
272 #[inline]
273 pub fn try_raw_with(self, ctx: &TimeContext) -> Result<Quantity<F::Unit>, ConversionError> {
274 F::try_from_time(self, ctx)
275 }
276}
277
278impl<S: Scale, F: TimeFormat> Time<S, F> {
279 #[allow(private_bounds)]
281 #[inline]
282 pub fn to<T>(self) -> T::Output
283 where
284 T: InfallibleConversionTarget<S, F>,
285 {
286 T::convert(self)
287 }
288
289 #[allow(private_bounds)]
291 #[inline]
292 pub fn try_to<T>(self) -> Result<T::Output, ConversionError>
293 where
294 T: ConversionTarget<S, F>,
295 {
296 T::try_convert(self)
297 }
298
299 #[allow(private_bounds)]
301 #[inline]
302 pub fn to_with<T>(self, ctx: &TimeContext) -> Result<T::Output, ConversionError>
303 where
304 T: ContextConversionTarget<S, F>,
305 {
306 T::convert_with(self, ctx)
307 }
308
309 #[allow(private_bounds)]
311 #[inline]
312 pub fn to_scale<S2: Scale>(self) -> Time<S2, F>
313 where
314 S: InfallibleScaleConvert<S2>,
315 {
316 let (hi, lo) = self.split_seconds();
317 let (new_hi, new_lo) = <S as InfallibleScaleConvert<S2>>::convert(hi, lo);
318 Time::from_split(new_hi, new_lo)
319 }
320
321 #[allow(private_bounds)]
323 #[inline]
324 pub fn to_scale_with<S2: Scale>(self, ctx: &TimeContext) -> Result<Time<S2, F>, ConversionError>
325 where
326 S: ContextScaleConvert<S2>,
327 {
328 let (hi, lo) = self.split_seconds();
329 let (new_hi, new_lo) = <S as ContextScaleConvert<S2>>::convert_with(hi, lo, ctx)?;
330 Ok(Time::from_split(new_hi, new_lo))
331 }
332}
333
334impl<S: Scale, F: FormatForScale<S>> Time<S, F> {
335 #[inline]
340 pub fn try_new(raw: Quantity<F::Unit>) -> Result<Self, ConversionError> {
341 F::try_into_time(raw, &TimeContext::new())
342 }
343
344 #[inline]
346 pub fn try_new_with(
347 raw: Quantity<F::Unit>,
348 ctx: &TimeContext,
349 ) -> Result<Self, ConversionError> {
350 F::try_into_time(raw, ctx)
351 }
352}
353
354impl<S: Scale, F: InfallibleFormatForScale<S>> Time<S, F> {
355 #[track_caller]
361 #[inline]
362 pub fn new(value: f64) -> Self {
363 assert!(
364 !value.is_nan(),
365 "time scalar must not be NaN (±∞ is allowed)"
366 );
367 F::into_time(Quantity::<F::Unit>::new(value))
368 }
369}
370
371impl<S: CoordinateScale, F: InfallibleFormatForScale<S>> Time<S, F> {
372 #[inline]
373 pub fn min(self, other: Self) -> Self {
374 if self <= other {
375 self
376 } else {
377 other
378 }
379 }
380
381 #[inline]
382 pub fn max(self, other: Self) -> Self {
383 if self >= other {
384 self
385 } else {
386 other
387 }
388 }
389
390 #[inline]
391 pub fn mean(self, other: Self) -> Self {
392 let t = self.to_j2000s() + ((other.to_j2000s() - self.to_j2000s()) * 0.5);
393 t.reinterpret()
394 }
395}
396
397impl Time<TT, crate::format::JD> {
399 pub const JD_EPOCH_J2000_0: Self = Self {
400 instant: SplitPoint1::from_split(SplitQuantity::from_normalized_parts(
401 Second::new(0.0),
402 Second::new(0.0),
403 )),
404 _fmt: PhantomData,
405 };
406}
407
408impl<S: Scale> Time<S, crate::format::JD> {
409 #[inline]
411 pub fn jd_epoch_tt() -> Self
412 where
413 S: CoordinateScale,
414 {
415 Time::<S, J2000s>::from_raw_j2000_seconds(Second::new(0.0))
416 .expect("J2000 origin")
417 .reinterpret()
418 }
419
420 #[inline]
421 pub fn value(self) -> f64
422 where
423 S: CoordinateScale,
424 {
425 self.raw().value()
426 }
427
428 #[inline]
429 pub fn julian_centuries(self) -> f64
430 where
431 S: CoordinateScale,
432 {
433 jd_to_julian_centuries(self.raw())
434 }
435}
436
437impl<S: Scale> Time<S, crate::format::MJD> {
438 #[inline]
439 pub fn value(self) -> f64
440 where
441 S: CoordinateScale,
442 {
443 self.raw().value()
444 }
445}
446
447impl<S: CoordinateScale, F, U> Add<Quantity<U>> for Time<S, F>
448where
449 F: InfallibleFormatForScale<S>,
450 U: TimeUnit,
451{
452 type Output = Self;
453
454 #[inline]
455 fn add(self, rhs: Quantity<U>) -> Self::Output {
456 Self {
457 instant: self.instant + rhs.to::<SecondUnit>(),
458 _fmt: PhantomData,
459 }
460 }
461}
462
463impl<S: CoordinateScale, F, U> Sub<Quantity<U>> for Time<S, F>
464where
465 F: InfallibleFormatForScale<S>,
466 U: TimeUnit,
467{
468 type Output = Self;
469
470 #[inline]
471 fn sub(self, rhs: Quantity<U>) -> Self::Output {
472 Self {
473 instant: self.instant - rhs.to::<SecondUnit>(),
474 _fmt: PhantomData,
475 }
476 }
477}
478
479impl<S: CoordinateScale, F> Sub for Time<S, F>
480where
481 F: InfallibleFormatForScale<S>,
482 F::Unit: TimeUnit,
483{
484 type Output = Quantity<F::Unit>;
485
486 #[inline]
487 fn sub(self, rhs: Self) -> Self::Output {
488 let delta: Second = self.instant - rhs.instant;
489 delta.to::<F::Unit>()
490 }
491}
492
493impl<S: CoordinateScale, F, U> AddAssign<Quantity<U>> for Time<S, F>
494where
495 F: InfallibleFormatForScale<S>,
496 U: TimeUnit,
497{
498 #[inline]
499 fn add_assign(&mut self, rhs: Quantity<U>) {
500 *self = *self + rhs;
501 }
502}
503
504impl<S: CoordinateScale, F, U> SubAssign<Quantity<U>> for Time<S, F>
505where
506 F: InfallibleFormatForScale<S>,
507 U: TimeUnit,
508{
509 #[inline]
510 fn sub_assign(&mut self, rhs: Quantity<U>) {
511 *self = *self - rhs;
512 }
513}