monorepo/dashboard/src/soluna_client.rs

152 lines
5.2 KiB
Rust

// 41.78, -71.41
// https://api.solunar.org/solunar/41.78,-71.41,20211029,-4
use chrono::{Date, DateTime, Duration, NaiveTime, Offset, Timelike, Utc};
use chrono_tz::Tz;
use geo_types::{Latitude, Longitude};
use memorycache::MemoryCache;
use reqwest;
const ENDPOINT: &str = "https://api.solunar.org/solunar";
#[derive(Clone, Debug, PartialEq)]
pub struct SunMoon {
pub sunleviĝo: DateTime<Tz>,
pub sunfalo: DateTime<Tz>,
pub lunleviĝo: Option<DateTime<Tz>>,
pub lunfalo: Option<DateTime<Tz>>,
pub lunfazo: LunarPhase,
}
impl SunMoon {
fn from_js(day: Date<Tz>, val: SunMoonJs) -> Self {
fn new_time(day: Date<Tz>, val: String) -> Option<DateTime<Tz>> {
NaiveTime::parse_from_str(&val, "%H:%M")
.map(|time| day.and_hms(time.hour(), time.minute(), 0))
.ok()
}
let sunleviĝo = new_time(day.clone(), val.sunleviĝo).unwrap();
let sunfalo = new_time(day.clone(), val.sunfalo).unwrap();
let lunleviĝo = val.lunleviĝo.and_then(|v| new_time(day.clone(), v));
let lunfalo = val.lunfalo.and_then(|v| new_time(day.clone(), v));
Self {
sunleviĝo,
sunfalo,
lunleviĝo,
lunfalo,
lunfazo: val.lunfazo,
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct SunMoonJs {
#[serde(alias = "sunRise")]
sunleviĝo: String,
#[serde(alias = "sunSet")]
sunfalo: String,
#[serde(alias = "moonRise")]
lunleviĝo: Option<String>,
#[serde(alias = "moonSet")]
lunfalo: Option<String>,
#[serde(alias = "moonPhase")]
lunfazo: 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::new(),
}
}
pub async fn request(
&self,
latitude: Latitude,
longitude: Longitude,
day: Date<Tz>,
) -> SunMoon {
let date = day.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(|dt_local| DateTime::<Utc>::from(dt_local))
.unwrap_or(Utc::now() + Duration::seconds(3600));
let soluna: SunMoonJs = response.json().await.unwrap();
(expiration, soluna)
})
.await;
SunMoon::from_js(day, js)
}
}
#[cfg(test)]
mod test {
use super::*;
use chrono::TimeZone;
use chrono_tz::America::New_York;
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 day = New_York.ymd(2021, 10, 29);
let sun_moon_js: SunMoonJs = serde_json::from_str(EXAMPLE).unwrap();
let sun_moon = SunMoon::from_js(day.clone(), sun_moon_js);
assert_eq!(
sun_moon,
SunMoon {
sunleviĝo: day.and_hms(7, 15, 0),
sunfalo: day.and_hms(17, 45, 0),
lunleviĝo: None,
lunfalo: Some(day.and_hms(15, 02, 0)),
lunfazo: LunarPhase::WaningCrescent,
}
);
}
}