155 lines
5.4 KiB
Rust
155 lines
5.4 KiB
Rust
// 41.78, -71.41
|
|
// https://api.solunar.org/solunar/41.78,-71.41,20211029,-4
|
|
|
|
use chrono::{DateTime, Duration, Local, NaiveTime, Offset, TimeZone, Timelike, Utc};
|
|
use geo_types::{Latitude, Longitude};
|
|
use memorycache::MemoryCache;
|
|
use serde::Deserialize;
|
|
|
|
const ENDPOINT: &str = "https://api.solunar.org/solunar";
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SunMoon {
|
|
pub sunrise: NaiveTime,
|
|
pub sunset: NaiveTime,
|
|
pub moonrise: Option<NaiveTime>,
|
|
pub moonset: Option<NaiveTime>,
|
|
pub moon_phase: LunarPhase,
|
|
}
|
|
|
|
impl SunMoon {
|
|
fn from_js(val: SunMoonJs) -> Self {
|
|
fn parse_time(val: String) -> Option<NaiveTime> {
|
|
NaiveTime::parse_from_str(&val, "%H:%M").ok()
|
|
}
|
|
|
|
let sunrise = parse_time(val.sunrise).unwrap();
|
|
let sunset = parse_time(val.sunset).unwrap();
|
|
let moonrise = val.moonrise.and_then(parse_time);
|
|
let moonset = val.moonset.and_then(parse_time);
|
|
|
|
Self {
|
|
sunrise,
|
|
sunset,
|
|
moonrise,
|
|
moonset,
|
|
moon_phase: val.moon_phase,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
pub(crate) struct SunMoonJs {
|
|
#[serde(alias = "sunRise")]
|
|
sunrise: String,
|
|
#[serde(alias = "sunSet")]
|
|
sunset: String,
|
|
#[serde(alias = "moonRise")]
|
|
moonrise: Option<String>,
|
|
#[serde(alias = "moonSet")]
|
|
moonset: Option<String>,
|
|
#[serde(alias = "moonPhase")]
|
|
moon_phase: LunarPhase,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
|
pub enum LunarPhase {
|
|
#[serde(alias = "New Moon")]
|
|
NewMoon,
|
|
#[serde(alias = "Waxing Crescent")]
|
|
WaxingCrescent,
|
|
#[serde(alias = "First Quarter")]
|
|
FirstQuarter,
|
|
#[serde(alias = "Waxing Gibbous")]
|
|
WaxingGibbous,
|
|
#[serde(alias = "Full Moon")]
|
|
FullMoon,
|
|
#[serde(alias = "Waning Gibbous")]
|
|
WaningGibbous,
|
|
#[serde(alias = "Last Quarter")]
|
|
LastQuarter,
|
|
#[serde(alias = "Waning Crescent")]
|
|
WaningCrescent,
|
|
}
|
|
|
|
pub struct SolunaClient {
|
|
client: reqwest::Client,
|
|
memory_cache: MemoryCache<SunMoonJs>,
|
|
}
|
|
|
|
impl SolunaClient {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
client: reqwest::Client::new(),
|
|
memory_cache: MemoryCache::default(),
|
|
}
|
|
}
|
|
|
|
pub async fn request<Tz: TimeZone>(
|
|
&self,
|
|
latitude: Latitude,
|
|
longitude: Longitude,
|
|
day: DateTime<Tz>,
|
|
) -> SunMoon {
|
|
let date = day.date_naive().format("%Y%m%d");
|
|
let url = format!(
|
|
"{}/{},{},{},{}",
|
|
ENDPOINT,
|
|
latitude,
|
|
longitude,
|
|
date,
|
|
day.offset().fix().local_minus_utc() / 3600
|
|
);
|
|
let js = self
|
|
.memory_cache
|
|
.find(&url, async {
|
|
let response = self.client.get(&url).send().await.unwrap();
|
|
let expiration = response
|
|
.headers()
|
|
.get(reqwest::header::EXPIRES)
|
|
.and_then(|header| header.to_str().ok())
|
|
.and_then(|expiration| DateTime::parse_from_rfc2822(expiration).ok())
|
|
.map(DateTime::<Utc>::from)
|
|
.unwrap_or(
|
|
Local::now()
|
|
.with_hour(0)
|
|
.and_then(|dt| dt.with_minute(0))
|
|
.and_then(|dt| dt.with_second(0))
|
|
.and_then(|dt| dt.with_nanosecond(0))
|
|
.map(|dt| dt.with_timezone(&Utc))
|
|
.unwrap()
|
|
+ Duration::days(1),
|
|
);
|
|
let soluna: SunMoonJs = response.json().await.unwrap();
|
|
(expiration, soluna)
|
|
})
|
|
.await;
|
|
|
|
SunMoon::from_js(js)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use serde_json;
|
|
|
|
const EXAMPLE: &str = "{\"sunRise\":\"7:15\",\"sunTransit\":\"12:30\",\"sunSet\":\"17:45\",\"moonRise\":null,\"moonTransit\":\"7:30\",\"moonUnder\":\"19:54\",\"moonSet\":\"15:02\",\"moonPhase\":\"Waning Crescent\",\"moonIllumination\":0.35889454647387764,\"sunRiseDec\":7.25,\"sunTransitDec\":12.5,\"sunSetDec\":17.75,\"moonRiseDec\":null,\"moonSetDec\":15.033333333333333,\"moonTransitDec\":7.5,\"moonUnderDec\":19.9,\"minor1Start\":null,\"minor1Stop\":null,\"minor2StartDec\":14.533333333333333,\"minor2Start\":\"14:32\",\"minor2StopDec\":15.533333333333333,\"minor2Stop\":\"15:32\",\"major1StartDec\":6.5,\"major1Start\":\"06:30\",\"major1StopDec\":8.5,\"major1Stop\":\"08:30\",\"major2StartDec\":18.9,\"major2Start\":\"18:54\",\"major2StopDec\":20.9,\"major2Stop\":\"20:54\",\"dayRating\":1,\"hourlyRating\":{\"0\":20,\"1\":20,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":20,\"7\":40,\"8\":40,\"9\":20,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":20,\"16\":20,\"17\":20,\"18\":40,\"19\":20,\"20\":20,\"21\":20,\"22\":0,\"23\":0}}";
|
|
|
|
#[test]
|
|
fn it_parses_a_response() {
|
|
let sun_moon_js: SunMoonJs = serde_json::from_str(EXAMPLE).unwrap();
|
|
let sun_moon = SunMoon::from_js(sun_moon_js);
|
|
assert_eq!(
|
|
sun_moon,
|
|
SunMoon {
|
|
sunrise: NaiveTime::from_hms_opt(7, 15, 0).unwrap(),
|
|
sunset: NaiveTime::from_hms_opt(17, 45, 0).unwrap(),
|
|
moonrise: None,
|
|
moonset: Some(NaiveTime::from_hms_opt(15, 02, 0).unwrap()),
|
|
moon_phase: LunarPhase::WaningCrescent,
|
|
}
|
|
);
|
|
}
|
|
}
|