Skip to main content

lox_core/time/
deltas.rs

1// SPDX-FileCopyrightText: 2024 Angus Morrison <github@angus-morrison.com>
2// SPDX-FileCopyrightText: 2024 Helge Eichhorn <git@helgeeichhorn.de>
3//
4// SPDX-License-Identifier: MPL-2.0
5
6/*!
7    Module `deltas` contains [TimeDelta], the key primitive of the `lox-time` crate.
8
9    [TimeDelta] is a signed, unscaled delta relative to an arbitrary epoch. This forms the basis
10    of instants in all continuous time scales.
11
12    The [ToDelta] trait specifies the method by which such scaled time representations should
13    expose their underlying [TimeDelta].
14*/
15
16use std::fmt::Display;
17use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
18
19use lox_test_utils::approx_eq::ApproxEq;
20
21use crate::f64;
22use crate::i64::consts::{
23    ATTOSECONDS_IN_FEMTOSECOND, ATTOSECONDS_IN_MICROSECOND, ATTOSECONDS_IN_MILLISECOND,
24    ATTOSECONDS_IN_NANOSECOND, ATTOSECONDS_IN_PICOSECOND, ATTOSECONDS_IN_SECOND,
25    SECONDS_BETWEEN_JD_AND_J2000, SECONDS_PER_DAY as I64_SECONDS_PER_DAY,
26    SECONDS_PER_HOUR as I64_SECONDS_PER_HOUR, SECONDS_PER_MINUTE as I64_SECONDS_PER_MINUTE,
27};
28use crate::types::units::Days;
29
30use super::julian_dates::{Epoch, JulianDate, Unit};
31use super::subsecond::Subsecond;
32
33/// Extension trait for creating [`TimeDelta`] values from numeric types.
34///
35/// # Examples
36///
37/// ```
38/// use lox_core::time::deltas::TimeUnits;
39///
40/// let delta = 1.5.days();
41/// let delta = 30.mins();
42/// let delta = 500.millis();
43/// ```
44pub trait TimeUnits {
45    /// Creates a [`TimeDelta`] from a value in days.
46    fn days(&self) -> TimeDelta;
47    /// Creates a [`TimeDelta`] from a value in hours.
48    fn hours(&self) -> TimeDelta;
49    /// Creates a [`TimeDelta`] from a value in minutes.
50    fn mins(&self) -> TimeDelta;
51    /// Creates a [`TimeDelta`] from a value in seconds.
52    fn secs(&self) -> TimeDelta;
53    /// Creates a [`TimeDelta`] from a value in milliseconds.
54    fn millis(&self) -> TimeDelta;
55    /// Creates a [`TimeDelta`] from a value in microseconds.
56    fn micros(&self) -> TimeDelta;
57    /// Creates a [`TimeDelta`] from a value in nanoseconds.
58    fn nanos(&self) -> TimeDelta;
59    /// Creates a [`TimeDelta`] from a value in picoseconds.
60    fn picos(&self) -> TimeDelta;
61    /// Creates a [`TimeDelta`] from a value in femtoseconds.
62    fn femtos(&self) -> TimeDelta;
63    /// Creates a [`TimeDelta`] from a value in attoseconds.
64    fn attos(&self) -> TimeDelta;
65}
66
67impl TimeUnits for f64 {
68    fn days(&self) -> TimeDelta {
69        TimeDelta::from_days_f64(*self)
70    }
71    fn hours(&self) -> TimeDelta {
72        TimeDelta::from_hours_f64(*self)
73    }
74    fn mins(&self) -> TimeDelta {
75        TimeDelta::from_minutes_f64(*self)
76    }
77    fn secs(&self) -> TimeDelta {
78        TimeDelta::from_seconds_f64(*self)
79    }
80    fn millis(&self) -> TimeDelta {
81        TimeDelta::from_seconds_f64(*self * f64::consts::SECONDS_PER_MILLISECOND)
82    }
83    fn micros(&self) -> TimeDelta {
84        TimeDelta::from_seconds_f64(*self * f64::consts::SECONDS_PER_MICROSECOND)
85    }
86    fn nanos(&self) -> TimeDelta {
87        TimeDelta::from_seconds_f64(*self * f64::consts::SECONDS_PER_NANOSECOND)
88    }
89    fn picos(&self) -> TimeDelta {
90        TimeDelta::from_seconds_f64(*self * f64::consts::SECONDS_PER_PICOSECOND)
91    }
92    fn femtos(&self) -> TimeDelta {
93        TimeDelta::from_seconds_f64(*self * f64::consts::SECONDS_PER_FEMTOSECOND)
94    }
95    fn attos(&self) -> TimeDelta {
96        TimeDelta::from_seconds_f64(*self * f64::consts::SECONDS_PER_ATTOSECOND)
97    }
98}
99
100impl TimeUnits for i64 {
101    fn days(&self) -> TimeDelta {
102        TimeDelta::from_days(*self)
103    }
104    fn hours(&self) -> TimeDelta {
105        TimeDelta::from_hours(*self)
106    }
107    fn mins(&self) -> TimeDelta {
108        TimeDelta::from_minutes(*self)
109    }
110    fn secs(&self) -> TimeDelta {
111        TimeDelta::from_seconds(*self)
112    }
113    fn millis(&self) -> TimeDelta {
114        TimeDelta::from_milliseconds(*self)
115    }
116    fn micros(&self) -> TimeDelta {
117        TimeDelta::from_microseconds(*self)
118    }
119    fn nanos(&self) -> TimeDelta {
120        TimeDelta::from_nanoseconds(*self)
121    }
122    fn picos(&self) -> TimeDelta {
123        TimeDelta::from_picoseconds(*self)
124    }
125    fn femtos(&self) -> TimeDelta {
126        TimeDelta::from_femtoseconds(*self)
127    }
128    fn attos(&self) -> TimeDelta {
129        TimeDelta::from_attoseconds(*self)
130    }
131}
132
133/// A unifying trait for types that can be converted into a [TimeDelta].
134pub trait ToDelta {
135    /// Transforms the value into a [TimeDelta].
136    fn to_delta(&self) -> TimeDelta;
137}
138
139/// A high-precision representation of a duration in seconds.
140///
141/// Uses a two-part floating point representation where the value is `hi + lo`.
142/// The `lo` component is a correction term that captures precision lost in the
143/// `hi` component. This allows representing values with roughly double the
144/// precision of a single f64.
145///
146/// This is used to preserve precision when converting [`TimeDelta`] to floating
147/// point, especially for large second values combined with small subsecond values.
148#[derive(Copy, Clone, Debug, Default, PartialEq)]
149pub struct Seconds {
150    /// The high-order component (primary value in seconds)
151    pub hi: f64,
152    /// The low-order component (correction term in seconds)
153    pub lo: f64,
154}
155
156impl Seconds {
157    /// Creates a new Seconds from high and low components.
158    pub const fn new(hi: f64, lo: f64) -> Self {
159        Self { hi, lo }
160    }
161
162    /// Creates a Seconds from a single f64 value.
163    pub const fn from_f64(value: f64) -> Self {
164        Self { hi: value, lo: 0.0 }
165    }
166
167    /// Converts to a single f64 (lossy for large values with small corrections).
168    pub const fn to_f64(self) -> f64 {
169        self.hi + self.lo
170    }
171
172    /// Returns true if either component is NaN.
173    pub const fn is_nan(self) -> bool {
174        self.hi.is_nan() || self.lo.is_nan()
175    }
176
177    /// Returns true if either component is infinite.
178    pub const fn is_infinite(self) -> bool {
179        self.hi.is_infinite() || self.lo.is_infinite()
180    }
181
182    /// Adds two Seconds values with error compensation (Knuth's algorithm).
183    fn compensated_add(self, other: Self) -> Self {
184        let (s, e) = two_sum(self.hi, other.hi);
185        let e = e + self.lo + other.lo;
186        let (hi, lo) = two_sum(s, e);
187        Self { hi, lo }
188    }
189
190    /// Subtracts another Seconds from this one.
191    fn compensated_sub(self, other: Self) -> Self {
192        self.compensated_add(other.neg())
193    }
194
195    /// Negates this Seconds.
196    pub const fn neg(self) -> Self {
197        Self {
198            hi: -self.hi,
199            lo: -self.lo,
200        }
201    }
202
203    /// Multiplies this Seconds by an f64 scalar.
204    pub fn mul_f64(self, rhs: f64) -> Self {
205        let (p, e) = two_prod(self.hi, rhs);
206        let e = e + self.lo * rhs;
207        let (hi, lo) = two_sum(p, e);
208        Self { hi, lo }
209    }
210
211    /// Multiplies two Seconds values.
212    fn compensated_mul(self, other: Self) -> Self {
213        let (p, e) = two_prod(self.hi, other.hi);
214        let e = e + self.hi * other.lo + self.lo * other.hi;
215        let (hi, lo) = two_sum(p, e);
216        Self { hi, lo }
217    }
218}
219
220impl Add for Seconds {
221    type Output = Self;
222    fn add(self, rhs: Self) -> Self::Output {
223        self.compensated_add(rhs)
224    }
225}
226
227impl Sub for Seconds {
228    type Output = Self;
229    fn sub(self, rhs: Self) -> Self::Output {
230        self.compensated_sub(rhs)
231    }
232}
233
234impl Neg for Seconds {
235    type Output = Self;
236    fn neg(self) -> Self::Output {
237        Seconds::neg(self)
238    }
239}
240
241impl Mul<f64> for Seconds {
242    type Output = Self;
243    fn mul(self, rhs: f64) -> Self::Output {
244        self.mul_f64(rhs)
245    }
246}
247
248impl Mul for Seconds {
249    type Output = Self;
250    fn mul(self, rhs: Self) -> Self::Output {
251        self.compensated_mul(rhs)
252    }
253}
254
255impl Mul<Seconds> for f64 {
256    type Output = Seconds;
257    fn mul(self, rhs: Seconds) -> Self::Output {
258        rhs.mul_f64(self)
259    }
260}
261
262impl From<f64> for Seconds {
263    fn from(value: f64) -> Self {
264        Self::from_f64(value)
265    }
266}
267
268/// Knuth's two-sum algorithm for error-free addition.
269///
270/// Returns (sum, error) where sum + error = a + b exactly.
271#[inline]
272fn two_sum(a: f64, b: f64) -> (f64, f64) {
273    let s = a + b;
274    let v = s - a;
275    let e = (a - (s - v)) + (b - v);
276    (s, e)
277}
278
279/// Two-product algorithm using FMA for error-free multiplication.
280///
281/// Returns (product, error) where product + error = a * b exactly.
282#[inline]
283fn two_prod(a: f64, b: f64) -> (f64, f64) {
284    let p = a * b;
285    let e = a.mul_add(b, -p); // FMA: a * b - p
286    (p, e)
287}
288
289/// A signed time delta with attosecond precision.
290///
291/// `TimeDelta` represents a duration as whole seconds plus a fractional attosecond
292/// component. It also supports sentinel values for NaN and positive/negative infinity.
293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
294#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
295pub enum TimeDelta {
296    /// A finite time delta with whole seconds and an attosecond remainder in `[0, 10¹⁸)`.
297    Valid {
298        /// Whole seconds component.
299        seconds: i64,
300        /// Attosecond remainder in `[0, 10¹⁸)`.
301        attoseconds: i64,
302    },
303    /// Not-a-number sentinel.
304    NaN,
305    /// Positive infinity sentinel.
306    PosInf,
307    /// Negative infinity sentinel.
308    NegInf,
309}
310
311impl TimeDelta {
312    /// A zero-length time delta.
313    pub const ZERO: Self = TimeDelta::from_seconds(0);
314
315    /// Creates a new `TimeDelta` from seconds and attoseconds.
316    ///
317    /// The attoseconds value is automatically normalized to [0, 10¹⁸), with
318    /// overflow/underflow carried into the seconds component.
319    ///
320    /// # Examples
321    ///
322    /// ```
323    /// use lox_core::time::deltas::TimeDelta;
324    ///
325    /// let dt = TimeDelta::new(1, 500_000_000_000_000_000);
326    /// assert_eq!(dt.seconds(), Some(1));
327    /// assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
328    /// ```
329    pub const fn new(seconds: i64, attoseconds: i64) -> Self {
330        let (seconds, attoseconds) = Self::normalize(seconds, attoseconds);
331        Self::Valid {
332            seconds,
333            attoseconds,
334        }
335    }
336
337    /// Normalizes attoseconds to [0, ATTOSECONDS_IN_SECOND) range.
338    const fn normalize(mut seconds: i64, mut attoseconds: i64) -> (i64, i64) {
339        // Handle overflow: attoseconds >= ATTOSECONDS_IN_SECOND
340        if attoseconds >= ATTOSECONDS_IN_SECOND {
341            let carry = attoseconds / ATTOSECONDS_IN_SECOND;
342            seconds += carry;
343            attoseconds %= ATTOSECONDS_IN_SECOND;
344        }
345
346        // Handle underflow: attoseconds < 0
347        if attoseconds < 0 {
348            // Calculate how many seconds to borrow
349            let borrow = (-attoseconds + ATTOSECONDS_IN_SECOND - 1) / ATTOSECONDS_IN_SECOND;
350            seconds -= borrow;
351            attoseconds += borrow * ATTOSECONDS_IN_SECOND;
352        }
353
354        (seconds, attoseconds)
355    }
356
357    /// Returns a [`TimeDeltaBuilder`] for constructing a `TimeDelta` from individual components.
358    pub const fn builder() -> TimeDeltaBuilder {
359        TimeDeltaBuilder::new()
360    }
361
362    /// Creates a `TimeDelta` from a floating-point number of seconds.
363    ///
364    /// Returns `NaN`, `PosInf`, or `NegInf` for the corresponding special float values.
365    pub const fn from_seconds_f64(value: f64) -> Self {
366        if value.is_nan() {
367            return TimeDelta::NaN;
368        }
369        if value < i64::MIN as f64 {
370            return TimeDelta::NegInf;
371        }
372        if value > i64::MAX as f64 {
373            return TimeDelta::PosInf;
374        }
375        let seconds = value.round_ties_even();
376        let subseconds = value - seconds;
377        if subseconds.is_sign_negative() {
378            let seconds = seconds as i64 - 1;
379            let attoseconds =
380                (subseconds * ATTOSECONDS_IN_SECOND as f64).round() as i64 + ATTOSECONDS_IN_SECOND;
381            TimeDelta::Valid {
382                seconds,
383                attoseconds,
384            }
385        } else {
386            let seconds = seconds as i64;
387            let attoseconds = (subseconds * ATTOSECONDS_IN_SECOND as f64).round() as i64;
388            TimeDelta::Valid {
389                seconds,
390                attoseconds,
391            }
392        }
393    }
394
395    /// Creates a `TimeDelta` from a whole number of seconds.
396    pub const fn from_seconds(seconds: i64) -> Self {
397        Self::new(seconds, 0)
398    }
399
400    /// Creates a `TimeDelta` from a whole number of minutes.
401    pub const fn from_minutes(minutes: i64) -> Self {
402        Self::from_seconds(minutes * I64_SECONDS_PER_MINUTE)
403    }
404
405    /// Creates a `TimeDelta` from a floating-point number of minutes.
406    pub const fn from_minutes_f64(value: f64) -> Self {
407        Self::from_seconds_f64(value * f64::consts::SECONDS_PER_MINUTE)
408    }
409
410    /// Creates a `TimeDelta` from a whole number of hours.
411    pub const fn from_hours(hours: i64) -> Self {
412        Self::from_seconds(hours * I64_SECONDS_PER_HOUR)
413    }
414
415    /// Creates a `TimeDelta` from a floating-point number of hours.
416    pub const fn from_hours_f64(value: f64) -> Self {
417        Self::from_seconds_f64(value * f64::consts::SECONDS_PER_HOUR)
418    }
419
420    /// Creates a `TimeDelta` from a whole number of days.
421    pub const fn from_days(days: i64) -> Self {
422        Self::from_seconds(days * I64_SECONDS_PER_DAY)
423    }
424
425    /// Creates a `TimeDelta` from a floating-point number of days.
426    pub const fn from_days_f64(value: f64) -> Self {
427        Self::from_seconds_f64(value * f64::consts::SECONDS_PER_DAY)
428    }
429
430    /// Creates a `TimeDelta` from a number of milliseconds.
431    pub const fn from_milliseconds(ms: i64) -> Self {
432        let seconds = ms / 1000;
433        let remainder = ms % 1000;
434        Self::new(seconds, remainder * ATTOSECONDS_IN_MILLISECOND)
435    }
436
437    /// Creates a `TimeDelta` from a number of microseconds.
438    pub const fn from_microseconds(us: i64) -> Self {
439        let seconds = us / 1_000_000;
440        let remainder = us % 1_000_000;
441        Self::new(seconds, remainder * ATTOSECONDS_IN_MICROSECOND)
442    }
443
444    /// Creates a `TimeDelta` from a number of nanoseconds.
445    pub const fn from_nanoseconds(ns: i64) -> Self {
446        let seconds = ns / 1_000_000_000;
447        let remainder = ns % 1_000_000_000;
448        Self::new(seconds, remainder * ATTOSECONDS_IN_NANOSECOND)
449    }
450
451    /// Creates a `TimeDelta` from a number of picoseconds.
452    pub const fn from_picoseconds(ps: i64) -> Self {
453        let seconds = ps / 1_000_000_000_000;
454        let remainder = ps % 1_000_000_000_000;
455        Self::new(seconds, remainder * ATTOSECONDS_IN_PICOSECOND)
456    }
457
458    /// Creates a `TimeDelta` from a number of femtoseconds.
459    pub const fn from_femtoseconds(fs: i64) -> Self {
460        let seconds = fs / 1_000_000_000_000_000;
461        let remainder = fs % 1_000_000_000_000_000;
462        Self::new(seconds, remainder * ATTOSECONDS_IN_FEMTOSECOND)
463    }
464
465    /// Creates a `TimeDelta` from a number of attoseconds.
466    pub const fn from_attoseconds(atto: i64) -> Self {
467        let seconds = atto / ATTOSECONDS_IN_SECOND;
468        let remainder = atto % ATTOSECONDS_IN_SECOND;
469        Self::new(seconds, remainder)
470    }
471
472    /// Creates a `TimeDelta` from a floating-point number of Julian years (365.25 days each).
473    pub const fn from_julian_years(value: f64) -> Self {
474        Self::from_seconds_f64(value * f64::consts::SECONDS_PER_JULIAN_YEAR)
475    }
476
477    /// Creates a `TimeDelta` from a floating-point number of Julian centuries (36525 days each).
478    pub const fn from_julian_centuries(value: f64) -> Self {
479        Self::from_seconds_f64(value * f64::consts::SECONDS_PER_JULIAN_CENTURY)
480    }
481
482    /// Creates a `TimeDelta` from whole seconds and a [`Subsecond`] fractional part.
483    pub const fn from_seconds_and_subsecond(seconds: i64, subsecond: Subsecond) -> Self {
484        Self::new(seconds, subsecond.as_attoseconds())
485    }
486
487    /// Creates a `TimeDelta` from floating-point seconds and a subsecond fraction.
488    pub const fn from_seconds_and_subsecond_f64(seconds: f64, subsecond: f64) -> Self {
489        Self::from_seconds_f64(subsecond).add_const(Self::from_seconds_f64(seconds))
490    }
491
492    /// Creates a `TimeDelta` from a Julian date relative to the given epoch.
493    pub const fn from_julian_date(julian_date: Days, epoch: Epoch) -> Self {
494        let seconds = julian_date * f64::consts::SECONDS_PER_DAY;
495        let seconds = match epoch {
496            Epoch::JulianDate => seconds - f64::consts::SECONDS_BETWEEN_JD_AND_J2000,
497            Epoch::ModifiedJulianDate => seconds - f64::consts::SECONDS_BETWEEN_MJD_AND_J2000,
498            Epoch::J1950 => seconds - f64::consts::SECONDS_BETWEEN_J1950_AND_J2000,
499            Epoch::J2000 => seconds,
500        };
501        Self::from_seconds_f64(seconds)
502    }
503
504    /// Creates a `TimeDelta` from a two-part Julian date (`jd1 + jd2`).
505    pub const fn from_two_part_julian_date(jd1: Days, jd2: Days) -> Self {
506        TimeDelta::from_seconds_f64(jd1 * f64::consts::SECONDS_PER_DAY)
507            .add_const(TimeDelta::from_seconds_f64(
508                jd2 * f64::consts::SECONDS_PER_DAY,
509            ))
510            .sub_const(TimeDelta::from_seconds(SECONDS_BETWEEN_JD_AND_J2000))
511    }
512
513    /// Returns the whole seconds and [`Subsecond`] components, or `None` for non-finite values.
514    pub const fn as_seconds_and_subsecond(&self) -> Option<(i64, Subsecond)> {
515        match self {
516            TimeDelta::Valid {
517                seconds,
518                attoseconds,
519            } => Some((*seconds, Subsecond::from_attoseconds(*attoseconds))),
520            _ => None,
521        }
522    }
523
524    /// Returns the time delta as a high-precision [`Seconds`] representation.
525    ///
526    /// The result is a [`Seconds`] where `hi` contains the whole seconds and `lo`
527    /// contains the subsecond fraction. This preserves full precision even
528    /// for large time values.
529    ///
530    /// For a lossy single f64, use `.to_seconds().to_f64()`.
531    pub const fn to_seconds(&self) -> Seconds {
532        let (seconds, attoseconds) = match self {
533            TimeDelta::Valid {
534                seconds,
535                attoseconds,
536            } => (*seconds, *attoseconds),
537            TimeDelta::NaN => return Seconds::new(f64::NAN, f64::NAN),
538            TimeDelta::PosInf => return Seconds::new(f64::INFINITY, 0.0),
539            TimeDelta::NegInf => return Seconds::new(f64::NEG_INFINITY, 0.0),
540        };
541        Seconds::new(
542            seconds as f64,
543            attoseconds as f64 / ATTOSECONDS_IN_SECOND as f64,
544        )
545    }
546
547    /// Returns `true` if the time delta is negative.
548    pub const fn is_negative(&self) -> bool {
549        match self {
550            TimeDelta::Valid { seconds, .. } => *seconds < 0,
551            TimeDelta::NegInf => true,
552            _ => false,
553        }
554    }
555
556    /// Returns `true` if the time delta is exactly zero.
557    pub const fn is_zero(&self) -> bool {
558        match &self {
559            TimeDelta::Valid {
560                seconds,
561                attoseconds,
562            } => *seconds == 0 && *attoseconds == 0,
563            _ => false,
564        }
565    }
566
567    /// Returns `true` if the time delta is positive.
568    pub const fn is_positive(&self) -> bool {
569        match self {
570            TimeDelta::Valid {
571                seconds,
572                attoseconds,
573            } => *seconds > 0 || *seconds == 0 && *attoseconds > 0,
574            TimeDelta::PosInf => true,
575            _ => false,
576        }
577    }
578
579    /// Returns `true` if the time delta is a finite value (not NaN or infinite).
580    pub const fn is_finite(&self) -> bool {
581        matches!(self, Self::Valid { .. })
582    }
583
584    /// Returns `true` if the time delta is NaN.
585    pub const fn is_nan(&self) -> bool {
586        matches!(self, Self::NaN)
587    }
588
589    /// Returns `true` if the time delta is positive or negative infinity.
590    pub const fn is_infinite(&self) -> bool {
591        matches!(self, Self::PosInf | Self::NegInf)
592    }
593
594    /// Returns the whole seconds component, or `None` for non-finite values.
595    pub const fn seconds(&self) -> Option<i64> {
596        match self {
597            Self::Valid { seconds, .. } => Some(*seconds),
598            _ => None,
599        }
600    }
601
602    /// Returns the subsecond fraction as an `f64`, or `None` for non-finite values.
603    pub const fn subsecond(&self) -> Option<f64> {
604        match self.as_seconds_and_subsecond() {
605            Some((_, subsecond)) => Some(subsecond.as_seconds_f64()),
606            None => None,
607        }
608    }
609
610    /// Returns the attosecond component, or `None` for non-finite values.
611    pub const fn attoseconds(&self) -> Option<i64> {
612        match self {
613            Self::Valid { attoseconds, .. } => Some(*attoseconds),
614            _ => None,
615        }
616    }
617
618    const fn neg_const(self) -> Self {
619        let (seconds, attoseconds) = match self {
620            TimeDelta::Valid {
621                seconds,
622                attoseconds,
623            } => (seconds, attoseconds),
624            TimeDelta::NaN => return Self::NaN,
625            TimeDelta::PosInf => return Self::NegInf,
626            TimeDelta::NegInf => return Self::PosInf,
627        };
628        if attoseconds == 0 {
629            return Self::Valid {
630                seconds: -seconds,
631                attoseconds,
632            };
633        }
634
635        Self::Valid {
636            seconds: -seconds - 1,
637            attoseconds: ATTOSECONDS_IN_SECOND - attoseconds,
638        }
639    }
640
641    /// Adds two `TimeDelta` values in a `const` context.
642    pub const fn add_const(self, rhs: Self) -> Self {
643        let (secs_lhs, attos_lhs, secs_rhs, attos_rhs) = match (self, rhs) {
644            (
645                TimeDelta::Valid {
646                    seconds: secs_lhs,
647                    attoseconds: attos_lhs,
648                },
649                TimeDelta::Valid {
650                    seconds: secs_rhs,
651                    attoseconds: attos_rhs,
652                },
653            ) => (secs_lhs, attos_lhs, secs_rhs, attos_rhs),
654            (TimeDelta::PosInf, TimeDelta::Valid { .. })
655            | (TimeDelta::Valid { .. }, TimeDelta::PosInf)
656            | (TimeDelta::PosInf, TimeDelta::PosInf) => return TimeDelta::PosInf,
657            (TimeDelta::NegInf, TimeDelta::Valid { .. })
658            | (TimeDelta::Valid { .. }, TimeDelta::NegInf)
659            | (TimeDelta::NegInf, TimeDelta::NegInf) => return TimeDelta::NegInf,
660            (TimeDelta::PosInf, TimeDelta::NegInf) | (TimeDelta::NegInf, TimeDelta::PosInf) => {
661                return TimeDelta::NaN;
662            }
663            (_, TimeDelta::NaN) | (TimeDelta::NaN, _) => return TimeDelta::NaN,
664        };
665
666        let seconds = secs_lhs + secs_rhs;
667        let attoseconds = attos_lhs + attos_rhs;
668        Self::new(seconds, attoseconds)
669    }
670
671    /// Subtracts `rhs` from `self` in a `const` context.
672    pub const fn sub_const(self, rhs: Self) -> Self {
673        let (secs_lhs, attos_lhs, secs_rhs, attos_rhs) = match (self, rhs) {
674            (
675                TimeDelta::Valid {
676                    seconds: secs_lhs,
677                    attoseconds: attos_lhs,
678                },
679                TimeDelta::Valid {
680                    seconds: secs_rhs,
681                    attoseconds: attos_rhs,
682                },
683            ) => (secs_lhs, attos_lhs, secs_rhs, attos_rhs),
684            (TimeDelta::PosInf, TimeDelta::Valid { .. }) => return TimeDelta::PosInf,
685            (TimeDelta::Valid { .. }, TimeDelta::PosInf) => return TimeDelta::NegInf,
686            (TimeDelta::NegInf, TimeDelta::Valid { .. }) => return TimeDelta::NegInf,
687            (TimeDelta::Valid { .. }, TimeDelta::NegInf) => return TimeDelta::PosInf,
688            (TimeDelta::PosInf, TimeDelta::PosInf) | (TimeDelta::NegInf, TimeDelta::NegInf) => {
689                return TimeDelta::NaN;
690            }
691            (TimeDelta::PosInf, TimeDelta::NegInf) => return TimeDelta::PosInf,
692            (TimeDelta::NegInf, TimeDelta::PosInf) => return TimeDelta::NegInf,
693            (_, TimeDelta::NaN) | (TimeDelta::NaN, _) => return TimeDelta::NaN,
694        };
695
696        let seconds = secs_lhs - secs_rhs;
697        let attoseconds = attos_lhs - attos_rhs;
698        Self::new(seconds, attoseconds)
699    }
700
701    /// Multiplies the time delta by an `f64` scalar in a `const` context.
702    pub const fn mul_const(self, rhs: f64) -> Self {
703        let (seconds, attoseconds) = match self {
704            TimeDelta::Valid {
705                seconds,
706                attoseconds,
707            } => (seconds, attoseconds),
708            TimeDelta::NaN => return TimeDelta::NaN,
709            TimeDelta::PosInf => {
710                return if rhs.is_nan() {
711                    TimeDelta::NaN
712                } else if rhs > 0.0 {
713                    TimeDelta::PosInf
714                } else if rhs < 0.0 {
715                    TimeDelta::NegInf
716                } else {
717                    TimeDelta::NaN
718                };
719            }
720            TimeDelta::NegInf => {
721                return if rhs.is_nan() {
722                    TimeDelta::NaN
723                } else if rhs > 0.0 {
724                    TimeDelta::NegInf
725                } else if rhs < 0.0 {
726                    TimeDelta::PosInf
727                } else {
728                    TimeDelta::NaN
729                };
730            }
731        };
732
733        if rhs.is_nan() {
734            return TimeDelta::NaN;
735        }
736        if !rhs.is_finite() {
737            return if rhs.is_sign_positive() {
738                TimeDelta::PosInf
739            } else {
740                TimeDelta::NegInf
741            };
742        }
743
744        // Multiply seconds component
745        let seconds_product = rhs * seconds as f64;
746
747        // Multiply attoseconds component (keeping high precision)
748        // attoseconds * factor / ATTOSECONDS_IN_SECOND
749        let attoseconds_product = rhs * attoseconds as f64 / ATTOSECONDS_IN_SECOND as f64;
750
751        // Combine results
752        TimeDelta::from_seconds_f64(attoseconds_product + seconds_product)
753    }
754}
755
756impl Default for TimeDelta {
757    fn default() -> Self {
758        Self::new(0, 0)
759    }
760}
761
762impl Ord for TimeDelta {
763    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
764        use std::cmp::Ordering;
765
766        match (self, other) {
767            // NaN is incomparable, but we need total ordering for Ord
768            // Define NaN as less than everything else
769            (TimeDelta::NaN, TimeDelta::NaN) => Ordering::Equal,
770            (TimeDelta::NaN, _) => Ordering::Less,
771            (_, TimeDelta::NaN) => Ordering::Greater,
772
773            // Infinities
774            (TimeDelta::NegInf, TimeDelta::NegInf) => Ordering::Equal,
775            (TimeDelta::NegInf, _) => Ordering::Less,
776            (_, TimeDelta::NegInf) => Ordering::Greater,
777
778            (TimeDelta::PosInf, TimeDelta::PosInf) => Ordering::Equal,
779            (TimeDelta::PosInf, _) => Ordering::Greater,
780            (_, TimeDelta::PosInf) => Ordering::Less,
781
782            // Both Valid: compare (seconds, attoseconds) tuples
783            (
784                TimeDelta::Valid {
785                    seconds: s1,
786                    attoseconds: a1,
787                },
788                TimeDelta::Valid {
789                    seconds: s2,
790                    attoseconds: a2,
791                },
792            ) => s1.cmp(s2).then_with(|| a1.cmp(a2)),
793        }
794    }
795}
796
797impl PartialOrd for TimeDelta {
798    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
799        Some(self.cmp(other))
800    }
801}
802
803impl From<f64> for TimeDelta {
804    fn from(value: f64) -> Self {
805        Self::from_seconds_f64(value)
806    }
807}
808
809impl Display for TimeDelta {
810    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
811        write!(f, "{} s", self.to_seconds().to_f64())
812    }
813}
814
815impl JulianDate for TimeDelta {
816    fn julian_date(&self, epoch: Epoch, unit: Unit) -> f64 {
817        let tf = self.to_seconds();
818        let epoch_offset = match epoch {
819            Epoch::JulianDate => f64::consts::SECONDS_BETWEEN_JD_AND_J2000,
820            Epoch::ModifiedJulianDate => f64::consts::SECONDS_BETWEEN_MJD_AND_J2000,
821            Epoch::J1950 => f64::consts::SECONDS_BETWEEN_J1950_AND_J2000,
822            Epoch::J2000 => 0.0,
823        };
824        let adjusted = tf + Seconds::from_f64(epoch_offset);
825        let seconds = adjusted.to_f64();
826        match unit {
827            Unit::Seconds => seconds,
828            Unit::Days => seconds / f64::consts::SECONDS_PER_DAY,
829            Unit::Years => seconds / f64::consts::SECONDS_PER_JULIAN_YEAR,
830            Unit::Centuries => seconds / f64::consts::SECONDS_PER_JULIAN_CENTURY,
831        }
832    }
833}
834
835impl Neg for TimeDelta {
836    type Output = Self;
837
838    fn neg(self) -> Self::Output {
839        self.neg_const()
840    }
841}
842
843impl Add for TimeDelta {
844    type Output = Self;
845
846    fn add(self, rhs: Self) -> Self::Output {
847        self.add_const(rhs)
848    }
849}
850
851impl AddAssign for TimeDelta {
852    fn add_assign(&mut self, rhs: Self) {
853        *self = *self + rhs
854    }
855}
856
857impl Sub for TimeDelta {
858    type Output = Self;
859
860    fn sub(self, rhs: Self) -> Self::Output {
861        self.sub_const(rhs)
862    }
863}
864
865impl SubAssign for TimeDelta {
866    fn sub_assign(&mut self, rhs: Self) {
867        *self = *self - rhs
868    }
869}
870
871impl Mul<TimeDelta> for f64 {
872    type Output = TimeDelta;
873
874    fn mul(self, rhs: TimeDelta) -> Self::Output {
875        rhs.mul_const(self)
876    }
877}
878
879impl From<i64> for TimeDelta {
880    fn from(value: i64) -> Self {
881        TimeDelta::from_seconds(value)
882    }
883}
884
885impl From<i32> for TimeDelta {
886    fn from(value: i32) -> Self {
887        TimeDelta::from_seconds(value as i64)
888    }
889}
890
891impl ApproxEq for TimeDelta {
892    fn approx_eq(
893        &self,
894        rhs: &Self,
895        atol: f64,
896        rtol: f64,
897    ) -> lox_test_utils::approx_eq::ApproxEqResults {
898        self.to_seconds()
899            .to_f64()
900            .approx_eq(&rhs.to_seconds().to_f64(), atol, rtol)
901    }
902}
903
904/// A builder for constructing [`TimeDelta`] values from individual time components.
905#[derive(Copy, Clone, Debug, Default)]
906pub struct TimeDeltaBuilder {
907    seconds: i64,
908    subsecond: Subsecond,
909    negative: bool,
910}
911
912impl TimeDeltaBuilder {
913    /// Creates a new builder with all components set to zero.
914    pub const fn new() -> Self {
915        Self {
916            seconds: 0,
917            subsecond: Subsecond::new(),
918            negative: false,
919        }
920    }
921
922    /// Sets the whole seconds component.
923    pub const fn seconds(mut self, seconds: i64) -> Self {
924        self.seconds = seconds;
925        self
926    }
927
928    /// Marks the resulting `TimeDelta` as negative.
929    pub const fn negative(mut self) -> Self {
930        self.negative = true;
931        self
932    }
933
934    /// Sets the milliseconds component, carrying overflow into seconds.
935    pub const fn milliseconds(mut self, milliseconds: u32) -> Self {
936        let extra_seconds = milliseconds / 1000;
937        let milliseconds = milliseconds % 1000;
938        self.seconds += extra_seconds as i64;
939        self.subsecond = self.subsecond.set_milliseconds(milliseconds);
940        self
941    }
942
943    /// Sets the microseconds component, carrying overflow into milliseconds.
944    pub const fn microseconds(mut self, microseconds: u32) -> Self {
945        let extra_milliseconds = microseconds / 1000;
946        let microseconds = microseconds % 1000;
947        let current_millis = self.subsecond.milliseconds();
948        self.subsecond = self
949            .subsecond
950            .set_milliseconds(current_millis + extra_milliseconds);
951        self.subsecond = self.subsecond.set_microseconds(microseconds);
952        self
953    }
954
955    /// Sets the nanoseconds component, carrying overflow into microseconds.
956    pub const fn nanoseconds(mut self, nanoseconds: u32) -> Self {
957        let extra_microseconds = nanoseconds / 1000;
958        let nanoseconds = nanoseconds % 1000;
959        let current_micros = self.subsecond.microseconds();
960        self.subsecond = self
961            .subsecond
962            .set_microseconds(current_micros + extra_microseconds);
963        self.subsecond = self.subsecond.set_nanoseconds(nanoseconds);
964        self
965    }
966
967    /// Sets the picoseconds component, carrying overflow into nanoseconds.
968    pub const fn picoseconds(mut self, picoseconds: u32) -> Self {
969        let extra_nanoseconds = picoseconds / 1000;
970        let picoseconds = picoseconds % 1000;
971        let current_nanos = self.subsecond.nanoseconds();
972        self.subsecond = self
973            .subsecond
974            .set_nanoseconds(current_nanos + extra_nanoseconds);
975        self.subsecond = self.subsecond.set_picoseconds(picoseconds);
976        self
977    }
978
979    /// Sets the femtoseconds component, carrying overflow into picoseconds.
980    pub const fn femtoseconds(mut self, femtoseconds: u32) -> Self {
981        let extra_picoseconds = femtoseconds / 1000;
982        let femtoseconds = femtoseconds % 1000;
983        let current_picos = self.subsecond.picoseconds();
984        self.subsecond = self
985            .subsecond
986            .set_picoseconds(current_picos + extra_picoseconds);
987        self.subsecond = self.subsecond.set_femtoseconds(femtoseconds);
988        self
989    }
990
991    /// Sets the attoseconds component, carrying overflow into femtoseconds.
992    pub const fn attoseconds(mut self, attoseconds: u32) -> Self {
993        let extra_femtoseconds = attoseconds / 1000;
994        let attoseconds = attoseconds % 1000;
995        let current_femtos = self.subsecond.femtoseconds();
996        self.subsecond = self
997            .subsecond
998            .set_femtoseconds(current_femtos + extra_femtoseconds);
999        self.subsecond = self.subsecond.set_attoseconds(attoseconds);
1000        self
1001    }
1002
1003    /// Builds the `TimeDelta` from the configured components.
1004    ///
1005    /// All subsecond components are automatically normalized and carried
1006    /// into the seconds component as needed.
1007    ///
1008    /// # Examples
1009    ///
1010    /// ```
1011    /// use lox_core::time::deltas::TimeDelta;
1012    ///
1013    /// let dt = TimeDelta::builder()
1014    ///     .seconds(1)
1015    ///     .milliseconds(500)
1016    ///     .build();
1017    /// assert_eq!(dt.seconds(), Some(1));
1018    /// ```
1019    pub const fn build(self) -> TimeDelta {
1020        let seconds = self.seconds;
1021        let attoseconds = self.subsecond.as_attoseconds();
1022
1023        // Check if negative: explicit flag OR negative seconds
1024        let is_negative = self.negative || seconds < 0;
1025
1026        if is_negative {
1027            // Use absolute value of seconds, then negate the whole thing
1028            let abs_seconds = if seconds < 0 { -seconds } else { seconds };
1029            let magnitude = TimeDelta::new(abs_seconds, attoseconds);
1030            magnitude.neg_const()
1031        } else {
1032            TimeDelta::new(seconds, attoseconds)
1033        }
1034    }
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039    use super::*;
1040    use crate::i64::consts::ATTOSECONDS_IN_SECOND;
1041
1042    #[test]
1043    fn test_new_normalizes_attoseconds() {
1044        // Attoseconds >= ATTOSECONDS_IN_SECOND should carry into seconds
1045        let dt = TimeDelta::new(1, ATTOSECONDS_IN_SECOND + 500);
1046        assert_eq!(dt.seconds(), Some(2));
1047        assert_eq!(dt.attoseconds(), Some(500));
1048
1049        // Negative attoseconds should borrow from seconds
1050        let dt = TimeDelta::new(1, -500);
1051        assert_eq!(dt.seconds(), Some(0));
1052        assert_eq!(dt.attoseconds(), Some(ATTOSECONDS_IN_SECOND - 500));
1053    }
1054
1055    #[test]
1056    fn test_from_seconds() {
1057        let dt = TimeDelta::from_seconds(60);
1058        assert_eq!(dt.seconds(), Some(60));
1059        assert_eq!(dt.attoseconds(), Some(0));
1060    }
1061
1062    #[test]
1063    fn test_from_seconds_f64_positive() {
1064        let dt = TimeDelta::from_seconds_f64(1.5);
1065        assert_eq!(dt.seconds(), Some(1));
1066        assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
1067    }
1068
1069    #[test]
1070    fn test_from_seconds_f64_negative() {
1071        let dt = TimeDelta::from_seconds_f64(-1.5);
1072        assert_eq!(dt.seconds(), Some(-2));
1073        assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
1074    }
1075
1076    #[test]
1077    fn test_from_seconds_f64_special_values() {
1078        assert!(matches!(
1079            TimeDelta::from_seconds_f64(f64::NAN),
1080            TimeDelta::NaN
1081        ));
1082        assert!(matches!(
1083            TimeDelta::from_seconds_f64(f64::INFINITY),
1084            TimeDelta::PosInf
1085        ));
1086        assert!(matches!(
1087            TimeDelta::from_seconds_f64(f64::NEG_INFINITY),
1088            TimeDelta::NegInf
1089        ));
1090    }
1091
1092    #[test]
1093    fn test_from_minutes() {
1094        let dt = TimeDelta::from_minutes_f64(1.0);
1095        assert_eq!(dt.seconds(), Some(60));
1096    }
1097
1098    #[test]
1099    fn test_from_hours() {
1100        let dt = TimeDelta::from_hours_f64(1.0);
1101        assert_eq!(dt.seconds(), Some(3600));
1102    }
1103
1104    #[test]
1105    fn test_from_days() {
1106        let dt = TimeDelta::from_days_f64(1.0);
1107        assert_eq!(dt.seconds(), Some(86400));
1108    }
1109
1110    #[test]
1111    fn test_is_positive() {
1112        assert!(TimeDelta::from_seconds(1).is_positive());
1113        assert!(!TimeDelta::from_seconds(-1).is_positive());
1114        assert!(!TimeDelta::from_seconds(0).is_positive());
1115        assert!(TimeDelta::new(0, 1).is_positive());
1116        assert!(TimeDelta::PosInf.is_positive());
1117    }
1118
1119    #[test]
1120    fn test_is_negative() {
1121        assert!(TimeDelta::from_seconds(-1).is_negative());
1122        assert!(!TimeDelta::from_seconds(1).is_negative());
1123        assert!(!TimeDelta::from_seconds(0).is_negative());
1124        assert!(TimeDelta::NegInf.is_negative());
1125    }
1126
1127    #[test]
1128    fn test_is_zero() {
1129        assert!(TimeDelta::from_seconds(0).is_zero());
1130        assert!(TimeDelta::ZERO.is_zero());
1131        assert!(!TimeDelta::from_seconds(1).is_zero());
1132        assert!(!TimeDelta::new(0, 1).is_zero());
1133    }
1134
1135    #[test]
1136    fn test_neg() {
1137        let dt = TimeDelta::new(1, 500_000_000_000_000_000);
1138        let neg = -dt;
1139        assert_eq!(neg.seconds(), Some(-2));
1140        assert_eq!(neg.attoseconds(), Some(500_000_000_000_000_000));
1141
1142        // Zero attoseconds
1143        let dt = TimeDelta::from_seconds(1);
1144        let neg = -dt;
1145        assert_eq!(neg.seconds(), Some(-1));
1146        assert_eq!(neg.attoseconds(), Some(0));
1147
1148        // Infinities
1149        assert!(matches!(-TimeDelta::PosInf, TimeDelta::NegInf));
1150        assert!(matches!(-TimeDelta::NegInf, TimeDelta::PosInf));
1151        assert!(matches!(-TimeDelta::NaN, TimeDelta::NaN));
1152    }
1153
1154    #[test]
1155    fn test_add_positive() {
1156        let a = TimeDelta::new(1, 600_000_000_000_000_000);
1157        let b = TimeDelta::new(1, 600_000_000_000_000_000);
1158        let sum = a + b;
1159        assert_eq!(sum.seconds(), Some(3));
1160        assert_eq!(sum.attoseconds(), Some(200_000_000_000_000_000));
1161    }
1162
1163    #[test]
1164    fn test_add_with_carry() {
1165        let a = TimeDelta::new(1, 700_000_000_000_000_000);
1166        let b = TimeDelta::new(0, 500_000_000_000_000_000);
1167        let sum = a + b;
1168        assert_eq!(sum.seconds(), Some(2));
1169        assert_eq!(sum.attoseconds(), Some(200_000_000_000_000_000));
1170    }
1171
1172    #[test]
1173    fn test_add_infinities() {
1174        assert!(matches!(
1175            TimeDelta::PosInf + TimeDelta::from_seconds(1),
1176            TimeDelta::PosInf
1177        ));
1178        assert!(matches!(
1179            TimeDelta::NegInf + TimeDelta::from_seconds(1),
1180            TimeDelta::NegInf
1181        ));
1182        assert!(matches!(
1183            TimeDelta::PosInf + TimeDelta::NegInf,
1184            TimeDelta::NaN
1185        ));
1186    }
1187
1188    #[test]
1189    fn test_sub_positive() {
1190        let a = TimeDelta::new(3, 200_000_000_000_000_000);
1191        let b = TimeDelta::new(1, 600_000_000_000_000_000);
1192        let diff = a - b;
1193        assert_eq!(diff.seconds(), Some(1));
1194        assert_eq!(diff.attoseconds(), Some(600_000_000_000_000_000));
1195    }
1196
1197    #[test]
1198    fn test_sub_with_borrow() {
1199        let a = TimeDelta::new(2, 200_000_000_000_000_000);
1200        let b = TimeDelta::new(1, 500_000_000_000_000_000);
1201        let diff = a - b;
1202        assert_eq!(diff.seconds(), Some(0));
1203        assert_eq!(diff.attoseconds(), Some(700_000_000_000_000_000));
1204    }
1205
1206    #[test]
1207    fn test_sub_to_negative() {
1208        let a = TimeDelta::from_seconds(1);
1209        let b = TimeDelta::from_seconds(2);
1210        let diff = a - b;
1211        assert_eq!(diff.seconds(), Some(-1));
1212        assert_eq!(diff.attoseconds(), Some(0));
1213    }
1214
1215    #[test]
1216    fn test_sub_infinities() {
1217        assert!(matches!(
1218            TimeDelta::PosInf - TimeDelta::from_seconds(1),
1219            TimeDelta::PosInf
1220        ));
1221        assert!(matches!(
1222            TimeDelta::from_seconds(1) - TimeDelta::PosInf,
1223            TimeDelta::NegInf
1224        ));
1225        assert!(matches!(
1226            TimeDelta::PosInf - TimeDelta::PosInf,
1227            TimeDelta::NaN
1228        ));
1229    }
1230
1231    #[test]
1232    fn test_ord_valid() {
1233        let a = TimeDelta::new(1, 500_000_000_000_000_000);
1234        let b = TimeDelta::new(2, 300_000_000_000_000_000);
1235        let c = TimeDelta::new(1, 500_000_000_000_000_000);
1236
1237        assert!(a < b);
1238        assert!(b > a);
1239        assert_eq!(a, c);
1240        assert!(a <= c);
1241        assert!(a >= c);
1242    }
1243
1244    #[test]
1245    fn test_ord_infinities() {
1246        let valid = TimeDelta::from_seconds(1);
1247
1248        assert!(TimeDelta::NegInf < valid);
1249        assert!(valid < TimeDelta::PosInf);
1250        assert!(TimeDelta::NegInf < TimeDelta::PosInf);
1251        assert!(TimeDelta::NaN < TimeDelta::NegInf);
1252        assert!(TimeDelta::NaN < valid);
1253        assert!(TimeDelta::NaN < TimeDelta::PosInf);
1254    }
1255
1256    #[test]
1257    fn test_builder() {
1258        let dt = TimeDelta::builder()
1259            .seconds(1)
1260            .milliseconds(500)
1261            .microseconds(250)
1262            .build();
1263
1264        assert_eq!(dt.seconds(), Some(1));
1265        assert_eq!(dt.attoseconds(), Some(500_250_000_000_000_000));
1266    }
1267
1268    #[test]
1269    fn test_builder_overflow() {
1270        let dt = TimeDelta::builder().seconds(0).milliseconds(1500).build();
1271
1272        assert_eq!(dt.seconds(), Some(1));
1273        assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
1274    }
1275
1276    #[test]
1277    fn test_to_seconds() {
1278        let dt = TimeDelta::new(1, 500_000_000_000_000_000);
1279        assert_eq!(dt.to_seconds().to_f64(), 1.5);
1280
1281        let dt = TimeDelta::new(-2, 500_000_000_000_000_000);
1282        assert_eq!(dt.to_seconds().to_f64(), -1.5);
1283    }
1284
1285    #[test]
1286    fn test_from_integer() {
1287        let dt: TimeDelta = 42i32.into();
1288        assert_eq!(dt.seconds(), Some(42));
1289
1290        let dt: TimeDelta = 42i64.into();
1291        assert_eq!(dt.seconds(), Some(42));
1292    }
1293
1294    #[test]
1295    fn test_add_assign() {
1296        let mut dt = TimeDelta::from_seconds(1);
1297        dt += TimeDelta::from_seconds(2);
1298        assert_eq!(dt.seconds(), Some(3));
1299    }
1300
1301    #[test]
1302    fn test_sub_assign() {
1303        let mut dt = TimeDelta::from_seconds(5);
1304        dt -= TimeDelta::from_seconds(2);
1305        assert_eq!(dt.seconds(), Some(3));
1306    }
1307
1308    #[test]
1309    fn test_time_delta_julian_date() {
1310        let dt = TimeDelta::builder()
1311            .seconds(-725803232)
1312            .milliseconds(184)
1313            .build();
1314        let exp = -725803232.184;
1315        let act = dt.julian_date(Epoch::J2000, Unit::Seconds);
1316        assert_eq!(act, exp);
1317    }
1318
1319    #[test]
1320    fn test_mul_precision() {
1321        // Test that multiplication preserves precision better than naive conversion
1322        let dt = TimeDelta::new(1000000, 123_456_789_012_345_678);
1323        let factor = 1e-10;
1324
1325        let result = factor * dt;
1326
1327        // Expected: 1000000.123456789012345678 * 1e-10 = 0.0001000000123456789...
1328        // With improved precision, we should preserve more digits
1329        let result_f64 = result.to_seconds().to_f64();
1330        let expected = 0.0001000000123456789;
1331
1332        // Check within attosecond precision
1333        assert!((result_f64 - expected).abs() < 1e-17);
1334    }
1335
1336    #[test]
1337    fn test_mul_small_factors() {
1338        // Test with very small factors like those used in time scale conversions
1339        let dt = TimeDelta::new(788000833, 145_000_000_000_000_000);
1340        let lg = 6.969290134e-10; // From TCG/TT conversions
1341
1342        let result = lg * dt;
1343
1344        // The result should be computed with higher precision than naive approach
1345        let result_seconds = result.to_seconds().to_f64();
1346
1347        // Verify it's in the expected range (around 0.55 seconds for these values)
1348        assert!(result_seconds > 0.5 && result_seconds < 0.6);
1349        assert!(result.is_finite());
1350    }
1351
1352    #[test]
1353    fn test_mul_special_values() {
1354        let dt = TimeDelta::from_seconds(100);
1355
1356        // Multiply by zero
1357        let result = 0.0 * dt;
1358        assert!(result.is_zero());
1359
1360        // Multiply by NaN
1361        let result = f64::NAN * dt;
1362        assert!(result.is_nan());
1363
1364        // Multiply by infinity
1365        let result = f64::INFINITY * dt;
1366        assert_eq!(result, TimeDelta::PosInf);
1367
1368        let result = f64::NEG_INFINITY * dt;
1369        assert_eq!(result, TimeDelta::NegInf);
1370
1371        // Multiply infinity by negative factor
1372        let result = -2.0 * TimeDelta::PosInf;
1373        assert_eq!(result, TimeDelta::NegInf);
1374
1375        let result = -2.0 * TimeDelta::NegInf;
1376        assert_eq!(result, TimeDelta::PosInf);
1377
1378        // Multiply infinity by zero
1379        let result = 0.0 * TimeDelta::PosInf;
1380        assert!(result.is_nan());
1381    }
1382
1383    #[test]
1384    fn test_builder_negative_subsecond_only() {
1385        // Test that negative() works for sub-second values (the main fix)
1386        let dt = TimeDelta::builder()
1387            .microseconds(65)
1388            .nanoseconds(500)
1389            .negative()
1390            .build();
1391
1392        // Should be -65.5 microseconds = -6.55e-5 seconds
1393        let expected = -65.5e-6;
1394        assert!(
1395            (dt.to_seconds().to_f64() - expected).abs() < 1e-15,
1396            "expected {} but got {}",
1397            expected,
1398            dt.to_seconds().to_f64()
1399        );
1400        assert!(dt.is_negative());
1401    }
1402
1403    #[test]
1404    fn test_builder_negative_with_seconds() {
1405        // Test negative() with whole seconds
1406        let dt = TimeDelta::builder()
1407            .seconds(1)
1408            .milliseconds(500)
1409            .negative()
1410            .build();
1411
1412        assert_eq!(dt.to_seconds().to_f64(), -1.5);
1413        assert!(dt.is_negative());
1414    }
1415
1416    #[test]
1417    fn test_builder_negative_seconds_without_flag() {
1418        // Existing behavior: negative seconds should still work
1419        let dt = TimeDelta::builder().seconds(-1).milliseconds(500).build();
1420
1421        assert_eq!(dt.to_seconds().to_f64(), -1.5);
1422        assert!(dt.is_negative());
1423    }
1424
1425    #[test]
1426    fn test_builder_negative_flag_with_negative_seconds() {
1427        // Both negative flag and negative seconds: should be negative (no double negation)
1428        let dt = TimeDelta::builder()
1429            .seconds(-1)
1430            .milliseconds(500)
1431            .negative()
1432            .build();
1433
1434        assert_eq!(dt.to_seconds().to_f64(), -1.5);
1435        assert!(dt.is_negative());
1436    }
1437
1438    #[test]
1439    fn test_seconds_precision() {
1440        // Verify that Seconds preserves precision for large values
1441        let dt = TimeDelta::builder()
1442            .seconds(-725803167)
1443            .milliseconds(816)
1444            .build();
1445        let tf = dt.to_seconds();
1446
1447        // For negative times, internal representation stores one less second
1448        // and a positive subsecond fraction that adds up to the correct value.
1449        // -725803167.816 = -725803168 + 0.184
1450        assert_eq!(tf.hi, -725803168.0);
1451        // lo should be the subsecond fraction (1.0 - 0.816 = 0.184)
1452        assert!((tf.lo - 0.184).abs() < 1e-15);
1453
1454        // Combined should give the correct value
1455        assert!((tf.to_f64() - (-725803167.816)).abs() < 1e-9);
1456    }
1457
1458    #[test]
1459    fn test_seconds_arithmetic() {
1460        let a = Seconds::new(1e15, 0.5);
1461        let b = Seconds::new(1.0, 0.25);
1462
1463        // Addition
1464        let sum = a + b;
1465        assert_eq!(sum.to_f64(), 1e15 + 1.75);
1466
1467        // Subtraction
1468        let diff = a - b;
1469        assert_eq!(diff.to_f64(), 1e15 - 1.0 + 0.25);
1470
1471        // Multiplication by scalar
1472        let prod = a * 2.0;
1473        assert_eq!(prod.to_f64(), 2e15 + 1.0);
1474
1475        // Negation
1476        let neg = -a;
1477        assert_eq!(neg.hi, -1e15);
1478        assert_eq!(neg.lo, -0.5);
1479    }
1480
1481    #[test]
1482    fn test_time_units_f64() {
1483        assert_eq!(1.0.days(), TimeDelta::from_days_f64(1.0));
1484        assert_eq!(2.0.hours(), TimeDelta::from_hours_f64(2.0));
1485        assert_eq!(30.0.mins(), TimeDelta::from_minutes_f64(30.0));
1486        assert_eq!(60.0.secs(), TimeDelta::from_seconds_f64(60.0));
1487        assert_eq!(500.0.millis(), TimeDelta::from_seconds_f64(0.5));
1488        assert_eq!(1000.0.micros(), TimeDelta::from_seconds_f64(1e-3));
1489        assert_eq!(1000.0.nanos(), TimeDelta::from_seconds_f64(1e-6));
1490        assert_eq!(1000.0.picos(), TimeDelta::from_seconds_f64(1e-9));
1491        assert_eq!(1000.0.femtos(), TimeDelta::from_seconds_f64(1e-12));
1492        assert_eq!(1000.0.attos(), TimeDelta::from_seconds_f64(1e-15));
1493    }
1494
1495    #[test]
1496    fn test_time_units_i64() {
1497        assert_eq!(1_i64.days(), TimeDelta::from_days_f64(1.0));
1498        assert_eq!(2_i64.hours(), TimeDelta::from_hours_f64(2.0));
1499        assert_eq!(30_i64.mins(), TimeDelta::from_minutes_f64(30.0));
1500        assert_eq!(60_i64.secs(), TimeDelta::from_seconds(60));
1501        assert_eq!(500_i64.millis(), TimeDelta::from_milliseconds(500));
1502        assert_eq!(1000_i64.micros(), TimeDelta::from_microseconds(1000));
1503        assert_eq!(1000_i64.nanos(), TimeDelta::from_nanoseconds(1000));
1504        assert_eq!(1000_i64.picos(), TimeDelta::from_picoseconds(1000));
1505        assert_eq!(1000_i64.femtos(), TimeDelta::from_femtoseconds(1000));
1506        assert_eq!(1000_i64.attos(), TimeDelta::from_attoseconds(1000));
1507    }
1508
1509    #[test]
1510    fn test_from_milliseconds() {
1511        let dt = TimeDelta::from_milliseconds(1500);
1512        assert_eq!(dt.seconds(), Some(1));
1513        assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
1514
1515        let dt = TimeDelta::from_milliseconds(-1500);
1516        assert_eq!(dt.seconds(), Some(-2));
1517        assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
1518    }
1519
1520    #[test]
1521    fn test_from_microseconds() {
1522        let dt = TimeDelta::from_microseconds(1_000_000);
1523        assert_eq!(dt.seconds(), Some(1));
1524        assert_eq!(dt.attoseconds(), Some(0));
1525
1526        let dt = TimeDelta::from_microseconds(1_500_000);
1527        assert_eq!(dt.seconds(), Some(1));
1528        assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
1529    }
1530
1531    #[test]
1532    fn test_from_nanoseconds() {
1533        let dt = TimeDelta::from_nanoseconds(500_000_000);
1534        assert_eq!(dt.to_seconds().to_f64(), 0.5);
1535
1536        let dt = TimeDelta::from_nanoseconds(1_500_000_000);
1537        assert_eq!(dt.seconds(), Some(1));
1538        assert_eq!(dt.attoseconds(), Some(500_000_000_000_000_000));
1539    }
1540
1541    #[test]
1542    fn test_from_picoseconds() {
1543        let dt = TimeDelta::from_picoseconds(1_000_000_000_000);
1544        assert_eq!(dt.seconds(), Some(1));
1545        assert_eq!(dt.attoseconds(), Some(0));
1546    }
1547
1548    #[test]
1549    fn test_from_femtoseconds() {
1550        let dt = TimeDelta::from_femtoseconds(1_000_000_000_000_000);
1551        assert_eq!(dt.seconds(), Some(1));
1552        assert_eq!(dt.attoseconds(), Some(0));
1553    }
1554
1555    #[test]
1556    fn test_from_attoseconds() {
1557        let dt = TimeDelta::from_attoseconds(ATTOSECONDS_IN_SECOND);
1558        assert_eq!(dt.seconds(), Some(1));
1559        assert_eq!(dt.attoseconds(), Some(0));
1560
1561        let dt = TimeDelta::from_attoseconds(ATTOSECONDS_IN_SECOND + 42);
1562        assert_eq!(dt.seconds(), Some(1));
1563        assert_eq!(dt.attoseconds(), Some(42));
1564    }
1565}