1use alloc::borrow::ToOwned;
8use alloc::string::String;
9use core::fmt;
10use core::fmt::Display;
11use core::fmt::Formatter;
12use core::ops::Add;
13use core::ops::Sub;
14use core::str::FromStr;
15
16use lox_core::f64;
17use lox_core::i64;
18use lox_core::types::units::Days;
19use lox_test_utils::approx_eq::ApproxEq;
20use lox_test_utils::approx_eq::results::ApproxEqResults;
21use thiserror::Error;
22
23use crate::calendar_dates::CalendarDate;
24use crate::calendar_dates::Date;
25use crate::calendar_dates::DateError;
26use crate::deltas::TimeDelta;
27use crate::deltas::ToDelta;
28use crate::julian_dates::Epoch;
29use crate::julian_dates::JulianDate;
30use crate::julian_dates::Unit;
31use crate::offsets::DefaultOffsetProvider;
32use crate::offsets::Offset;
33use crate::offsets::TryOffset;
34use crate::subsecond::Subsecond;
35use crate::time_of_day::CivilTime;
36use crate::time_of_day::TimeOfDay;
37use crate::time_of_day::TimeOfDayError;
38use crate::time_scales::DynTimeScale;
39use crate::time_scales::Tai;
40use crate::time_scales::Tcb;
41use crate::time_scales::Tcg;
42use crate::time_scales::Tdb;
43use crate::time_scales::TimeScale;
44use crate::time_scales::Tt;
45use crate::time_scales::Ut1;
46
47#[derive(Clone, Debug, Error, PartialEq, Eq)]
49pub enum TimeError {
50 #[error(transparent)]
52 DateError(#[from] DateError),
53 #[error(transparent)]
55 TimeError(#[from] TimeOfDayError),
56 #[error("leap seconds do not exist in continuous time scales; use `Utc` instead")]
58 LeapSecondOutsideUtc,
59 #[error("invalid ISO string `{0}`")]
61 InvalidIsoString(String),
62 #[error("year {year} is outside the representable range relative to J2000")]
64 DateOutOfRange {
65 year: i64,
67 },
68}
69
70#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct Time<T: TimeScale> {
77 scale: T,
78 delta: TimeDelta,
79}
80
81pub type DynTime = Time<DynTimeScale>;
83
84impl<T: TimeScale> Time<T> {
85 pub const fn new(scale: T, seconds: i64, subsecond: Subsecond) -> Self {
88 let delta = TimeDelta::new(seconds, subsecond.as_attoseconds());
89 Self { scale, delta }
90 }
91
92 pub fn from_date_and_time(scale: T, date: Date, time: TimeOfDay) -> Result<Self, TimeError> {
99 let secs_f = date.days_since_j2000() * f64::consts::SECONDS_PER_DAY;
100 if !secs_f.is_finite() || secs_f < i64::MIN as f64 || secs_f > i64::MAX as f64 {
101 return Err(TimeError::DateOutOfRange { year: date.year() });
102 }
103 let mut seconds = secs_f as i64;
104 if time.second() == 60 {
105 return Err(TimeError::LeapSecondOutsideUtc);
106 }
107 seconds += time.second_of_day();
108 Ok(Self::new(scale, seconds, time.subsecond()))
109 }
110
111 pub fn from_iso(scale: T, iso: &str) -> Result<Self, TimeError> {
117 let Some((date, time_and_scale)) = iso.split_once('T') else {
118 return Err(TimeError::InvalidIsoString(iso.to_owned()));
119 };
120
121 let mut parts = time_and_scale.split_whitespace();
122 let (time, scale_abbrv) = match (parts.next(), parts.next(), parts.next()) {
123 (Some(a), Some(b), None) => (a, b),
124 _ => (time_and_scale, ""),
125 };
126
127 if !scale_abbrv.is_empty() && scale_abbrv != scale.abbreviation() {
128 return Err(TimeError::InvalidIsoString(iso.to_owned()));
129 }
130
131 let date: Date = date.parse()?;
132 let time: TimeOfDay = time.parse()?;
133
134 Self::from_date_and_time(scale, date, time)
135 }
136
137 pub const fn from_delta(scale: T, delta: TimeDelta) -> Self {
139 Self { scale, delta }
140 }
141
142 pub const fn from_epoch(scale: T, epoch: Epoch) -> Self {
147 match epoch {
148 Epoch::JulianDate => Self {
149 scale,
150 delta: TimeDelta::from_seconds(-i64::consts::SECONDS_BETWEEN_JD_AND_J2000),
151 },
152 Epoch::ModifiedJulianDate => Self {
153 scale,
154 delta: TimeDelta::from_seconds(-i64::consts::SECONDS_BETWEEN_MJD_AND_J2000),
155 },
156 Epoch::J1950 => Self {
157 scale,
158 delta: TimeDelta::from_seconds(-i64::consts::SECONDS_BETWEEN_J1950_AND_J2000),
159 },
160 Epoch::J2000 => Self {
161 scale,
162 delta: TimeDelta::ZERO,
163 },
164 }
165 }
166
167 pub fn from_julian_date(scale: T, julian_date: Days, epoch: Epoch) -> Self {
170 let delta = TimeDelta::from_julian_date(julian_date, epoch);
171 Self { scale, delta }
172 }
173
174 pub fn from_two_part_julian_date(scale: T, jd1: Days, jd2: Days) -> Self {
176 let delta = TimeDelta::from_two_part_julian_date(jd1, jd2);
177 Self { scale, delta }
178 }
179
180 pub fn builder_with_scale(scale: T) -> TimeBuilder<T> {
182 TimeBuilder::new(scale)
183 }
184
185 pub fn scale(&self) -> T
187 where
188 T: Copy,
189 {
190 self.scale
191 }
192
193 pub fn with_scale<S: TimeScale>(&self, scale: S) -> Time<S> {
197 Time::from_delta(scale, self.delta)
198 }
199
200 pub fn try_to_scale<S, P>(&self, scale: S, provider: &P) -> Result<Time<S>, P::Error>
202 where
203 T: Copy,
204 S: TimeScale + Copy,
205 P: TryOffset<T, S> + ?Sized,
206 {
207 let offset = provider.try_offset(self.scale, scale, self.to_delta())?;
208 Ok(self.with_scale_and_delta(scale, offset))
209 }
210
211 pub fn to_scale<S>(&self, scale: S) -> Time<S>
213 where
214 T: Copy,
215 S: TimeScale + Copy,
216 DefaultOffsetProvider: Offset<T, S>,
217 {
218 let offset = DefaultOffsetProvider.offset(self.scale, scale, self.to_delta());
219 self.with_scale_and_delta(scale, offset)
220 }
221
222 pub fn with_scale_and_delta<S: TimeScale>(&self, scale: S, delta: TimeDelta) -> Time<S> {
227 Time::from_delta(scale, self.to_delta() + delta)
228 }
229
230 pub fn jd0(scale: T) -> Self {
232 Self::from_epoch(scale, Epoch::JulianDate)
233 }
234
235 pub fn mjd0(scale: T) -> Self {
237 Self::from_epoch(scale, Epoch::ModifiedJulianDate)
238 }
239
240 pub fn j1950(scale: T) -> Self {
242 Self::from_epoch(scale, Epoch::J1950)
243 }
244
245 pub fn j2000(scale: T) -> Self {
247 Self::from_epoch(scale, Epoch::J2000)
248 }
249
250 pub fn as_seconds_and_subsecond(&self) -> Option<(i64, Subsecond)> {
252 self.delta.as_seconds_and_subsecond()
253 }
254
255 pub fn seconds(&self) -> Option<i64> {
257 self.as_seconds_and_subsecond().map(|(seconds, _)| seconds)
258 }
259
260 pub fn subsecond(&self) -> Option<f64> {
262 self.as_seconds_and_subsecond()
263 .map(|(_, subsecond)| subsecond.as_seconds_f64())
264 }
265}
266
267impl<T: TimeScale + Into<DynTimeScale>> Time<T> {
268 pub fn into_dyn(self) -> DynTime {
270 Time::from_delta(self.scale.into(), self.delta)
271 }
272}
273
274impl<T: TimeScale + Eq> PartialOrd for Time<T> {
275 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
276 Some(self.cmp(other))
277 }
278}
279
280impl<T: TimeScale + Eq> Ord for Time<T> {
281 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
282 assert!(
283 self.scale == other.scale,
284 "cannot compare `Time` objects with different time scales"
285 );
286 self.delta.cmp(&other.delta)
287 }
288}
289
290#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
292#[error("cannot compare `Time` objects with different time scales `{lhs}` and `{rhs}`")]
293pub struct TimeScaleMismatch {
294 lhs: DynTimeScale,
295 rhs: DynTimeScale,
296}
297
298impl DynTime {
299 pub fn checked_cmp(&self, other: &Self) -> Result<core::cmp::Ordering, TimeScaleMismatch> {
301 if self.scale != other.scale {
302 return Err(TimeScaleMismatch {
303 lhs: self.scale,
304 rhs: other.scale,
305 });
306 }
307 Ok(self.delta.cmp(&other.delta))
308 }
309}
310
311impl<T: TimeScale + core::fmt::Debug> ApproxEq for Time<T> {
312 fn approx_eq(&self, rhs: &Self, atol: f64, rtol: f64) -> ApproxEqResults {
313 self.to_delta().approx_eq(&rhs.to_delta(), atol, rtol)
314 }
315}
316
317impl<T: TimeScale> ToDelta for Time<T> {
318 fn to_delta(&self) -> TimeDelta {
319 self.delta
320 }
321}
322
323impl<T: TimeScale> JulianDate for Time<T> {
324 fn julian_date(&self, epoch: Epoch, unit: Unit) -> f64 {
325 self.delta.julian_date(epoch, unit)
326 }
327}
328
329impl<T: TimeScale> Display for Time<T> {
330 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
331 let precision = f.precision().unwrap_or(3);
332 write!(
333 f,
334 "{}T{:.*} {}",
335 self.date(),
336 precision,
337 self.time(),
338 self.scale.abbreviation()
339 )
340 }
341}
342
343impl FromStr for Time<Tai> {
344 type Err = TimeError;
345
346 fn from_str(iso: &str) -> Result<Self, Self::Err> {
347 Self::from_iso(Tai, iso)
348 }
349}
350
351impl FromStr for Time<Tcb> {
352 type Err = TimeError;
353
354 fn from_str(iso: &str) -> Result<Self, Self::Err> {
355 Self::from_iso(Tcb, iso)
356 }
357}
358
359impl FromStr for Time<Tcg> {
360 type Err = TimeError;
361
362 fn from_str(iso: &str) -> Result<Self, Self::Err> {
363 Self::from_iso(Tcg, iso)
364 }
365}
366
367impl FromStr for Time<Tdb> {
368 type Err = TimeError;
369
370 fn from_str(iso: &str) -> Result<Self, Self::Err> {
371 Self::from_iso(Tdb, iso)
372 }
373}
374
375impl FromStr for Time<Tt> {
376 type Err = TimeError;
377
378 fn from_str(iso: &str) -> Result<Self, Self::Err> {
379 Self::from_iso(Tt, iso)
380 }
381}
382
383impl FromStr for Time<Ut1> {
384 type Err = TimeError;
385
386 fn from_str(iso: &str) -> Result<Self, Self::Err> {
387 Self::from_iso(Ut1, iso)
388 }
389}
390
391impl<T: TimeScale> Add<TimeDelta> for Time<T> {
392 type Output = Self;
393
394 fn add(self, rhs: TimeDelta) -> Self::Output {
395 Self {
396 scale: self.scale,
397 delta: self.delta + rhs,
398 }
399 }
400}
401
402impl<T: TimeScale> Sub<TimeDelta> for Time<T> {
403 type Output = Self;
404
405 fn sub(self, rhs: TimeDelta) -> Self::Output {
406 Self {
407 scale: self.scale,
408 delta: self.delta - rhs,
409 }
410 }
411}
412
413impl<T: TimeScale> Sub<Time<T>> for Time<T> {
414 type Output = TimeDelta;
415
416 fn sub(self, rhs: Time<T>) -> Self::Output {
417 self.delta - rhs.delta
418 }
419}
420
421impl<T: TimeScale> CivilTime for Time<T> {
422 fn time(&self) -> TimeOfDay {
423 debug_assert!(self.delta.is_finite());
424 let (seconds, subsecond) = self.as_seconds_and_subsecond().unwrap();
425 TimeOfDay::from_seconds_since_j2000(seconds).with_subsecond(subsecond)
426 }
427}
428
429impl<T: TimeScale> CalendarDate for Time<T> {
430 fn date(&self) -> Date {
431 debug_assert!(self.delta.is_finite());
432 let seconds = self.seconds().unwrap();
433 Date::from_seconds_since_j2000(seconds)
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Eq)]
439pub struct TimeBuilder<T: TimeScale> {
440 scale: T,
441 date: Result<Date, DateError>,
442 time: Result<TimeOfDay, TimeOfDayError>,
443}
444
445impl<T: TimeScale> TimeBuilder<T> {
446 pub fn new(scale: T) -> Self {
448 Self {
449 scale,
450 date: Ok(Date::default()),
451 time: Ok(TimeOfDay::default()),
452 }
453 }
454
455 pub fn with_ymd(self, year: i64, month: u8, day: u8) -> Self {
457 Self {
458 date: Date::new(year, month, day),
459 ..self
460 }
461 }
462
463 pub fn with_doy(self, year: i64, day_of_year: u16) -> Self {
465 Self {
466 date: Date::from_day_of_year(year, day_of_year),
467 ..self
468 }
469 }
470
471 pub fn with_hms(self, hour: u8, minute: u8, seconds: f64) -> Self {
473 Self {
474 time: TimeOfDay::from_hms(hour, minute, seconds),
475 ..self
476 }
477 }
478
479 pub fn build(self) -> Result<Time<T>, TimeError> {
487 let date = self.date?;
488 let time = self.time?;
489 Time::from_date_and_time(self.scale, date, time)
490 }
491}
492
493#[macro_export]
509macro_rules! time {
510 ($scale:expr, $year:literal, $month:literal, $day:literal) => {
511 $crate::time::Time::builder_with_scale($scale)
512 .with_ymd($year, $month, $day)
513 .build()
514 };
515 ($scale:expr, $year:literal, $month:literal, $day:literal, $hour:literal) => {
516 $crate::time::Time::builder_with_scale($scale)
517 .with_ymd($year, $month, $day)
518 .with_hms($hour, 0, 0.0)
519 .build()
520 };
521 ($scale:expr, $year:literal, $month:literal, $day:literal, $hour:literal, $minute:literal) => {
522 $crate::time::Time::builder_with_scale($scale)
523 .with_ymd($year, $month, $day)
524 .with_hms($hour, $minute, 0.0)
525 .build()
526 };
527 ($scale:expr, $year:literal, $month:literal, $day:literal, $hour:literal, $minute:literal, $second:literal) => {
528 $crate::time::Time::builder_with_scale($scale)
529 .with_ymd($year, $month, $day)
530 .with_hms($hour, $minute, $second)
531 .build()
532 };
533}
534
535#[cfg(test)]
536mod tests {
537 use alloc::format;
538 use alloc::string::ToString;
539 use core::cmp::Ordering;
540 use lox_core::f64::consts::DAYS_PER_JULIAN_CENTURY;
541 use lox_test_utils::assert_approx_eq;
542 use rstest::rstest;
543
544 use crate::Time;
545 use crate::time_scales::{DynTimeScale, Tai, Tdb, Tt};
546 use lox_core::i64::consts::{SECONDS_PER_DAY, SECONDS_PER_HALF_DAY};
547
548 use super::*;
549
550 use lox_core::i64::consts::{
551 SECONDS_BETWEEN_J1950_AND_J2000, SECONDS_BETWEEN_JD_AND_J2000,
552 SECONDS_BETWEEN_MJD_AND_J2000, SECONDS_PER_HOUR, SECONDS_PER_JULIAN_CENTURY,
553 SECONDS_PER_MINUTE,
554 };
555
556 #[test]
557 fn test_time_builder() {
558 let time = Time::builder_with_scale(Tai)
559 .with_ymd(2000, 1, 1)
560 .build()
561 .unwrap();
562 assert_eq!(time.seconds(), Some(-SECONDS_PER_HALF_DAY));
563 let time = Time::builder_with_scale(Tai)
564 .with_ymd(2000, 1, 1)
565 .with_hms(12, 0, 0.0)
566 .build()
567 .unwrap();
568 assert_eq!(time.seconds(), Some(0));
569 }
570
571 #[test]
572 fn test_time_from_seconds() {
573 let scale = Tai;
574 let seconds = 1234567890;
575 let subsecond = Subsecond::from_f64(0.9876543210).unwrap();
576 let expected = Time::new(scale, seconds, subsecond);
577 let actual = Time::new(scale, seconds, subsecond);
578 assert_eq!(expected, actual);
579 }
580
581 #[rstest]
582 #[case(Epoch::JulianDate, -SECONDS_BETWEEN_JD_AND_J2000)]
583 #[case(Epoch::ModifiedJulianDate, -SECONDS_BETWEEN_MJD_AND_J2000)]
584 #[case(Epoch::J1950, -SECONDS_BETWEEN_J1950_AND_J2000)]
585 #[case(Epoch::J2000, 0)]
586 fn test_time_from_julian_date(#[case] epoch: Epoch, #[case] seconds: i64) {
587 let time = Time::from_julian_date(Tai, 0.0, epoch);
588 assert_eq!(time.seconds(), Some(seconds));
589 }
590
591 #[test]
592 fn test_time_from_julian_date_subsecond() {
593 let time = Time::from_julian_date(Tai, 0.3 / f64::consts::SECONDS_PER_DAY, Epoch::J2000);
594 assert_approx_eq!(time.subsecond().unwrap(), 0.3, atol <= 1e-15);
595 }
596
597 #[test]
598 fn test_time_from_two_part_julian_date() {
599 let t0 = time!(Tai, 2024, 7, 11, 8, 2, 14.0).unwrap();
600 let (jd1, jd2) = t0.two_part_julian_date();
601 let t1 = Time::from_two_part_julian_date(Tai, jd1, jd2);
602 assert_approx_eq!(t0, t1);
603 }
604
605 #[rstest]
606 #[case(i64::MAX as f64, 1.0)]
607 #[case(i64::MIN as f64, -1.0)]
608 fn test_time_from_two_part_julian_date_edge_cases(#[case] jd1: f64, #[case] jd2: f64) {
609 let time = Time::from_two_part_julian_date(Tai, jd1, jd2);
610 assert!(!time.to_delta().is_finite());
612 }
613
614 #[rstest]
615 #[case(
616 (SECONDS_BETWEEN_JD_AND_J2000 as f64) / f64::consts::SECONDS_PER_DAY,
617 0.0,
618 0,
619 )]
620 #[case(
621 (SECONDS_BETWEEN_JD_AND_J2000 as f64 + 0.5) / f64::consts::SECONDS_PER_DAY,
622 0.6 / f64::consts::SECONDS_PER_DAY,
623 1,
624 )]
625 #[case(
626 (SECONDS_BETWEEN_JD_AND_J2000 as f64 + 0.5) / f64::consts::SECONDS_PER_DAY,
627 -0.6 / f64::consts::SECONDS_PER_DAY,
628 -1,
629 )]
630 fn test_time_from_two_part_julian_date_adjustments(
631 #[case] jd1: f64,
632 #[case] jd2: f64,
633 #[case] expected: i64,
634 ) {
635 let time = Time::from_two_part_julian_date(Tai, jd1, jd2);
636 assert_eq!(time.seconds(), Some(expected));
637 }
638
639 #[test]
640 fn test_time_with_scale_and_delta() {
641 let tai: Time<Tai> = Time::default();
642 let delta = TimeDelta::from_seconds(20);
643 let tdb = tai.with_scale_and_delta(Tdb, delta);
644 assert_eq!(tdb.scale(), Tdb);
645 assert_eq!(tdb.seconds(), Some(tai.seconds().unwrap() + 20));
646 }
647
648 #[rstest]
649 #[case(f64::INFINITY)]
650 #[case(-f64::INFINITY)]
651 #[case(f64::NAN)]
652 #[case(-f64::NAN)]
653 #[case(i64::MAX as f64 / f64::consts::SECONDS_PER_DAY + 1.0)]
654 #[case(i64::MIN as f64 / f64::consts::SECONDS_PER_DAY - 1.0)]
655 fn test_time_from_julian_date_special_values(#[case] julian_date: f64) {
656 let time = Time::from_julian_date(Tai, julian_date, Epoch::J2000);
657 assert!(!time.to_delta().is_finite());
659 }
660
661 #[rstest]
662 #[case("2000-01-01T00:00:00", Ok(time!(Tai, 2000, 1, 1).unwrap()))]
663 #[case("2000-01-01T00:00:00 TAI", Ok(time!(Tai, 2000, 1, 1).unwrap()))]
664 #[case("2000-1-01T00:00:00", Err(TimeError::DateError(DateError::InvalidIsoString("2000-1-01".to_string()))))]
665 #[case("2000-01-01T0:00:00", Err(TimeError::TimeError(TimeOfDayError::InvalidIsoString("0:00:00".to_string()))))]
666 #[case("2000-01-01-00:00:00", Err(TimeError::InvalidIsoString("2000-01-01-00:00:00".to_string())))]
667 #[case("2000-01-01T00:00:00 UTC", Err(TimeError::InvalidIsoString("2000-01-01T00:00:00 UTC".to_string())))]
668 fn test_time_from_str_tai(#[case] iso: &str, #[case] expected: Result<Time<Tai>, TimeError>) {
669 let actual: Result<Time<Tai>, TimeError> = iso.parse();
670 assert_eq!(actual, expected)
671 }
672
673 #[rstest]
674 #[case("2000-01-01T00:00:00", Ok(time!(Tcb, 2000, 1, 1).unwrap()))]
675 #[case("2000-01-01T00:00:00 TCB", Ok(time!(Tcb, 2000, 1, 1).unwrap()))]
676 #[case("2000-1-01T00:00:00", Err(TimeError::DateError(DateError::InvalidIsoString("2000-1-01".to_string()))))]
677 #[case("2000-01-01T0:00:00", Err(TimeError::TimeError(TimeOfDayError::InvalidIsoString("0:00:00".to_string()))))]
678 #[case("2000-01-01-00:00:00", Err(TimeError::InvalidIsoString("2000-01-01-00:00:00".to_string())))]
679 #[case("2000-01-01T00:00:00 UTC", Err(TimeError::InvalidIsoString("2000-01-01T00:00:00 UTC".to_string())))]
680 fn test_time_from_str_tcb(#[case] iso: &str, #[case] expected: Result<Time<Tcb>, TimeError>) {
681 let actual: Result<Time<Tcb>, TimeError> = iso.parse();
682 assert_eq!(actual, expected)
683 }
684
685 #[rstest]
686 #[case("2000-01-01T00:00:00", Ok(time!(Tcg, 2000, 1, 1).unwrap()))]
687 #[case("2000-01-01T00:00:00 TCG", Ok(time!(Tcg, 2000, 1, 1).unwrap()))]
688 #[case("2000-1-01T00:00:00", Err(TimeError::DateError(DateError::InvalidIsoString("2000-1-01".to_string()))))]
689 #[case("2000-01-01T0:00:00", Err(TimeError::TimeError(TimeOfDayError::InvalidIsoString("0:00:00".to_string()))))]
690 #[case("2000-01-01-00:00:00", Err(TimeError::InvalidIsoString("2000-01-01-00:00:00".to_string())))]
691 #[case("2000-01-01T00:00:00 UTC", Err(TimeError::InvalidIsoString("2000-01-01T00:00:00 UTC".to_string())))]
692 fn test_time_from_str_tcg(#[case] iso: &str, #[case] expected: Result<Time<Tcg>, TimeError>) {
693 let actual: Result<Time<Tcg>, TimeError> = iso.parse();
694 assert_eq!(actual, expected)
695 }
696
697 #[rstest]
698 #[case("2000-01-01T00:00:00", Ok(time!(Tdb, 2000, 1, 1).unwrap()))]
699 #[case("2000-01-01T00:00:00 TDB", Ok(time!(Tdb, 2000, 1, 1).unwrap()))]
700 #[case("2000-1-01T00:00:00", Err(TimeError::DateError(DateError::InvalidIsoString("2000-1-01".to_string()))))]
701 #[case("2000-01-01T0:00:00", Err(TimeError::TimeError(TimeOfDayError::InvalidIsoString("0:00:00".to_string()))))]
702 #[case("2000-01-01-00:00:00", Err(TimeError::InvalidIsoString("2000-01-01-00:00:00".to_string())))]
703 #[case("2000-01-01T00:00:00 UTC", Err(TimeError::InvalidIsoString("2000-01-01T00:00:00 UTC".to_string())))]
704 fn test_time_from_str_tdb(#[case] iso: &str, #[case] expected: Result<Time<Tdb>, TimeError>) {
705 let actual: Result<Time<Tdb>, TimeError> = iso.parse();
706 assert_eq!(actual, expected)
707 }
708
709 #[rstest]
710 #[case("2000-01-01T00:00:00", Ok(time!(Tt, 2000, 1, 1).unwrap()))]
711 #[case("2000-01-01T00:00:00 TT", Ok(time!(Tt, 2000, 1, 1).unwrap()))]
712 #[case("2000-1-01T00:00:00", Err(TimeError::DateError(DateError::InvalidIsoString("2000-1-01".to_string()))))]
713 #[case("2000-01-01T0:00:00", Err(TimeError::TimeError(TimeOfDayError::InvalidIsoString("0:00:00".to_string()))))]
714 #[case("2000-01-01-00:00:00", Err(TimeError::InvalidIsoString("2000-01-01-00:00:00".to_string())))]
715 #[case("2000-01-01T00:00:00 UTC", Err(TimeError::InvalidIsoString("2000-01-01T00:00:00 UTC".to_string())))]
716 fn test_time_from_str_tt(#[case] iso: &str, #[case] expected: Result<Time<Tt>, TimeError>) {
717 let actual: Result<Time<Tt>, TimeError> = iso.parse();
718 assert_eq!(actual, expected)
719 }
720
721 #[rstest]
722 #[case("2000-01-01T00:00:00", Ok(time!(Ut1, 2000, 1, 1).unwrap()))]
723 #[case("2000-01-01T00:00:00 UT1", Ok(time!(Ut1, 2000, 1, 1).unwrap()))]
724 #[case("2000-1-01T00:00:00", Err(TimeError::DateError(DateError::InvalidIsoString("2000-1-01".to_string()))))]
725 #[case("2000-01-01T0:00:00", Err(TimeError::TimeError(TimeOfDayError::InvalidIsoString("0:00:00".to_string()))))]
726 #[case("2000-01-01-00:00:00", Err(TimeError::InvalidIsoString("2000-01-01-00:00:00".to_string())))]
727 #[case("2000-01-01T00:00:00 UTC", Err(TimeError::InvalidIsoString("2000-01-01T00:00:00 UTC".to_string())))]
728 fn test_time_from_str_ut1(#[case] iso: &str, #[case] expected: Result<Time<Ut1>, TimeError>) {
729 let actual: Result<Time<Ut1>, TimeError> = iso.parse();
730 assert_eq!(actual, expected)
731 }
732
733 #[test]
734 fn test_time_display() {
735 let time = Time::j2000(Tai);
736 let expected = "2000-01-01T12:00:00.000 TAI".to_string();
737 let actual = time.to_string();
738 assert_eq!(expected, actual);
739 let expected = "2000-01-01T12:00:00.000000000000000 TAI".to_string();
740 let actual = format!("{time:.15}");
741 assert_eq!(expected, actual);
742 }
743
744 #[test]
745 fn test_time_j2000() {
746 let actual = Time::j2000(Tai);
747 let expected = Time {
748 scale: Tai,
749 ..Default::default()
750 };
751 assert_eq!(expected, actual);
752 }
753
754 #[test]
755 fn test_time_jd0() {
756 let actual = Time::jd0(Tai);
757 let expected = Time::new(Tai, -211813488000, Subsecond::default());
758 assert_eq!(expected, actual);
759 }
760
761 #[test]
762 fn test_time_seconds() {
763 let time = Time::new(Tai, 1234567890, Subsecond::from_f64(0.9876543210).unwrap());
764 let expected = Some(1234567890);
765 let actual = time.seconds();
766 assert_eq!(
767 expected, actual,
768 "expected Time to have {expected:?} seconds, but got {actual:?}"
769 );
770 }
771
772 #[test]
773 fn test_julian_date() {
774 let time = Time::jd0(Tdb);
775 assert_eq!(time.julian_date(Epoch::JulianDate, Unit::Days), 0.0);
776 assert_eq!(time.seconds_since_julian_epoch(), 0.0);
777 assert_eq!(time.days_since_julian_epoch(), 0.0);
778 assert_eq!(time.centuries_since_julian_epoch(), 0.0);
779 }
780
781 #[test]
782 fn test_modified_julian_date() {
783 let time = Time::mjd0(Tdb);
784 assert_eq!(time.julian_date(Epoch::ModifiedJulianDate, Unit::Days), 0.0);
785 assert_eq!(time.seconds_since_modified_julian_epoch(), 0.0);
786 assert_eq!(time.days_since_modified_julian_epoch(), 0.0);
787 assert_eq!(time.centuries_since_modified_julian_epoch(), 0.0);
788 }
789
790 #[test]
791 fn test_j1950() {
792 let time = Time::j1950(Tdb);
793 assert_eq!(time.julian_date(Epoch::J1950, Unit::Days), 0.0);
794 assert_eq!(time.seconds_since_j1950(), 0.0);
795 assert_eq!(time.days_since_j1950(), 0.0);
796 assert_eq!(time.centuries_since_j1950(), 0.0);
797 }
798
799 #[test]
800 fn test_j2000() {
801 let time = Time::j2000(Tdb);
802 assert_eq!(time.julian_date(Epoch::J2000, Unit::Days), 0.0);
803 assert_eq!(time.seconds_since_j2000(), 0.0);
804 assert_eq!(time.days_since_j2000(), 0.0);
805 assert_eq!(time.centuries_since_j2000(), 0.0);
806 }
807
808 #[test]
809 fn test_j2100() {
810 let time = time!(Tdb, 2100, 1, 1, 12).unwrap();
811 assert_eq!(
812 time.julian_date(Epoch::J2000, Unit::Days),
813 DAYS_PER_JULIAN_CENTURY
814 );
815 assert_eq!(time.seconds_since_j2000(), 3155760000.0);
816 assert_eq!(time.days_since_j2000(), DAYS_PER_JULIAN_CENTURY);
817 assert_eq!(time.centuries_since_j2000(), 1.0);
818 }
819
820 #[test]
821 fn test_two_part_julian_date() {
822 let time = time!(Tdb, 2100, 1, 2).unwrap();
823 let (jd1, jd2) = time.two_part_julian_date();
824 assert_eq!(jd1, 2451545.0 + DAYS_PER_JULIAN_CENTURY);
825 assert_eq!(jd2, 0.5);
826 }
827
828 #[test]
829 fn test_time_macro() {
830 let time = time!(Tai, 2000, 1, 1).unwrap();
831 assert_eq!(time.seconds(), Some(-SECONDS_PER_HALF_DAY));
832 let time = time!(Tai, 2000, 1, 1, 12).unwrap();
833 assert_eq!(time.seconds(), Some(0));
834 let time = time!(Tai, 2000, 1, 1, 12, 0).unwrap();
835 assert_eq!(time.seconds(), Some(0));
836 let time = time!(Tai, 2000, 1, 1, 12, 0, 0.0).unwrap();
837 assert_eq!(time.seconds(), Some(0));
838 }
843
844 #[test]
845 fn test_time_subsecond() {
846 let time = Time::new(Tai, 0, Subsecond::from_f64(0.123).unwrap());
847 assert_eq!(time.subsecond(), Some(0.123));
848 }
849
850 #[rstest]
851 #[case::zero_delta(Time::default(), Time::default(), TimeDelta::default())]
852 #[case::positive_delta(Time::default(), Time::new(Tai, 1, Subsecond::default()), TimeDelta::from_seconds(-1))]
853 #[case::negative_delta(Time::default(), Time::new(Tai, -1, Subsecond::default()), TimeDelta::from_seconds(1))]
854 fn test_time_delta(
855 #[case] lhs: Time<Tai>,
856 #[case] rhs: Time<Tai>,
857 #[case] expected: TimeDelta,
858 ) {
859 assert_eq!(expected, lhs - rhs);
860 }
861
862 const MAX_FEMTOSECONDS: Subsecond = Subsecond::from_attoseconds(999_999_999_999_999);
863
864 #[rstest]
865 #[case::zero_value(Time::new(Tai, 0, Subsecond::default()), 12)]
866 #[case::one_femtosecond_less_than_an_hour(Time::new(Tai, SECONDS_PER_HOUR - 1, MAX_FEMTOSECONDS), 12)]
867 #[case::exactly_one_hour(Time::new(Tai, SECONDS_PER_HOUR, Subsecond::default()), 13)]
868 #[case::half_day(Time::new(Tai, SECONDS_PER_DAY / 2, Subsecond::default()), 0)]
869 #[case::negative_half_day(Time::new(Tai, -SECONDS_PER_DAY / 2, Subsecond::default()), 0)]
870 #[case::one_day_and_one_hour(Time::new(Tai, SECONDS_PER_HOUR * 25, Subsecond::default()), 13)]
871 #[case::one_femtosecond_less_than_the_epoch(Time::new(Tai, -1, MAX_FEMTOSECONDS), 11)]
872 #[case::one_hour_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_HOUR, Subsecond::default()), 11)]
873 #[case::one_hour_and_one_femtosecond_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_HOUR - 1, MAX_FEMTOSECONDS), 10)]
874 #[case::one_day_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_DAY, Subsecond::default()), 12)]
875 #[case::one_day_and_one_hour_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_DAY - SECONDS_PER_HOUR, Subsecond::default()), 11)]
876 #[case::two_days_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_DAY * 2, Subsecond::default()), 12)]
877 fn test_time_civil_time_hour(#[case] time: Time<Tai>, #[case] expected: u8) {
878 let actual = time.hour();
879 assert_eq!(expected, actual);
880 }
881
882 #[rstest]
883 #[case::zero_value(Time::new(Tai, 0, Subsecond::default()), 0)]
884 #[case::one_femtosecond_less_than_one_minute(Time::new(Tai, SECONDS_PER_MINUTE - 1, MAX_FEMTOSECONDS), 0)]
885 #[case::one_minute(Time::new(Tai, SECONDS_PER_MINUTE, Subsecond::default()), 1)]
886 #[case::one_femtosecond_less_than_an_hour(Time::new(Tai, SECONDS_PER_HOUR - 1, MAX_FEMTOSECONDS), 59)]
887 #[case::exactly_one_hour(Time::new(Tai, SECONDS_PER_HOUR, Subsecond::default()), 0)]
888 #[case::one_hour_and_one_minute(Time::new(Tai, SECONDS_PER_HOUR + SECONDS_PER_MINUTE, Subsecond::default()), 1)]
889 #[case::one_hour_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_HOUR, Subsecond::default()), 0)]
890 #[case::one_femtosecond_less_than_the_epoch(Time::new(Tai, -1, MAX_FEMTOSECONDS), 59)]
891 #[case::one_minute_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_MINUTE, Subsecond::default()), 59)]
892 #[case::one_minute_and_one_femtosecond_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_MINUTE - 1, MAX_FEMTOSECONDS), 58)]
893 fn test_time_civil_time_minute(#[case] time: Time<Tai>, #[case] expected: u8) {
894 let actual = time.minute();
895 assert_eq!(expected, actual);
896 }
897
898 #[rstest]
899 #[case::zero_value(Time::new(Tai, 0, Subsecond::default()), 0)]
900 #[case::one_femtosecond_less_than_one_second(Time::new(Tai, 0, MAX_FEMTOSECONDS), 0)]
901 #[case::one_second(Time::new(Tai, 1, Subsecond::default()), 1)]
902 #[case::one_femtosecond_less_than_a_minute(Time::new(Tai, SECONDS_PER_MINUTE - 1, MAX_FEMTOSECONDS), 59)]
903 #[case::exactly_one_minute(Time::new(Tai, SECONDS_PER_MINUTE, Subsecond::default()), 0)]
904 #[case::one_minute_and_one_second(Time::new(Tai, SECONDS_PER_MINUTE + 1, Subsecond::default()), 1)]
905 #[case::one_femtosecond_less_than_the_epoch(Time::new(Tai, -1, MAX_FEMTOSECONDS), 59)]
906 #[case::one_second_less_than_the_epoch(Time::new(Tai, -1, Subsecond::default()), 59)]
907 #[case::one_second_and_one_femtosecond_less_than_the_epoch(Time::new(Tai, -2, MAX_FEMTOSECONDS), 58)]
908 #[case::one_minute_less_than_the_epoch(Time::new(Tai, -SECONDS_PER_MINUTE, Subsecond::default()), 0)]
909 fn test_time_civil_time_second(#[case] time: Time<Tai>, #[case] expected: u8) {
910 let actual = time.second();
911 assert_eq!(expected, actual);
912 }
913
914 const POSITIVE_BASE_TIME_SUBSECONDS_FIXTURE: Time<Tai> = Time::new(
915 Tai,
916 0,
917 Subsecond::new()
918 .set_milliseconds(123)
919 .set_microseconds(456)
920 .set_nanoseconds(789)
921 .set_picoseconds(12)
922 .set_femtoseconds(345),
923 );
924
925 const NEGATIVE_BASE_TIME_SUBSECONDS_FIXTURE: Time<Tai> = Time::new(
926 Tai,
927 -1,
928 Subsecond::new()
929 .set_milliseconds(123)
930 .set_microseconds(456)
931 .set_nanoseconds(789)
932 .set_picoseconds(12)
933 .set_femtoseconds(345),
934 );
935
936 #[rstest]
937 #[case::positive_time_millisecond(
938 POSITIVE_BASE_TIME_SUBSECONDS_FIXTURE,
939 CivilTime::millisecond,
940 123
941 )]
942 #[case::positive_time_microsecond(
943 POSITIVE_BASE_TIME_SUBSECONDS_FIXTURE,
944 CivilTime::microsecond,
945 456
946 )]
947 #[case::positive_time_nanosecond(
948 POSITIVE_BASE_TIME_SUBSECONDS_FIXTURE,
949 CivilTime::nanosecond,
950 789
951 )]
952 #[case::positive_time_picosecond(
953 POSITIVE_BASE_TIME_SUBSECONDS_FIXTURE,
954 CivilTime::picosecond,
955 12
956 )]
957 #[case::positive_time_femtosecond(
958 POSITIVE_BASE_TIME_SUBSECONDS_FIXTURE,
959 CivilTime::femtosecond,
960 345
961 )]
962 #[case::negative_time_millisecond(
963 NEGATIVE_BASE_TIME_SUBSECONDS_FIXTURE,
964 CivilTime::millisecond,
965 123
966 )]
967 #[case::negative_time_microsecond(
968 NEGATIVE_BASE_TIME_SUBSECONDS_FIXTURE,
969 CivilTime::microsecond,
970 456
971 )]
972 #[case::negative_time_nanosecond(
973 NEGATIVE_BASE_TIME_SUBSECONDS_FIXTURE,
974 CivilTime::nanosecond,
975 789
976 )]
977 #[case::negative_time_picosecond(
978 NEGATIVE_BASE_TIME_SUBSECONDS_FIXTURE,
979 CivilTime::picosecond,
980 12
981 )]
982 #[case::negative_time_femtosecond(
983 NEGATIVE_BASE_TIME_SUBSECONDS_FIXTURE,
984 CivilTime::femtosecond,
985 345
986 )]
987 fn test_time_subseconds(
988 #[case] time: Time<Tai>,
989 #[case] f: fn(&Time<Tai>) -> u32,
990 #[case] expected: u32,
991 ) {
992 let actual = f(&time);
993 assert_eq!(expected, actual);
994 }
995
996 #[rstest]
997 #[case::zero_delta(Time::default(), TimeDelta::default(), Time::default())]
998 #[case::pos_delta_no_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(300)), TimeDelta::from_seconds_and_subsecond(1, Subsecond::new().set_milliseconds(600)), Time::new(Tai, 2, Subsecond::new().set_milliseconds(900)))]
999 #[case::pos_delta_with_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(300)), TimeDelta::from_seconds_and_subsecond(1, Subsecond::new().set_milliseconds(900)), Time::new(Tai, 3, Subsecond::new().set_milliseconds(200)))]
1000 #[case::neg_delta_no_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(600)), TimeDelta::from_seconds_and_subsecond(-2, Subsecond::new().set_milliseconds(700)), Time::new(Tai, 0, Subsecond::new().set_milliseconds(300)))]
1001 #[case::neg_delta_with_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(600)), TimeDelta::from_seconds_and_subsecond(-2, Subsecond::new().set_milliseconds(300)), Time::new(Tai, -1, Subsecond::new().set_milliseconds(900)))]
1002 fn test_time_add_time_delta(
1003 #[case] time: Time<Tai>,
1004 #[case] delta: TimeDelta,
1005 #[case] expected: Time<Tai>,
1006 ) {
1007 let actual = time + delta;
1008 assert_eq!(expected, actual);
1009 }
1010
1011 #[rstest]
1012 #[case::zero_delta(Time::default(), TimeDelta::default(), Time::default())]
1013 #[case::pos_delta_no_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(900)), TimeDelta::from_seconds_and_subsecond(1, Subsecond::new().set_milliseconds(300)), Time::new(Tai, 0, Subsecond::new().set_milliseconds(600)))]
1014 #[case::pos_delta_with_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(300)), TimeDelta::from_seconds_and_subsecond(1, Subsecond::new().set_milliseconds(400)), Time::new(Tai, -1, Subsecond::new().set_milliseconds(900)))]
1015 #[case::neg_delta_no_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(600)), TimeDelta::from_seconds_and_subsecond(-1, Subsecond::new().set_milliseconds(700)), Time::new(Tai, 1, Subsecond::new().set_milliseconds(900)))]
1016 #[case::neg_delta_with_carry(Time::new(Tai, 1, Subsecond::new().set_milliseconds(900)), TimeDelta::from_seconds_and_subsecond(-1, Subsecond::new().set_milliseconds(300)), Time::new(Tai, 2, Subsecond::new().set_milliseconds(600)))]
1017 fn test_time_sub_time_delta(
1018 #[case] time: Time<Tai>,
1019 #[case] delta: TimeDelta,
1020 #[case] expected: Time<Tai>,
1021 ) {
1022 let actual = time - delta;
1023 assert_eq!(expected, actual);
1024 }
1025
1026 #[rstest]
1027 #[case(Time::default(), Time::default())]
1028 #[case(Time::default(), Time::new(Tai, 1, Subsecond::new().set_milliseconds(900)))]
1029 #[case(
1030 Time::new(Tai, 0, Subsecond::new().set_milliseconds(900)),
1031 Time::new(Tai, 1, Subsecond::new().set_milliseconds(600))
1032 )]
1033 #[case(Time::new(Tai, 1, Subsecond::new().set_milliseconds(900)), Time::default())]
1034 #[case(
1035 Time::new(Tai, 1, Subsecond::new().set_milliseconds(600)),
1036 Time::new(Tai, 0, Subsecond::new().set_milliseconds(900))
1037 )]
1038 #[case(Time::new(Tai, 1, Subsecond::new().set_milliseconds(600)), Time::new(Tai, -1, Subsecond::new().set_milliseconds(900)), )]
1039 #[case(Time::new(Tai, -1, Subsecond::new().set_milliseconds(900)), Time::new(Tai, 1, Subsecond::new().set_milliseconds(600)), )]
1040 #[case(Time::new(Tai, 1, Subsecond::new().set_milliseconds(900)), Time::new(Tai, -1, Subsecond::new().set_milliseconds(600)), )]
1041 #[case(Time::new(Tai, -1, Subsecond::new().set_milliseconds(600)), Time::new(Tai, 1, Subsecond::new().set_milliseconds(900)), )]
1042 fn test_time_sub_time(#[case] time1: Time<Tai>, #[case] time2: Time<Tai>) {
1043 let delta = time2 - time1;
1044 let actual = time1 + delta;
1045 assert_eq!(actual, time2);
1046 }
1047
1048 #[rstest]
1049 #[case::at_the_epoch(Time::default(), 0.0)]
1050 #[case::exactly_one_day_after_the_epoch(
1051 Time::new(Tai, SECONDS_PER_DAY, Subsecond::default()),
1052 1.0
1053 )]
1054 #[case::exactly_one_day_before_the_epoch(
1055 Time::new(Tai, -SECONDS_PER_DAY, Subsecond::default()),
1056 -1.0
1057 )]
1058 #[case::a_partial_number_of_days_after_the_epoch(
1059 Time::new(Tai, (SECONDS_PER_DAY / 2) * 3, Subsecond::new().set_milliseconds(500)),
1060 1.5000057870370371
1061 )]
1062 fn test_time_days_since_j2000(#[case] time: Time<Tai>, #[case] expected: f64) {
1063 let actual = time.days_since_j2000();
1064 assert_approx_eq!(expected, actual, atol <= 1e-12);
1065 }
1066
1067 #[rstest]
1068 #[case::at_the_epoch(Time::default(), 0.0)]
1069 #[case::exactly_one_century_after_the_epoch(
1070 Time::new(Tai, SECONDS_PER_JULIAN_CENTURY, Subsecond::default()),
1071 1.0
1072 )]
1073 #[case::exactly_one_century_before_the_epoch(
1074 Time::new(Tai, -SECONDS_PER_JULIAN_CENTURY, Subsecond::default()),
1075 -1.0
1076 )]
1077 #[case::a_partial_number_of_centuries_after_the_epoch(
1078 Time::new(Tai, (SECONDS_PER_JULIAN_CENTURY / 2) * 3, Subsecond::new().set_milliseconds(500)),
1079 1.5000000001584404
1080 )]
1081 fn test_time_centuries_since_j2000(#[case] time: Time<Tai>, #[case] expected: f64) {
1082 let actual = time.centuries_since_j2000();
1083 assert_approx_eq!(expected, actual, atol <= 1e-12);
1084 }
1085
1086 #[rstest]
1087 #[case::j2000(Time::default(), Date::new(2000, 1, 1).unwrap())]
1088 #[case::next_day(Time::new(Tai, SECONDS_PER_DAY, Subsecond::default()), Date::new(2000, 1, 2).unwrap())]
1089 #[case::leap_year(Time::new(Tai, SECONDS_PER_DAY * 366, Subsecond::default()), Date::new(2001, 1, 1).unwrap())]
1090 #[case::non_leap_year(Time::new(Tai, SECONDS_PER_DAY * (366 + 365), Subsecond::default()), Date::new(2002, 1, 1).unwrap())]
1091 #[case::negative_time(Time::new(Tai, -SECONDS_PER_DAY, Subsecond::default()), Date::new(1999, 12, 31).unwrap())]
1092 fn test_time_calendar_date(#[case] time: Time<Tai>, #[case] expected: Date) {
1093 assert_eq!(expected, time.date());
1094 assert_eq!(expected.year(), time.year());
1095 assert_eq!(expected.month(), time.month());
1096 assert_eq!(expected.day(), time.day());
1097 }
1098
1099 #[test]
1100 fn test_time_scale() {
1101 let time: Time<Tai> = Time::default();
1102 assert_eq!(time.scale(), Tai);
1103 }
1104
1105 #[test]
1106 fn test_time_override_scale() {
1107 let time: Time<Tai> = Time::default();
1108 let time = time.with_scale(Tt);
1109 assert_eq!(time.scale(), Tt);
1110 }
1111
1112 #[test]
1113 fn test_time_leap_second_outside_utc() {
1114 let actual = time!(Tai, 2000, 1, 1, 23, 59, 60.0);
1115 let expected = Err(TimeError::LeapSecondOutsideUtc);
1116 assert_eq!(actual, expected);
1117 }
1118
1119 #[test]
1120 fn test_time_to_delta() {
1121 let time = time!(Tai, 2000, 1, 1, 12, 0, 0.0).unwrap();
1122 let actual = time.to_delta();
1123 let expected = TimeDelta::from_seconds(0);
1124 assert_eq!(actual, expected);
1125 }
1126
1127 #[test]
1128 fn test_time_into_dyn() {
1129 let time = time!(Tai, 2000, 1, 1, 12, 0, 0.0).unwrap();
1130 let dyn_time = time.into_dyn();
1131 assert_eq!(dyn_time.scale(), DynTimeScale::Tai);
1132 assert_eq!(dyn_time.to_delta(), time.to_delta());
1133
1134 let tdb_time = time!(Tdb, 2023, 6, 15, 10, 30, 0.0).unwrap();
1135 let dyn_tdb = tdb_time.into_dyn();
1136 assert_eq!(dyn_tdb.scale(), DynTimeScale::Tdb);
1137 assert_eq!(dyn_tdb.to_delta(), tdb_time.to_delta());
1138 }
1139
1140 #[test]
1141 fn test_checked_cmp_same_scale() {
1142 let t1 = time!(Tai, 2000, 1, 1, 12, 0, 0.0).unwrap().into_dyn();
1143 let t2 = time!(Tai, 2000, 1, 1, 13, 0, 0.0).unwrap().into_dyn();
1144 assert_eq!(t1.checked_cmp(&t2), Ok(Ordering::Less));
1145 assert_eq!(t2.checked_cmp(&t1), Ok(Ordering::Greater));
1146 assert_eq!(t1.checked_cmp(&t1), Ok(Ordering::Equal));
1147 }
1148
1149 #[test]
1150 fn test_checked_cmp_different_scale_returns_err() {
1151 let t_tai = time!(Tai, 2000, 1, 1, 12, 0, 0.0).unwrap().into_dyn();
1152 let t_tt = time!(Tt, 2000, 1, 1, 12, 0, 0.0).unwrap().into_dyn();
1153 assert!(t_tai.checked_cmp(&t_tt).is_err());
1154 assert!(t_tt.checked_cmp(&t_tai).is_err());
1155 }
1156
1157 #[test]
1158 #[should_panic(expected = "cannot compare `Time` objects with different time scales")]
1159 fn test_ord_different_scale_panics() {
1160 let t_tai = time!(Tai, 2000, 1, 1, 12, 0, 0.0).unwrap().into_dyn();
1161 let t_tt = time!(Tt, 2000, 1, 1, 12, 0, 0.0).unwrap().into_dyn();
1162 let _ = t_tai < t_tt;
1163 }
1164}