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
This commit is contained in:
Paul van Tilburg 2022-02-20 17:39:49 +01:00
parent 17d5daeabc
commit 7432fb3cd3
1 changed files with 60 additions and 8 deletions

View File

@ -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<Row> 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<DateTime<Utc>, 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<Item>) -> Vec<Item> {
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<Vec<Item>> {
.has_headers(false)
.delimiter(b'|')
.from_reader(output.as_bytes());
rdr.deserialize().collect::<Result<_, _>>().ok()
let items: Vec<Item> = rdr.deserialize().collect::<Result<_, _>>().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.