1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use super::TimeSystem;
use super::instant::{Era, Instant};
use super::{J1900_OFFSET, SECONDS_PER_DAY};

/// `ModifiedJulian` handles the Modified Julian Days as explained
/// [here](http://tycho.usno.navy.mil/mjd.html).
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct ModifiedJulian {
    pub days: f64,
}

impl ModifiedJulian {
    /// `julian_days` returns the true Julian days from epoch 01 Jan -4713, 12:00
    /// as explained in "Fundamentals of astrodynamics and applications", Vallado et al.
    /// 4th edition, page 182, and on [Wikipedia](https://en.wikipedia.org/wiki/Julian_day).
    pub fn julian_days(self) -> f64 {
        self.days + 2_400_000.5
    }
}

impl TimeSystem for ModifiedJulian {
    /// `from_instant` converts an `Instant` to a ModifiedJulian as detailed in Vallado et al.
    /// 4th edition, page 182.
    ///
    /// [Leap second source](https://www.ietf.org/timezones/data/leap-seconds.list) contains
    /// information pertinent to the NTP time definition, whose epoch is twelve hours *ahead* of
    /// the Julian Day. Here is the relevant quote:
    /// > The NTP timestamps are in units of seconds since the NTP epoch,
    /// > which is 1 January 1900, 00:00:00. The Modified Julian Day number
    /// > corresponding to the NTP time stamp, X, can be computed as
    /// >
    /// > `X/86400 + 15020`
    /// >
    /// > where the first term converts seconds to days and the second
    /// > term adds the MJD corresponding to the time origin defined above.
    /// > The integer portion of the result is the integer MJD for that
    /// > day, and any remainder is the time of day, expressed as the
    /// > fraction of the day since 0 hours UTC. The conversion from day
    /// > fraction to seconds or to hours, minutes, and seconds may involve
    /// > rounding or truncation, depending on the method used in the
    /// > computation.
    fn from_instant(instant: Instant) -> ModifiedJulian {
        let modifier = if instant.era() == Era::Present {
            1.0
        } else {
            -1.0
        };
        ModifiedJulian {
            days: J1900_OFFSET + modifier * (instant.secs() as f64) / SECONDS_PER_DAY
                + f64::from(instant.nanos()) * 1e-9,
        }
    }

    /// `into_instant` returns an `Instant` from the ModifiedJulian.
    fn into_instant(self) -> Instant {
        let era: Era;
        let modifier = if self.days >= J1900_OFFSET {
            era = Era::Present;
            1.0
        } else {
            era = Era::Past;
            -1.0
        };
        let secs_frac = (self.days - J1900_OFFSET) * SECONDS_PER_DAY * modifier;
        let seconds = secs_frac.round();
        let nanos = (secs_frac - seconds) * 1e9 / (SECONDS_PER_DAY * modifier);
        Instant::new(seconds as u64, nanos.round() as u32, era)
    }
}