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