rust_jpl/
time.rs

1//! Time conversion utilities for Julian dates and calendar dates
2
3use crate::Error;
4
5/// Represents a Julian Date (JD)
6#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
7pub struct JulianDate {
8    /// Julian day number
9    pub jd: f64,
10}
11
12impl JulianDate {
13    /// Create a Julian date from a Julian day number
14    pub fn new(jd: f64) -> Self {
15        Self { jd }
16    }
17
18    /// Convert calendar date to Julian date
19    ///
20    /// # Arguments
21    /// * `year` - Year (e.g., 2024)
22    /// * `month` - Month (1-12)
23    /// * `day` - Day of month (1-31)
24    /// * `hour` - Hour (0-23)
25    /// * `minute` - Minute (0-59)
26    /// * `second` - Second (0-59.999...)
27    ///
28    /// # Example
29    /// ```
30    /// use rust_jpl::JulianDate;
31    /// let jd = JulianDate::from_calendar(2024, 1, 15, 12, 0, 0.0);
32    /// ```
33    pub fn from_calendar(
34        year: i32,
35        month: i32,
36        day: i32,
37        hour: i32,
38        minute: i32,
39        second: f64,
40    ) -> Result<Self, Error> {
41        if !(1..=12).contains(&month) {
42            return Err(Error::InvalidDate(format!("Invalid month: {}", month)));
43        }
44        if !(1..=31).contains(&day) {
45            return Err(Error::InvalidDate(format!("Invalid day: {}", day)));
46        }
47        if !(0..=23).contains(&hour) {
48            return Err(Error::InvalidDate(format!("Invalid hour: {}", hour)));
49        }
50        if !(0..=59).contains(&minute) {
51            return Err(Error::InvalidDate(format!("Invalid minute: {}", minute)));
52        }
53        if !(0.0..60.0).contains(&second) {
54            return Err(Error::InvalidDate(format!("Invalid second: {}", second)));
55        }
56
57        let a = (14 - month) / 12;
58        let y = year + 4800 - a;
59        let m = month + 12 * a - 3;
60
61        let jdn = day as f64 + (153 * m + 2) as f64 / 5.0 + 365.0 * y as f64 + (y / 4) as f64
62            - (y / 100) as f64
63            + (y / 400) as f64
64            - 32045.0;
65
66        let fraction = (hour as f64 + minute as f64 / 60.0 + second / 3600.0) / 24.0;
67
68        Ok(Self {
69            jd: jdn + fraction - 0.5,
70        })
71    }
72
73    /// Convert Julian date to calendar date
74    pub fn to_calendar(&self) -> CalendarDate {
75        let j = (self.jd + 0.5).floor() as i64;
76        let f = self.jd + 0.5 - j as f64;
77
78        let j0 = j;
79        let j1 = j0 + 68569;
80        let j2 = 4 * j1 / 146097;
81        let j3 = j1 - (146097 * j2 + 3) / 4;
82        let j4 = 4000 * (j3 + 1) / 1461001;
83        let j5 = j3 - 1461 * j4 / 4 + 31;
84        let j6 = 80 * j5 / 2447;
85        let j7 = j5 - 2447 * j6 / 80;
86        let j8 = j6 / 11;
87        let j9 = j6 + 2 - 12 * j8;
88        let j10 = 100 * (j2 - 49) + j4 + j8;
89
90        let year = j10 as i32;
91        let month = j9 as i32;
92        let day = j7 as i32;
93
94        let total_seconds = f * 86400.0;
95        let hour = (total_seconds / 3600.0) as i32;
96        let minute = ((total_seconds % 3600.0) / 60.0) as i32;
97        let second = total_seconds % 60.0;
98
99        CalendarDate {
100            year,
101            month,
102            day,
103            hour,
104            minute,
105            second,
106        }
107    }
108
109    /// Get the Julian day number
110    pub fn as_f64(&self) -> f64 {
111        self.jd
112    }
113}
114
115/// Represents a calendar date
116#[derive(Debug, Clone, Copy, PartialEq)]
117pub struct CalendarDate {
118    pub year: i32,
119    pub month: i32,
120    pub day: i32,
121    pub hour: i32,
122    pub minute: i32,
123    pub second: f64,
124}
125
126impl CalendarDate {
127    /// Create a new calendar date
128    pub fn new(year: i32, month: i32, day: i32, hour: i32, minute: i32, second: f64) -> Self {
129        Self {
130            year,
131            month,
132            day,
133            hour,
134            minute,
135            second,
136        }
137    }
138
139    /// Convert to Julian date
140    pub fn to_julian(&self) -> Result<JulianDate, Error> {
141        JulianDate::from_calendar(
142            self.year,
143            self.month,
144            self.day,
145            self.hour,
146            self.minute,
147            self.second,
148        )
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_julian_date_conversion() {
158        let jd = JulianDate::from_calendar(2024, 1, 15, 12, 0, 0.0).unwrap();
159        let cal = jd.to_calendar();
160        assert_eq!(cal.year, 2024);
161        assert_eq!(cal.month, 1);
162        assert_eq!(cal.day, 15);
163    }
164}