From 7432fb3cd3f58ab41ba1e67dcb5f8bb88feccf08 Mon Sep 17 00:00:00 2001 From: Paul van Tilburg Date: Sun, 20 Feb 2022 17:39:49 +0100 Subject: [PATCH] Fix Buienradar timestamp madness (closes: #3) * Use the `Europe::Amsterdam` time zone from `chrono_tz` to determine what date/time it is in that time zone * Parse timestamps in the rain text API relative to this date/time * Add the `fix_items_day_boundary` function to fix up stuff if the series of timestamps in the rain text cross the day boundary --- src/providers/buienradar.rs | 68 ++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/src/providers/buienradar.rs b/src/providers/buienradar.rs index 81614cb..4760558 100644 --- a/src/providers/buienradar.rs +++ b/src/providers/buienradar.rs @@ -6,7 +6,7 @@ use cached::proc_macro::cached; use chrono::offset::TimeZone; use chrono::serde::ts_seconds; -use chrono::{DateTime, Local, NaiveTime, ParseError, Utc}; +use chrono::{DateTime, Datelike, Duration, NaiveTime, ParseError, Utc}; use chrono_tz::Europe; use csv::ReaderBuilder; use reqwest::Url; @@ -65,13 +65,13 @@ impl TryFrom for Item { /// The provided time has the format `HH:MM` and is considered to be in the Europe/Amsterdam /// time zone. fn parse_time(t: &str) -> Result, ParseError> { - // First, get the naive time. + // First, get the current date in the Europe/Amsterdam time zone. + let today = Utc::now().with_timezone(&Europe::Amsterdam).date(); + // Then, parse the time and interpret it relative to "today". let ntime = NaiveTime::parse_from_str(t, "%H:%M")?; - // FIXME: This might actually be the day before when started on a machine that - // doesn't run in the Europe/Amsterdam time zone. - let ndtime = Local::today().naive_local().and_time(ntime); - // Then, interpret the naive date/time in the Europe/Amsterdam time zone and convert it to the - // UTC time zone. + let ndtime = today.naive_local().and_time(ntime); + // Finally, interpret the naive date/time in the Europe/Amsterdam time zone and convert it to + // the UTC time zone. let ldtime = Europe::Amsterdam.from_local_datetime(&ndtime).unwrap(); let dtime = ldtime.with_timezone(&Utc); @@ -88,6 +88,44 @@ fn convert_value(v: u16) -> f32 { (value * 10.0).round() / 10.0 } +/// Fix the timestamps of the items either before or after the day boundary. +/// +/// If in the Europe/Amsterdam time zone it is still before 0:00, all timestamps after 0:00 need to +/// be bumped up with a day. If it is already after 0:00, all timestamps before 0:00 need to be +/// bumped back with a day. +// TODO: If something in Sinoptik needs unit tests, it is this! +fn fix_items_day_boundary(items: Vec) -> Vec { + let now = Utc::now().with_timezone(&Europe::Amsterdam); + // Use noon on the same day as "now" as a comparison moment. + let noon = Europe::Amsterdam + .ymd(now.year(), now.month(), now.day()) + .and_hms(12, 0, 0); + + if now < noon { + // It is still before noon, so bump timestamps after noon a day back. + items + .into_iter() + .map(|mut item| { + if item.time > noon { + item.time = item.time - Duration::days(1) + } + item + }) + .collect() + } else { + // It is already after noon, so bump the timestamps before noon a day forward. + items + .into_iter() + .map(|mut item| { + if item.time < noon { + item.time = item.time + Duration::days(1) + } + item + }) + .collect() + } +} + /// Retrieves the Buienradar forecasted precipitation items for the provided position. /// /// Returns [`None`] if retrieval or deserialization fails. @@ -111,7 +149,21 @@ async fn get_precipitation(position: Position) -> Option> { .has_headers(false) .delimiter(b'|') .from_reader(output.as_bytes()); - rdr.deserialize().collect::>().ok() + let items: Vec = rdr.deserialize().collect::>().ok()?; + + // Check if the first item stamp is (timewise) later than the last item stamp. + // In this case `parse_time` interpreted e.g. 23:00 and later 0:30 in the same day and some + // time stamps need to be fixed. + if items + .first() + .zip(items.last()) + .map(|(it1, it2)| it1.time > it2.time) + == Some(true) + { + Some(fix_items_day_boundary(items)) + } else { + Some(items) + } } /// Retrieves the Buienradar forecasted pollen samples for the provided position.