152 lines
5.2 KiB
Rust
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,
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|