[#14] BasicDate now relies on DateTime for its parsing
parent
5dd2b3a878
commit
dd1efaa34d
|
@ -174,6 +174,16 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2554a3155fec064362507487171dcc4edc3df60cb10f3a1fb10ed8094822b120"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"parse-zoneinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.2.5"
|
||||
|
@ -340,6 +350,7 @@ name = "fej"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rocket",
|
||||
|
@ -1010,6 +1021,15 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pear"
|
||||
version = "0.1.4"
|
||||
|
|
|
@ -10,6 +10,7 @@ edition = "2018"
|
|||
rocket = "0.4.7"
|
||||
serde = "1.0.124"
|
||||
chrono = "0.4.19"
|
||||
chrono-tz = "0.5.3"
|
||||
regex = "1.4.5"
|
||||
|
||||
[dependencies.reqwest]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// I can probably do this way easier using an external crate, I should do that
|
||||
use rocket::http::Status;
|
||||
|
||||
pub enum FejError {
|
||||
|
@ -20,3 +21,9 @@ impl From<reqwest::Error> for FejError {
|
|||
FejError::FailedRequest
|
||||
}
|
||||
}
|
||||
|
||||
impl From<chrono::ParseError> for FejError {
|
||||
fn from(_: chrono::ParseError) -> FejError {
|
||||
FejError::InvalidArgument
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use super::structs::{BasicDate, PickupTime, Street};
|
||||
use crate::errors::FejError;
|
||||
use chrono::DateTime;
|
||||
use chrono_tz::Tz;
|
||||
use reqwest::blocking as reqwest;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{From, TryFrom};
|
||||
|
@ -8,10 +10,10 @@ const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling";
|
|||
const CAL_URL: &str = "https://www.ivago.be/nl/particulier/garbage/pick-up/pickups";
|
||||
|
||||
pub fn get_pickup_times(
|
||||
street: Street,
|
||||
number: u32,
|
||||
start_date: BasicDate,
|
||||
end_date: BasicDate,
|
||||
street: &Street,
|
||||
number: &u32,
|
||||
start_date: &DateTime<Tz>,
|
||||
end_date: &DateTime<Tz>,
|
||||
) -> Result<Vec<PickupTime>, FejError> {
|
||||
let client = reqwest::Client::builder().cookie_store(true).build()?;
|
||||
|
||||
|
@ -31,8 +33,8 @@ pub fn get_pickup_times(
|
|||
.query(&[
|
||||
("_format", "json"),
|
||||
("type", ""),
|
||||
("start", &start_date.epoch().to_string()),
|
||||
("end", &end_date.epoch().to_string()),
|
||||
("start", &start_date.timestamp().to_string()),
|
||||
("end", &end_date.timestamp().to_string()),
|
||||
])
|
||||
.send()?;
|
||||
let data: Vec<HashMap<String, String>> = response.json()?;
|
||||
|
@ -41,8 +43,9 @@ pub fn get_pickup_times(
|
|||
|
||||
for map in data.iter() {
|
||||
output.push(PickupTime::new(
|
||||
// TODO should I check here if the parsing worked?
|
||||
BasicDate::try_from(map.get("date").unwrap().as_str()).unwrap(),
|
||||
// TODO it's really not logical here that this would return an
|
||||
// "InvalidArgument" error, it should just skip the item
|
||||
BasicDate::try_from(map.get("date").unwrap().as_str())?,
|
||||
map.get("label").unwrap().to_string(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,33 +1,16 @@
|
|||
use chrono::{FixedOffset, TimeZone};
|
||||
use regex::Regex;
|
||||
use crate::errors::FejError;
|
||||
use chrono::{DateTime, NaiveDate, TimeZone};
|
||||
use chrono_tz::Europe::Brussels;
|
||||
use chrono_tz::Tz;
|
||||
use rocket::http::RawStr;
|
||||
use rocket::request::FromFormValue;
|
||||
use serde::ser::Serializer;
|
||||
use serde::Serialize;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// Represents a very simple Timezoneless date. Considering the timezone will
|
||||
/// always be CEST (aka Belgium's timezone), this is good enough. I use this
|
||||
/// instead of a NaiveDate to avoid E0117.
|
||||
pub struct BasicDate {
|
||||
year: u32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
}
|
||||
|
||||
impl BasicDate {
|
||||
/// Return the seconds since epoch for this date
|
||||
pub fn epoch(&self) -> i64 {
|
||||
// Timezone of Brussels is UTC + 2 hours in the western hemisphere
|
||||
FixedOffset::west(7_200)
|
||||
.ymd(self.year as i32, self.month as u32, self.day as u32)
|
||||
// For some reason, I couldn't get .timestamp() to work on a Date
|
||||
// without a time component, even though the docs seemed to
|
||||
// indicate this was possible
|
||||
.and_hms(0, 0, 0)
|
||||
.timestamp()
|
||||
}
|
||||
}
|
||||
/// This class is a simple wrapper around chrono's DateTime. Its sole purpose
|
||||
/// is to avoid error E0117.
|
||||
pub struct BasicDate(pub DateTime<Tz>);
|
||||
|
||||
/// This allows us to use BasicDate as a query parameter in our routes.
|
||||
impl<'v> FromFormValue<'v> for BasicDate {
|
||||
|
@ -41,29 +24,25 @@ impl<'v> FromFormValue<'v> for BasicDate {
|
|||
}
|
||||
}
|
||||
|
||||
/// We need this when deserializing BasicDate.
|
||||
impl ToString for BasicDate {
|
||||
fn to_string(&self) -> String {
|
||||
format!("{}-{}-{}", self.year, self.month, self.day)
|
||||
/// This is used to serialize BasicDate.
|
||||
impl TryFrom<&str> for BasicDate {
|
||||
type Error = FejError;
|
||||
|
||||
fn try_from(s: &str) -> Result<BasicDate, Self::Error> {
|
||||
let naive_date = NaiveDate::parse_from_str(s, "%Y-%m-%d")?;
|
||||
|
||||
Ok(BasicDate(
|
||||
Brussels
|
||||
.from_local_datetime(&naive_date.and_hms(0, 0, 0))
|
||||
.single()
|
||||
.ok_or(FejError::InvalidArgument)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// This is used to serialize BasicDate.
|
||||
impl TryFrom<&str> for BasicDate {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(s: &str) -> Result<BasicDate, Self::Error> {
|
||||
let re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})$").unwrap();
|
||||
|
||||
match re.captures(s) {
|
||||
None => Err(()),
|
||||
Some(caps) => Ok(BasicDate {
|
||||
// TODO change this to ? operator if possible
|
||||
year: caps.get(1).unwrap().as_str().parse().unwrap(),
|
||||
month: caps.get(2).unwrap().as_str().parse().unwrap(),
|
||||
day: caps.get(3).unwrap().as_str().parse().unwrap(),
|
||||
}),
|
||||
}
|
||||
impl From<&BasicDate> for String {
|
||||
fn from(date: &BasicDate) -> String {
|
||||
format!("{}", date.0.format("%Y-%m-%d"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +51,6 @@ impl Serialize for BasicDate {
|
|||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
serializer.serialize_str(&String::from(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ pub struct Street {
|
|||
pub city: String,
|
||||
}
|
||||
|
||||
impl From<Street> for String {
|
||||
fn from(street: Street) -> String {
|
||||
impl From<&Street> for String {
|
||||
fn from(street: &Street) -> String {
|
||||
format!("{} ({})", street.name, street.city)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ pub fn route_get_pickup_times(
|
|||
end_date: BasicDate,
|
||||
) -> Result<Json<Vec<PickupTime>>, Status> {
|
||||
Ok(Json(get_pickup_times(
|
||||
street, number, start_date, end_date,
|
||||
&street,
|
||||
&number,
|
||||
&start_date.0,
|
||||
&end_date.0,
|
||||
)?))
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
extern crate chrono_tz;
|
||||
|
||||
// Route modules
|
||||
mod catchers;
|
||||
mod errors;
|
||||
|
|
Loading…
Reference in New Issue