sidereon_core/reduced_orbit/
time.rs1use crate::astro::constants::time::{SECONDS_PER_DAY, TT_MINUS_TAI_S};
4use crate::astro::time::model::TimeScale;
5use crate::astro::time::scales::{find_leap_seconds, julian_day_number, TimeScales};
6
7use crate::constants::{BDT_MINUS_TAI_S, GPST_MINUS_TAI_S};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct CalendarEpoch {
15 pub year: i32,
17 pub month: i32,
19 pub day: i32,
21 pub hour: i32,
23 pub minute: i32,
25 pub second: f64,
27}
28
29impl CalendarEpoch {
30 pub const fn new(year: i32, month: i32, day: i32, hour: i32, minute: i32, second: f64) -> Self {
32 Self {
33 year,
34 month,
35 day,
36 hour,
37 minute,
38 second,
39 }
40 }
41
42 pub(crate) fn time_scales(self, scale: TimeScale) -> TimeScales {
48 let utc = scale_calendar_to_utc(self, scale);
49 TimeScales::from_utc(
50 utc.year, utc.month, utc.day, utc.hour, utc.minute, utc.second,
51 )
52 .expect("calendar epoch has a finite second")
53 }
54}
55
56fn scale_calendar_to_utc(cal: CalendarEpoch, scale: TimeScale) -> CalendarEpoch {
57 if matches!(scale, TimeScale::Utc) {
58 return cal;
59 }
60 let tai = normalize_calendar_seconds(cal, cal.second + tai_minus_scale_seconds(scale));
61 tai_calendar_to_utc(tai)
62}
63
64fn tai_minus_scale_seconds(scale: TimeScale) -> f64 {
65 match scale {
66 TimeScale::Utc => 0.0,
67 TimeScale::Tai => 0.0,
68 TimeScale::Tt | TimeScale::Tdb => -TT_MINUS_TAI_S,
69 TimeScale::Gpst | TimeScale::Gst => GPST_MINUS_TAI_S,
70 TimeScale::Bdt => BDT_MINUS_TAI_S,
71 }
72}
73
74fn tai_calendar_to_utc(tai: CalendarEpoch) -> CalendarEpoch {
75 if let Some(utc) = positive_leap_second_utc_label(tai) {
76 return utc;
77 }
78
79 let mut leap = leap_seconds_at_utc_label(tai);
80 let mut utc = normalize_calendar_seconds(tai, tai.second - leap);
81 for _ in 0..3 {
82 let next_leap = leap_seconds_at_utc_label(utc);
83 if next_leap == leap {
84 return utc;
85 }
86 leap = next_leap;
87 utc = normalize_calendar_seconds(tai, tai.second - leap);
88 }
89 utc
90}
91
92fn positive_leap_second_utc_label(tai: CalendarEpoch) -> Option<CalendarEpoch> {
93 let tai_sod = seconds_of_day(tai);
94 let utc_midnight = CalendarEpoch::new(tai.year, tai.month, tai.day, 0, 0, 0.0);
95 let previous_second = normalize_calendar_seconds(utc_midnight, -1.0);
96 let old_leap = leap_seconds_at_utc_label(previous_second);
97 let new_leap = leap_seconds_at_utc_label(utc_midnight);
98 if new_leap <= old_leap || !(old_leap..new_leap).contains(&tai_sod) {
99 return None;
100 }
101
102 let mut utc = previous_second;
103 utc.second = 60.0 + (tai_sod - old_leap);
104 Some(utc)
105}
106
107fn leap_seconds_at_utc_label(cal: CalendarEpoch) -> f64 {
108 let jd1 = julian_day_number(cal.year, cal.month, cal.day) as f64 - 0.5;
109 let lookup_second = if cal.second >= 60.0 { 59.0 } else { cal.second };
110 let jd2 =
111 (cal.hour as f64 * 3600.0 + cal.minute as f64 * 60.0 + lookup_second) / SECONDS_PER_DAY;
112 find_leap_seconds(jd1 + jd2)
113}
114
115fn seconds_of_day(cal: CalendarEpoch) -> f64 {
116 cal.hour as f64 * 3600.0 + cal.minute as f64 * 60.0 + cal.second
117}
118
119fn normalize_calendar_seconds(mut cal: CalendarEpoch, second: f64) -> CalendarEpoch {
120 cal.second = second;
121 while cal.second < 0.0 {
122 cal.second += 60.0;
123 cal.minute -= 1;
124 }
125 while cal.second >= 60.0 {
126 cal.second -= 60.0;
127 cal.minute += 1;
128 }
129 while cal.minute < 0 {
130 cal.minute += 60;
131 cal.hour -= 1;
132 }
133 while cal.minute > 59 {
134 cal.minute -= 60;
135 cal.hour += 1;
136 }
137 while cal.hour < 0 {
138 cal.hour += 24;
139 cal.day -= 1;
140 }
141 while cal.hour > 23 {
142 cal.hour -= 24;
143 cal.day += 1;
144 }
145 while cal.day < 1 {
146 cal.month -= 1;
147 if cal.month < 1 {
148 cal.month = 12;
149 cal.year -= 1;
150 }
151 cal.day = days_in_month(cal.year, cal.month);
152 }
153 loop {
154 let month_days = days_in_month(cal.year, cal.month);
155 if cal.day <= month_days {
156 break;
157 }
158 cal.day -= month_days;
159 cal.month += 1;
160 if cal.month > 12 {
161 cal.month = 1;
162 cal.year += 1;
163 }
164 }
165 cal
166}
167
168fn days_in_month(year: i32, month: i32) -> i32 {
169 match month {
170 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
171 4 | 6 | 9 | 11 => 30,
172 2 if is_leap_year(year) => 29,
173 2 => 28,
174 _ => 31,
175 }
176}
177
178fn is_leap_year(year: i32) -> bool {
179 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
180}
181
182pub(crate) fn dt_seconds(t0: &TimeScales, t: &TimeScales) -> f64 {
184 let dwhole = t.jd_whole - t0.jd_whole;
185 let dfrac = t.tt_fraction - t0.tt_fraction;
186 (dwhole + dfrac) * SECONDS_PER_DAY
187}