geodate/
geodate.rs

1use earth_orbit::*;
2use moon_phase::*;
3use sun_transit::*;
4
5use alloc::string::String;
6#[cfg(not(feature = "std"))]
7use num_traits::Float;
8
9#[derive(Clone, Copy)]
10pub enum Epoch {
11    Gregorian,
12    Unix
13}
14
15#[derive(Clone, Copy)]
16pub enum Calendar {
17    Lunisolar,
18    Solar
19}
20
21static ZEROS: [i64; 6] = [
22               0, // 1970-01-01 | Unix epoch
23
24      -410227200, // 1957-01-01 | The following dates all
25     -1009843200, // 1938-01-01 | occurs on a new moon day,
26     -2208988800, // 1900-01-01 | but we cannot go further
27     -8551872000, // 1699-01-01 | back than 1620 with the
28    -10950249600  // 1623-01-01 | current delta time formula.
29];
30
31/// Get a string representation of a geodate
32///
33/// Format:
34/// - %h   Century (hectoyear)
35/// - %y   Solar year starting in 1900, Gregorian friendly epoch
36/// - %m   Lunar month (for a lunisolar calendar)
37/// - %d   Solar day
38/// - %c   Centiday
39/// - %b   Dimiday
40///
41/// %u Solar year starting in 1970, Unix friendly epoch
42/// %s Seasonal month (for a solar calendar)
43///
44/// %x Unix timestamp
45pub fn get_formatted_date(format: &str, timestamp: i64, longitude: f64) -> String {
46    let mut res = String::from(format);
47    let now = timestamp;
48    let lon = longitude;
49
50    if format.contains("%x") {
51        res = res.replace("%x", &format!("{}", now));
52
53        if !format.contains("%") {
54            return res;
55        }
56    }
57
58    let epoch = if format.contains("%u") {
59        Epoch::Unix
60    } else {
61        Epoch::Gregorian
62    };
63
64    let calendar = if format.contains("%s") {
65        Calendar::Solar
66    } else {
67        Calendar::Lunisolar
68    };
69
70    let mut first_new_moon = 0;
71    let mut zero = 0;
72    for &e in &ZEROS {
73        // Pick the nearest zero to shorten calculations
74        first_new_moon = get_next_new_moon(e);
75        zero = get_midnight(first_new_moon, lon);
76        if zero < now {
77            break;
78        }
79    }
80    if now < zero {
81        panic!("too far back in time");
82    }
83
84    let mut new_year = get_next_december_solstice(zero);
85    let mut new_month = match calendar {
86        Calendar::Solar     => get_next_march_equinox(zero),
87        Calendar::Lunisolar => get_next_new_moon(first_new_moon)
88    };
89
90    let mut midnight = get_midnight(now, lon);
91    if midnight > now {
92        midnight -= 86400;
93    } else if midnight <= now - 86400 {
94        midnight += 86400;
95    }
96
97    let mut d = 0;
98    let mut m = 0;
99    let mut y = 0;
100    let mut t = zero;
101    while t < midnight - 2000 { // Mean solar day approximation
102        d += 1;
103        t += 86400;
104        if new_month < t + 86400 {
105            new_month = match calendar {
106                Calendar::Solar => {
107                    match m {
108                        0 => get_next_june_solstice(new_month),
109                        1 => get_next_september_equinox(new_month),
110                        2 => get_next_december_solstice(new_month),
111                        3 => get_next_march_equinox(new_month),
112                        _ => unreachable!()
113                    }
114                },
115                Calendar::Lunisolar => {
116                    get_next_new_moon(new_month)
117                }
118            };
119            d = 0;
120            m += 1;
121            if new_year < t + 86400 {
122                new_year = get_next_december_solstice(new_year);
123                m = 0;
124                y += 1;
125            }
126        }
127    }
128
129    let epoch_zero = match epoch {
130        Epoch::Unix      => ZEROS[0],
131        Epoch::Gregorian => ZEROS[3]
132    };
133
134    y += ((zero - epoch_zero) as f64 / 86400.0 / 365.25).round() as i64;
135
136    if y < 0 {
137        y = y.abs();
138        if res.contains("%h") || res.contains("%y") || res.contains("%u") {
139            res.insert(0, '-');
140        }
141    }
142
143    if res.contains("%h") {
144        let h = y / 100;
145        res = res.replace("%h", &format!("{:02}", h));
146    }
147    y = y % 100;
148
149    res = res.replace("%u", &format!("{:02}", y));
150    res = res.replace("%y", &format!("{:02}", y));
151
152    res = res.replace("%m", &format!("{:02}", m));
153    res = res.replace("%s", &format!("{:02}", m));
154    res = res.replace("%d", &format!("{:02}", d));
155
156    let e = (10000 * (now - midnight)) / 86400;
157    let c = e / 100;
158    let b = e % 100;
159    res = res.replace("%c", &format!("{:02}", c));
160    res = res.replace("%b", &format!("{:02}", b));
161
162    res
163}
164
165/// Get date with the default formatting
166pub fn get_date(timestamp: i64, longitude: f64) -> String {
167    get_formatted_date("%h:%y:%m:%d:%c:%b", timestamp, longitude)
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use utils::*;
174
175    #[test]
176    fn get_date_test() {
177        assert_eq!("01:14:05:24:15:42", get_date(1403322675, -1.826189));
178    }
179
180    #[test]
181    fn get_solar_date_test() {
182        let format = "%u:%s:%d:%c:%b";
183        // Stonehenge coordinates: 51.178844, -1.826189
184        assert_eq!("44:02:00:15:42", get_formatted_date(format, 1403322675, -1.826189));
185    }
186
187    #[test]
188    fn get_lunisolar_date_test() {
189        let format = "%u:%m:%d:%c:%b";
190        assert_eq!("00:00:00:00:00", get_formatted_date(format, parse_time("1970-01-07T00:06:15+00:00"), 0.0));
191        assert_eq!("00:11:29:99:99", get_formatted_date(format, parse_time("1970-12-28T00:01:20+00:00"), 0.0));
192        assert_eq!("01:00:00:00:00", get_formatted_date(format, parse_time("1970-12-28T00:01:30+00:00"), 0.0));
193        assert_eq!("06:00:00:00:00", get_formatted_date(format, parse_time("1976-01-01T00:03:20+00:00"), 0.0));
194        assert_eq!("14:03:03:71:59", get_formatted_date(format, 449947500, -2.7653));
195        assert_eq!("43:11:28:99:98", get_formatted_date(format, parse_time("2014-01-01T00:03:20+00:00"), 0.0));
196        assert_eq!("43:11:28:99:99", get_formatted_date(format, parse_time("2014-01-01T00:03:30+00:00"), 0.0));
197        assert_eq!("44:00:00:00:00", get_formatted_date(format, parse_time("2014-01-01T00:03:40+00:00"), 0.0));
198
199        let format = "%h:%u:%m:%d:%c:%b";
200        assert_eq!("00:63:00:00:00:00", get_formatted_date(format, parse_time("2033-01-01T00:03:45+00:00"), 0.0));
201        assert_eq!("01:01:00:00:00:00", get_formatted_date(format, parse_time("2071-01-01T00:03:30+00:00"), 0.0));
202        assert_eq!("01:50:00:00:00:00", get_formatted_date(format, parse_time("2120-01-01T00:03:00+00:00"), 0.0));
203        assert_eq!("02:15:00:00:00:00", get_formatted_date(format, parse_time("2185-01-01T00:03:30+00:00"), 0.0));
204        assert_eq!("03:40:00:00:00:00", get_formatted_date(format, parse_time("2310-01-01T00:02:30+00:00"), 0.0));
205        assert_eq!("05:30:00:00:00:00", get_formatted_date(format, parse_time("2500-01-01T00:02:30+00:00"), 0.0));
206
207        let format = "%u:%m:%d:%c:%b";
208        assert_eq!("63:00:00:00:00", get_formatted_date(format, parse_time("2033-01-01T00:03:45+00:00"), 0.0));
209        assert_eq!("01:00:00:00:00", get_formatted_date(format, parse_time("2071-01-01T00:03:30+00:00"), 0.0));
210        assert_eq!("50:00:00:00:00", get_formatted_date(format, parse_time("2120-01-01T00:03:00+00:00"), 0.0));
211        assert_eq!("15:00:00:00:00", get_formatted_date(format, parse_time("2185-01-01T00:03:30+00:00"), 0.0));
212        assert_eq!("40:00:00:00:00", get_formatted_date(format, parse_time("2310-01-01T00:02:30+00:00"), 0.0));
213        assert_eq!("30:00:00:00:00", get_formatted_date(format, parse_time("2500-01-01T00:02:30+00:00"), 0.0));
214
215        // Check bugs fixed by version 0.2.1
216        assert_eq!("46:02:10:49:46", get_formatted_date(format, parse_time("2016-03-19T12:00:00+00:00"), 0.0));
217        assert_eq!("46:02:11:80:04", get_formatted_date(format, parse_time("2016-03-20T08:00:00+00:00"), 170.0));
218        assert_eq!("30:04:28:99:99", get_formatted_date(format, parse_time("2000-06-01T17:57:50+00:00"), 90.0));
219        assert_eq!("30:05:00:00:00", get_formatted_date(format, parse_time("2000-06-01T17:58:00+00:00"), 90.0));
220
221        // Check bugs fixed by version 0.3.0
222        assert_eq!("00:11:29:99:99", get_formatted_date(format, parse_time("1970-12-28T00:08:40+00:00"), -1.8262));
223        assert_eq!("01:00:00:00:00", get_formatted_date(format, parse_time("1970-12-28T00:08:50+00:00"), -1.8262)); // v0.2.1 panics
224        assert_eq!("01:00:01:49:35", get_formatted_date(format, parse_time("1970-12-29T12:00:00+00:00"), -1.8262)); // v0.2.1 panics
225        assert_eq!("01:00:02:49:32", get_formatted_date(format, parse_time("1970-12-30T12:00:00+00:00"), -1.8262)); // v0.2.1 panics
226        assert_eq!("01:00:03:99:28", get_formatted_date(format, parse_time("1970-12-31T23:59:59+00:00"), -1.8262)); // v0.2.1 panics
227        assert_eq!("01:00:03:99:29", get_formatted_date(format, parse_time("1971-01-01T00:00:00+00:00"), -1.8262));
228
229        // Negative time
230        let format = "%h:%u:%m:%d:%c:%b";
231        assert_eq!("-00:01:11:22:99:75", get_formatted_date(format, 0, 0.0)); // Unix Epoch
232        assert_eq!("-00:13:00:00:00:00", get_formatted_date(format, parse_time("1957-01-01T00:03:40+00:00"), 0.0));
233        assert_eq!("-00:70:00:00:00:00", get_formatted_date(format, parse_time("1900-01-01T00:03:40+00:00"), 0.0));
234        assert_eq!("-02:71:00:00:00:00", get_formatted_date(format, parse_time("1699-01-01T00:04:35+00:00"), 0.0));
235        assert_eq!("-03:28:00:00:00:00", get_formatted_date(format, parse_time("1642-01-01T00:04:40+00:00"), 0.0));
236        assert_eq!("-03:47:00:00:00:00", get_formatted_date(format, parse_time("1623-01-01T00:04:30+00:00"), 0.0));
237
238        let format = "%u:%m:%d:%c:%b";
239        assert_eq!("-01:11:22:99:75", get_formatted_date(format, 0, 0.0)); // Unix Epoch
240        assert_eq!("-13:00:00:00:00", get_formatted_date(format, parse_time("1957-01-01T00:03:40+00:00"), 0.0));
241        assert_eq!("-70:00:00:00:00", get_formatted_date(format, parse_time("1900-01-01T00:03:40+00:00"), 0.0));
242        assert_eq!("-71:00:00:00:00", get_formatted_date(format, parse_time("1699-01-01T00:04:35+00:00"), 0.0));
243        assert_eq!("-28:00:00:00:00", get_formatted_date(format, parse_time("1642-01-01T00:04:40+00:00"), 0.0));
244        assert_eq!("-47:00:00:00:00", get_formatted_date(format, parse_time("1623-01-01T00:04:30+00:00"), 0.0));
245
246        // Bug
247        assert_eq!("-30:11:28:99:99", get_formatted_date(format, parse_time("1940-12-28T00:01:39+00:00"), 0.0)); // Unix Epoch
248        assert_eq!("-29:00:00:00:00", get_formatted_date(format, parse_time("1940-12-28T00:01:40+00:00"), 0.0)); // Unix Epoch
249
250        // Bug with "50:08:28:100:00" at solar midnight
251        assert_eq!("50:08:27:99:99", get_formatted_date(format, parse_time("2020-09-15T23:55:01+00:00"), 0.0));
252        assert_eq!("50:08:28:00:00", get_formatted_date(format, parse_time("2020-09-15T23:55:02+00:00"), 0.0));
253        assert_eq!("50:08:28:00:00", get_formatted_date(format, parse_time("2020-09-15T23:55:03+00:00"), 0.0));
254    }
255}