From d6c0c640e64fceba6c280d76ee64d44831e9afef Mon Sep 17 00:00:00 2001 From: stuebinm Date: Tue, 1 Feb 2022 19:40:12 +0100 Subject: handle (esp. iceportal) timestamps correctly why use unix time if you can use unix time * 1000 while your margin of error will never be lower than a full minute? --- src/iceportal.rs | 16 +++++------ src/main.rs | 6 ++-- src/serde.rs | 21 ++++++++++---- src/types.rs | 86 +++++++++++++++++++++++++++----------------------------- 4 files changed, 68 insertions(+), 61 deletions(-) diff --git a/src/iceportal.rs b/src/iceportal.rs index a81d5d3..0d2076e 100644 --- a/src/iceportal.rs +++ b/src/iceportal.rs @@ -1,4 +1,4 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -48,10 +48,10 @@ struct Station { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct Timetable { - #[serde(deserialize_with = "option_naive_read_unixtime")] - scheduled_arrival_time: Option, - #[serde(deserialize_with = "option_naive_read_unixtime")] - actual_arrival_time: Option + #[serde(deserialize_with = "option_naive_read_unixtime_db")] + scheduled_arrival_time: Option>, + #[serde(deserialize_with = "option_naive_read_unixtime_db")] + actual_arrival_time: Option> } impl IsStation for Stop { @@ -59,12 +59,12 @@ impl IsStation for Stop { &self.station.name } - fn scheduled_arrival (&self) -> Option<&chrono::NaiveDateTime> { + fn scheduled_arrival (&self) -> Option<&chrono::DateTime> { self.timetable.scheduled_arrival_time.as_ref() } - fn real_arrival (&self) -> Option<&chrono::NaiveDateTime> { - self.timetable.actual_arrival_time.as_ref() + fn real_arrival (&self) -> Option<&chrono::DateTime> { + self.timetable.scheduled_arrival_time.as_ref() } fn ds100 (&self) -> &str { diff --git a/src/main.rs b/src/main.rs index cb9ac6c..6bac375 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,9 +159,9 @@ fn main() -> Result<(), ureq::Error> { Command::ICEPortal => { match get_request::("https://iceportal.de/api1/rs/tripInfo/trip") { Ok(resp) => { - println!("{}: Currently in {}", traveltext, resp.get_train_ref()); - println!("guessing last stop was: {:?}", resp.guess_last_station()); - println!("{}", resp.trip()) + println!("{}: Currently in {}\n", traveltext, resp.get_train_ref().to_string().green()); + println!("guessing last stop was: {:?}\n", resp.guess_last_station()); + println!("Stops:\n{}", resp.trip()) } Err(err) => { if cli.debug { diff --git a/src/serde.rs b/src/serde.rs index 9049d64..486929a 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,22 +1,33 @@ -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use serde::{Deserialize, Deserializer}; -pub fn naive_read_unixtime<'de, D>(d: D) -> Result +pub fn naive_read_unixtime<'de, D>(d: D) -> Result, D::Error> where D: Deserializer<'de>, { let ts = ::deserialize(d)?; - Ok(NaiveDateTime::from_timestamp(ts, 0)) + Ok(DateTime::::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc)) } -pub fn option_naive_read_unixtime<'de, D>(d: D) -> Result, D::Error> +pub fn option_naive_read_unixtime<'de, D>(d: D) -> Result>, D::Error> where D: Deserializer<'de>, { match ::deserialize(d) { Ok(ts) => - Ok(Some(NaiveDateTime::from_timestamp(ts, 0))), + Ok(Some(DateTime::::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc))), + Err(_) => Ok(None) + } +} + +pub fn option_naive_read_unixtime_db<'de, D>(d: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + match ::deserialize(d) { + Ok(ts) => + Ok(Some(DateTime::::from_utc(NaiveDateTime::from_timestamp(ts/1000, 0), Utc))), Err(_) => Ok(None) } } diff --git a/src/types.rs b/src/types.rs index 2d8d189..2e4d2e1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Deserializer}; -use chrono::NaiveDateTime; +use chrono::{DateTime, Local, NaiveDateTime, Utc}; use colored::*; use crate::serde::*; @@ -14,55 +14,53 @@ pub struct Station { latitude: f64, longitude: f64, #[serde(deserialize_with = "naive_read_unixtime")] - scheduled_time: NaiveDateTime, + scheduled_time: DateTime, #[serde(deserialize_with = "naive_read_unixtime")] - real_time: NaiveDateTime, + real_time: DateTime, } - pub fn parse_optional_station<'de, D>(d: D) -> Result, D::Error> where D: Deserializer<'de>, { let val = ::deserialize(d)?; - match serde_json::from_value(val) { - Ok(station) => Ok(Some(station)), - Err(_) => Ok(None) - } + match serde_json::from_value(val) { + Ok(station) => Ok(Some(station)), + Err(_) => Ok(None), + } } - - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Stop { name: String, #[serde(deserialize_with = "option_naive_read_unixtime")] - scheduled_arrival: Option, + scheduled_arrival: Option>, #[serde(deserialize_with = "option_naive_read_unixtime")] - real_arrival: Option, + real_arrival: Option>, #[serde(deserialize_with = "option_naive_read_unixtime")] - scheduled_departure: Option, + scheduled_departure: Option>, #[serde(deserialize_with = "option_naive_read_unixtime")] - real_departure: Option, + real_departure: Option>, } - - pub trait IsStation { - fn name (&self) -> &str; - fn scheduled_arrival (&self) -> Option<&NaiveDateTime>; - fn real_arrival (&self) -> Option<&NaiveDateTime>; - fn ds100 (&self) -> &str; + fn name(&self) -> &str; + fn scheduled_arrival(&self) -> Option<&DateTime>; + fn real_arrival(&self) -> Option<&DateTime>; + fn ds100(&self) -> &str; - fn to_fancy_string (&self) -> String { + fn to_fancy_string(&self) -> String { format!( "{} {} – {} ({})", - self.real_arrival().map(|t| t.time().to_string()).unwrap_or("??:??:??".to_string()).blue(), + self.real_arrival() // chrono's API for timezones is expressive, but reads like c++ … + .map(|t| >::from(*t).time().to_string()) + .unwrap_or("??:??:??".to_string()) + .blue(), { let delay = match (self.real_arrival(), self.scheduled_arrival()) { (Some(a), Some(s)) => (a.time() - s.time()).num_minutes(), - _ => 0 + _ => 0, }; let text = format!("({:+})", delay); if delay > 0 { @@ -78,40 +76,37 @@ pub trait IsStation { } impl IsStation for Station { - fn name (&self) -> &str { + fn name(&self) -> &str { &self.name } - fn scheduled_arrival (&self) -> Option<&NaiveDateTime> { + fn scheduled_arrival(&self) -> Option<&DateTime> { Some(&self.scheduled_time) } - fn real_arrival (&self) -> Option<&NaiveDateTime> { + fn real_arrival(&self) -> Option<&DateTime> { Some(&self.real_time) } - fn ds100 (&self) -> &str { + fn ds100(&self) -> &str { &self.ds100 } } impl IsStation for Stop { - fn name (&self) -> &str { + fn name(&self) -> &str { &self.name } - fn scheduled_arrival (&self) -> Option<&NaiveDateTime> { + fn scheduled_arrival(&self) -> Option<&DateTime> { self.scheduled_arrival.as_ref() } - fn real_arrival (&self) -> Option<&NaiveDateTime> { + fn real_arrival(&self) -> Option<&DateTime> { self.real_arrival.as_ref() } - fn ds100 (&self) -> &str { + fn ds100(&self) -> &str { "[??]" } } - - - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Train { @@ -122,8 +117,6 @@ pub struct Train { id: String, } - - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Status { @@ -142,8 +135,7 @@ pub struct Ds100 { inner: String, } -pub struct Trip<'a, S: IsStation> (pub &'a Vec); - +pub struct Trip<'a, S: IsStation>(pub &'a Vec); impl std::fmt::Display for Train { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -155,16 +147,16 @@ impl std::fmt::Display for Train { impl std::fmt::Display for Trip<'_, S> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.0.len() != 0 { - self.0.iter() - .map(|stop| stop.to_fancy_string()) - // .intersperse(" ↓".to_string()) - .for_each(|l| writeln!(f, " {}\n ↓", l).unwrap()); + self.0 + .iter() + .map(|stop| stop.to_fancy_string()) + // .intersperse(" ↓".to_string()) + .for_each(|l| writeln!(f, " {}\n ↓", l).unwrap()); } Ok(()) } } - impl std::fmt::Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.checked_in { @@ -179,14 +171,18 @@ impl std::fmt::Display for Status { f, "checked in to: {}.\n\n\ stops:\n {}\n ↓\n{} {}", - self.train.as_ref().map(|t| t.to_string()).unwrap_or("".to_string()).green(), + self.train + .as_ref() + .map(|t| t.to_string()) + .unwrap_or("".to_string()) + .green(), self.from_station.to_fancy_string(), Trip(&self.intermediate_stops), self.to_station .as_ref() .map(|s| s.to_fancy_string()) .unwrap_or_else(|| "🚄 Fahrt ins Blaue".blue().to_string()) - ) + ), } } } -- cgit v1.2.3