starstuff_types/
time.rs

1/*!
2Astonomical time types and utilities
3 */
4
5use crate::angle::{Angle, TWO_PI};
6
7use chrono::{DateTime, Datelike, Timelike, Utc};
8
9/**
10Earth Rotation Angle
11
12<https://en.wikipedia.org/wiki/Sidereal_time>
13 */
14#[allow(clippy::excessive_precision)] // NOTE: actual equation has that much precision
15pub fn earth_rotation_angle(time_julian_ut1: JulianDate) -> Angle {
16    Angle::Radian(
17        TWO_PI * (0.7790572732640 + 1.00273781191135448 * (time_julian_ut1.0 - 2451545.0)),
18    )
19}
20
21/// Julian Date (J2000 epoch)
22#[derive(Debug)]
23pub struct JulianDate(
24    /// Seconds since J2000
25    pub f64,
26);
27
28impl<T> From<DateTime<T>> for JulianDate
29where
30    T: chrono::TimeZone,
31    chrono::DateTime<chrono::Utc>: From<chrono::DateTime<T>>,
32{
33    fn from(date: DateTime<T>) -> Self {
34        // https://stackoverflow.com/a/52431241
35
36        // need UTC date
37        let date: DateTime<Utc> = date.into();
38        assert!(
39            1801 <= date.year() && date.year() <= 2099,
40            "Datetime must be between year 1801 and 2099" // TODO: Find an algorithm that has a wider range.
41        );
42
43        Self(
44            // Note: all the integer divisions are truncating toward zero *by design*.
45            (367 * date.year() - ((7 * (date.year() + (date.month() as i32 + 9) / 12)) / 4)
46                + ((275 * date.month() as i32) / 9)) as f64
47                + date.day() as f64
48                + 1721013.5
49                + (date.hour() as f64
50                    + date.minute() as f64 / 60.0
51                    + date.second() as f64 / 3600.0)
52                    / 24.0
53                - (0.5_f64).copysign(100.0 * date.year() as f64 + date.month() as f64 - 190002.5)
54                + 0.5,
55        )
56    }
57}
58
59/// Greenwich Mean Sidereal Time
60pub struct GMST(
61    /// Hour
62    pub Angle,
63);
64
65impl From<JulianDate> for GMST {
66    fn from(julian_date: JulianDate) -> Self {
67        // https://en.wikipedia.org/wiki/Sidereal_time
68        Self(Angle::Hour(earth_rotation_angle(julian_date).to_hr()))
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use chrono::TimeZone;
75
76    use crate::time::*;
77
78    #[test]
79    fn juliandate() {
80        // From: https://en.wikipedia.org/wiki/Epoch_(astronomy)#J2000
81        // Definition of J2000 epoch
82        assert_eq!(
83            JulianDate::from(Utc.with_ymd_and_hms(2000, 1, 1, 12, 0, 0).unwrap()).0,
84            2451545.0
85        );
86
87        // From: https://en.wikipedia.org/wiki/Julian_day
88        // 00:30:00.0 UT January 1, 2013, is 2_456_293.520_833
89        assert_float_absolute_eq!(
90            JulianDate::from(Utc.with_ymd_and_hms(2013, 1, 1, 0, 30, 0).unwrap()).0,
91            2_456_293.520_833,
92            1e-6
93        );
94    }
95}