2023-07-28 18:42:25 +00:00
|
|
|
use chrono::prelude::*;
|
2023-08-09 17:54:56 +00:00
|
|
|
use lazy_static::lazy_static;
|
2023-07-28 18:42:25 +00:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
// http://astropixels.com/ephemeris/soleq2001.html
|
2023-10-04 21:28:55 +00:00
|
|
|
const SOLSTICE_TEXT: &str = "
|
2023-07-28 18:42:25 +00:00
|
|
|
2001 Mar 20 13:31 Jun 21 07:38 Sep 22 23:05 Dec 21 19:22
|
|
|
|
2002 Mar 20 19:16 Jun 21 13:25 Sep 23 04:56 Dec 22 01:15
|
|
|
|
2003 Mar 21 01:00 Jun 21 19:11 Sep 23 10:47 Dec 22 07:04
|
|
|
|
2004 Mar 20 06:49 Jun 21 00:57 Sep 22 16:30 Dec 21 12:42
|
|
|
|
2005 Mar 20 12:34 Jun 21 06:46 Sep 22 22:23 Dec 21 18:35
|
|
|
|
2006 Mar 20 18:25 Jun 21 12:26 Sep 23 04:04 Dec 22 00:22
|
|
|
|
2007 Mar 21 00:07 Jun 21 18:06 Sep 23 09:51 Dec 22 06:08
|
|
|
|
2008 Mar 20 05:49 Jun 21 00:00 Sep 22 15:45 Dec 21 12:04
|
|
|
|
2009 Mar 20 11:44 Jun 21 05:45 Sep 22 21:18 Dec 21 17:47
|
|
|
|
2010 Mar 20 17:32 Jun 21 11:28 Sep 23 03:09 Dec 21 23:38
|
|
|
|
|
|
|
|
2011 Mar 20 23:21 Jun 21 17:16 Sep 23 09:05 Dec 22 05:30
|
|
|
|
2012 Mar 20 05:15 Jun 20 23:08 Sep 22 14:49 Dec 21 11:12
|
|
|
|
2013 Mar 20 11:02 Jun 21 05:04 Sep 22 20:44 Dec 21 17:11
|
|
|
|
2014 Mar 20 16:57 Jun 21 10:52 Sep 23 02:30 Dec 21 23:03
|
|
|
|
2015 Mar 20 22:45 Jun 21 16:38 Sep 23 08:20 Dec 22 04:48
|
|
|
|
2016 Mar 20 04:31 Jun 20 22:35 Sep 22 14:21 Dec 21 10:45
|
|
|
|
2017 Mar 20 10:29 Jun 21 04:25 Sep 22 20:02 Dec 21 16:29
|
|
|
|
2018 Mar 20 16:15 Jun 21 10:07 Sep 23 01:54 Dec 21 22:22
|
|
|
|
2019 Mar 20 21:58 Jun 21 15:54 Sep 23 07:50 Dec 22 04:19
|
|
|
|
2020 Mar 20 03:50 Jun 20 21:43 Sep 22 13:31 Dec 21 10:03
|
|
|
|
|
|
|
|
2021 Mar 20 09:37 Jun 21 03:32 Sep 22 19:21 Dec 21 15:59
|
|
|
|
2022 Mar 20 15:33 Jun 21 09:14 Sep 23 01:04 Dec 21 21:48
|
|
|
|
2023 Mar 20 21:25 Jun 21 14:58 Sep 23 06:50 Dec 22 03:28
|
|
|
|
2024 Mar 20 03:07 Jun 20 20:51 Sep 22 12:44 Dec 21 09:20
|
|
|
|
2025 Mar 20 09:02 Jun 21 02:42 Sep 22 18:20 Dec 21 15:03
|
|
|
|
2026 Mar 20 14:46 Jun 21 08:25 Sep 23 00:06 Dec 21 20:50
|
|
|
|
2027 Mar 20 20:25 Jun 21 14:11 Sep 23 06:02 Dec 22 02:43
|
|
|
|
2028 Mar 20 02:17 Jun 20 20:02 Sep 22 11:45 Dec 21 08:20
|
|
|
|
2029 Mar 20 08:01 Jun 21 01:48 Sep 22 17:37 Dec 21 14:14
|
|
|
|
2030 Mar 20 13:51 Jun 21 07:31 Sep 22 23:27 Dec 21 20:09
|
|
|
|
|
|
|
|
2031 Mar 20 19:41 Jun 21 13:17 Sep 23 05:15 Dec 22 01:56
|
|
|
|
2032 Mar 20 01:23 Jun 20 19:09 Sep 22 11:11 Dec 21 07:57
|
|
|
|
2033 Mar 20 07:23 Jun 21 01:01 Sep 22 16:52 Dec 21 13:45
|
|
|
|
2034 Mar 20 13:18 Jun 21 06:45 Sep 22 22:41 Dec 21 19:35
|
|
|
|
2035 Mar 20 19:03 Jun 21 12:33 Sep 23 04:39 Dec 22 01:31
|
|
|
|
2036 Mar 20 01:02 Jun 20 18:31 Sep 22 10:23 Dec 21 07:12
|
|
|
|
2037 Mar 20 06:50 Jun 21 00:22 Sep 22 16:13 Dec 21 13:08
|
|
|
|
2038 Mar 20 12:40 Jun 21 06:09 Sep 22 22:02 Dec 21 19:01
|
|
|
|
2039 Mar 20 18:32 Jun 21 11:58 Sep 23 03:50 Dec 22 00:41
|
|
|
|
2040 Mar 20 00:11 Jun 20 17:46 Sep 22 09:44 Dec 21 06:33
|
|
|
|
|
|
|
|
2041 Mar 20 06:07 Jun 20 23:37 Sep 22 15:27 Dec 21 12:19
|
|
|
|
2042 Mar 20 11:53 Jun 21 05:16 Sep 22 21:11 Dec 21 18:04
|
|
|
|
2043 Mar 20 17:29 Jun 21 10:59 Sep 23 03:07 Dec 22 00:02
|
|
|
|
2044 Mar 19 23:20 Jun 20 16:50 Sep 22 08:47 Dec 21 05:43
|
|
|
|
2045 Mar 20 05:08 Jun 20 22:34 Sep 22 14:33 Dec 21 11:36
|
|
|
|
2046 Mar 20 10:58 Jun 21 04:15 Sep 22 20:22 Dec 21 17:28
|
|
|
|
2047 Mar 20 16:52 Jun 21 10:02 Sep 23 02:07 Dec 21 23:07
|
|
|
|
2048 Mar 19 22:34 Jun 20 15:54 Sep 22 08:01 Dec 21 05:02
|
|
|
|
2049 Mar 20 04:28 Jun 20 21:47 Sep 22 13:42 Dec 21 10:51
|
|
|
|
2050 Mar 20 10:20 Jun 21 03:33 Sep 22 19:29 Dec 21 16:39
|
|
|
|
";
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
|
|
pub struct YearlyEvents {
|
|
|
|
pub year: i32,
|
|
|
|
pub spring_equinox: chrono::DateTime<chrono::Utc>,
|
|
|
|
pub summer_solstice: chrono::DateTime<chrono::Utc>,
|
|
|
|
pub autumn_equinox: chrono::DateTime<chrono::Utc>,
|
|
|
|
pub winter_solstice: chrono::DateTime<chrono::Utc>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
|
|
pub enum Event {
|
|
|
|
SpringEquinox(chrono::DateTime<chrono::Utc>),
|
|
|
|
SummerSolstice(chrono::DateTime<chrono::Utc>),
|
|
|
|
AutumnEquinox(chrono::DateTime<chrono::Utc>),
|
|
|
|
WinterSolstice(chrono::DateTime<chrono::Utc>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Event {
|
|
|
|
pub fn date(&self) -> chrono::DateTime<chrono::Utc> {
|
|
|
|
match *self {
|
|
|
|
Event::SpringEquinox(d) => d,
|
|
|
|
Event::SummerSolstice(d) => d,
|
|
|
|
Event::AutumnEquinox(d) => d,
|
|
|
|
Event::WinterSolstice(d) => d,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_time<'a>(
|
2023-10-04 21:28:55 +00:00
|
|
|
year: &str,
|
2023-07-28 18:42:25 +00:00
|
|
|
iter: impl Iterator<Item = &'a str>,
|
|
|
|
) -> chrono::DateTime<chrono::Utc> {
|
2023-10-04 21:28:55 +00:00
|
|
|
let parts = iter.collect::<Vec<&str>>();
|
|
|
|
let p = format!("{} {} {} {}", year, parts[0], parts[1], parts[2]);
|
|
|
|
NaiveDateTime::parse_from_str(&p, "%Y %b %d %H:%M")
|
|
|
|
.unwrap()
|
|
|
|
.and_utc()
|
2023-07-28 18:42:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_line(year: &str, rest: &[&str]) -> YearlyEvents {
|
|
|
|
let spring = parse_time(year, rest.iter().take(3).cloned());
|
|
|
|
let summer = parse_time(year, rest.iter().skip(3).take(3).cloned());
|
|
|
|
let autumn = parse_time(year, rest.iter().skip(6).take(3).cloned());
|
|
|
|
let winter = parse_time(year, rest.iter().skip(9).take(3).cloned());
|
|
|
|
YearlyEvents {
|
|
|
|
year: year.parse::<i32>().unwrap(),
|
|
|
|
spring_equinox: spring,
|
|
|
|
summer_solstice: summer,
|
|
|
|
autumn_equinox: autumn,
|
|
|
|
winter_solstice: winter,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_events() -> Vec<Option<YearlyEvents>> {
|
|
|
|
SOLSTICE_TEXT
|
|
|
|
.lines()
|
|
|
|
.map(|line| {
|
|
|
|
match line
|
2023-10-04 21:28:55 +00:00
|
|
|
.split(' ')
|
2023-07-28 18:42:25 +00:00
|
|
|
.filter(|elem| !elem.is_empty())
|
|
|
|
.collect::<Vec<&str>>()
|
|
|
|
.as_slice()
|
|
|
|
{
|
|
|
|
[year, rest @ ..] => Some(parse_line(year, rest)),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Solstices(HashMap<i32, YearlyEvents>);
|
|
|
|
|
|
|
|
impl Solstices {
|
2023-08-10 16:35:48 +00:00
|
|
|
pub fn yearly_events(&self, year: i32) -> Option<YearlyEvents> {
|
2023-10-04 21:28:55 +00:00
|
|
|
self.0.get(&year).copied()
|
2023-07-28 18:42:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn next_event(&self, date: chrono::DateTime<chrono::Utc>) -> Option<Event> {
|
|
|
|
let year_events = self.0.get(&date.year());
|
|
|
|
match year_events {
|
|
|
|
Some(year_events) => {
|
|
|
|
if date <= year_events.spring_equinox {
|
2023-10-04 21:28:55 +00:00
|
|
|
Some(Event::SpringEquinox(year_events.spring_equinox))
|
2023-07-28 18:42:25 +00:00
|
|
|
} else if date <= year_events.summer_solstice {
|
2023-10-04 21:28:55 +00:00
|
|
|
Some(Event::SummerSolstice(year_events.summer_solstice))
|
2023-07-28 18:42:25 +00:00
|
|
|
} else if date <= year_events.autumn_equinox {
|
2023-10-04 21:28:55 +00:00
|
|
|
Some(Event::AutumnEquinox(year_events.autumn_equinox))
|
2023-07-28 18:42:25 +00:00
|
|
|
} else if date <= year_events.winter_solstice {
|
2023-10-04 21:28:55 +00:00
|
|
|
Some(Event::WinterSolstice(year_events.winter_solstice))
|
2023-07-28 18:42:25 +00:00
|
|
|
} else {
|
|
|
|
self.0
|
|
|
|
.get(&(date.year() + 1))
|
2023-10-04 21:28:55 +00:00
|
|
|
.map(|_| Event::SpringEquinox(year_events.spring_equinox))
|
2023-07-28 18:42:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Vec<Option<YearlyEvents>>> for Solstices {
|
|
|
|
fn from(event_list: Vec<Option<YearlyEvents>>) -> Self {
|
|
|
|
Solstices(event_list.iter().fold(HashMap::new(), |mut m, record| {
|
|
|
|
match record {
|
|
|
|
Some(record) => {
|
2023-10-04 21:28:55 +00:00
|
|
|
m.insert(record.year, *record);
|
2023-07-28 18:42:25 +00:00
|
|
|
}
|
|
|
|
None => (),
|
|
|
|
}
|
|
|
|
m
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
pub static ref EVENTS: Solstices = Solstices::from(parse_events());
|
|
|
|
}
|
2023-10-04 21:28:55 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use chrono::{NaiveDate, NaiveDateTime};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn it_can_parse_a_solstice_time() {
|
|
|
|
let p = "2001 Mar 20 13:31".to_owned();
|
|
|
|
let parsed_date = NaiveDateTime::parse_from_str(&p, "%Y %b %d %H:%M")
|
|
|
|
.unwrap()
|
|
|
|
.and_utc();
|
|
|
|
assert_eq!(
|
|
|
|
parsed_date,
|
|
|
|
NaiveDate::from_ymd_opt(2001, 03, 20)
|
|
|
|
.unwrap()
|
|
|
|
.and_hms_opt(13, 31, 0)
|
|
|
|
.unwrap()
|
|
|
|
.and_utc()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|