Skip to content

Commit bc12dd0

Browse files
committed
feat: add timezone support
1 parent f689a45 commit bc12dd0

File tree

5 files changed

+170
-46
lines changed

5 files changed

+170
-46
lines changed

Cargo.lock

+76
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ sqlx = { version = "=0.7.3", features = ["chrono", "macros", "migrate"], default
1919
chrono = { version = "0.4.38", features = ["serde"] }
2020
anyhow = "1.0.86"
2121
poloto = "19.1.2"
22+
chrono-tz = "0.9.0"

src/form.rs

+33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! Custom form fields for Rocket
22
3+
use std::str::FromStr;
4+
35
use chrono::NaiveDateTime;
6+
use chrono::TimeZone;
47

58
/// Custom form field to parse a datetime string
69
/// in the format of %Y-%m-%dT%H:%M:%S
@@ -27,6 +30,36 @@ impl<'r> rocket::form::FromFormField<'r> for ParseableDateTime {
2730
impl core::ops::Deref for ParseableDateTime {
2831
type Target = chrono::DateTime<chrono::Utc>;
2932

33+
fn deref(&self) -> &Self::Target {
34+
&self.0
35+
}
36+
}
37+
38+
#[derive(Default)]
39+
pub struct Tz(pub chrono_tz::Tz);
40+
41+
impl<'r> rocket::form::FromFormField<'r> for Tz {
42+
fn from_value(field: rocket::form::ValueField<'r>) -> rocket::form::Result<'r, Self> {
43+
let value = field.value.to_string();
44+
let tz = match value.as_str() {
45+
// Try to parse as "Europe/Paris, America/New_York, etc"
46+
s if s.contains('/') => {
47+
chrono_tz::Tz::from_str(s).ok().unwrap_or(chrono_tz::Tz::UTC)
48+
},
49+
_ => chrono_tz::Tz::UTC,
50+
};
51+
52+
Ok(Tz(tz))
53+
}
54+
55+
fn default() -> Option<Self> {
56+
Some(Tz(chrono_tz::Tz::UTC))
57+
}
58+
}
59+
60+
impl core::ops::Deref for Tz {
61+
type Target = chrono_tz::Tz;
62+
3063
fn deref(&self) -> &Self::Target {
3164
&self.0
3265
}

src/main.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
//! - New fairings like the EVChargeFairing could be implmented in the future to
3838
//! add add other IoT devices or additional functionality.
3939
//!
40+
use chrono::TimeZone;
4041
use form::ParseableDateTime;
4142
use governor::Quota;
4243
use print_table::{get_avg_max_rows_for_token, get_paginated_rows_for_token, NoRowsError};
@@ -147,18 +148,19 @@ async fn post_token(
147148
}
148149

149150
/// Route GET /log/:token/html will return the data in HTML format
150-
#[get("/log/<_>/html?<page>&<count>", rank = 1)]
151+
#[get("/log/<_>/html?<page>&<count>&<tz>", rank = 1)]
151152
async fn list_table_html(
152153
page: Option<i32>,
153154
count: Option<i32>,
154155
token: &ValidDbToken,
156+
tz: form::Tz,
155157
mut db: Connection<Logs>,
156158
_ratelimit: RocketGovernor<'_, RateLimitGuard>,
157159
) -> (ContentType, String) {
158160
let page = page.unwrap_or(0);
159161
let count = count.unwrap_or(10);
160162

161-
let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count).await;
163+
let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count, &tz.0).await;
162164

163165
let mut result = String::new();
164166
result.push_str("<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><title>Consumption info</title></head><body><table>");
@@ -169,6 +171,7 @@ async fn list_table_html(
169171
result.push_str(&row.to_html());
170172
}
171173
result.push_str("\n</table>\n");
174+
172175
if has_next {
173176
result.push_str(&format!(
174177
"<a href=\"/log/{}/html?page={}&count={}\">Next</a>",
@@ -177,24 +180,29 @@ async fn list_table_html(
177180
count
178181
));
179182
}
183+
184+
// Add svg embedded
185+
result.push_str(format!("<hr /><img src=\"/log/{}/svg?tz={}\" alt=\"Energy consumption\" />\n", token.full_token(), tz.0).as_str());
186+
180187
result.push_str("</body></html>\n");
181188

182189
(ContentType::HTML, result)
183190
}
184191

185192
/// Route GET /log/:token/json will return the data in JSON format
186-
#[get("/log/<_>/json?<page>&<count>", rank = 1)]
193+
#[get("/log/<_>/json?<page>&<count>&<tz>", rank = 1)]
187194
async fn list_table_json(
188195
page: Option<i32>,
189196
count: Option<i32>,
190197
token: &ValidDbToken,
198+
tz: form::Tz,
191199
mut db: Connection<Logs>,
192200
_ratelimit: RocketGovernor<'_, RateLimitGuard>,
193201
) -> rocket::response::content::RawJson<String> {
194202
let page = page.unwrap_or(0);
195203
let count = count.unwrap_or(10);
196204

197-
let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count).await;
205+
let (rows, has_next) = get_paginated_rows_for_token(&mut db, &token, page, count, &tz.0).await;
198206

199207
let next_url = if has_next {
200208
format!(
@@ -217,12 +225,13 @@ async fn list_table_json(
217225

218226

219227
/// Route GET /log/:token/html will return the data in HTML format
220-
#[get("/log/<_>/svg?<start>&<end>&<interval>", rank = 1)]
228+
#[get("/log/<_>/svg?<start>&<end>&<interval>&<tz>", rank = 1)]
221229
async fn list_table_svg(
222230
start: Option<ParseableDateTime>,
223231
end: Option<ParseableDateTime>,
224232
interval: Option<i32>,
225233
token: &ValidDbToken,
234+
tz: form::Tz,
226235
mut db: Connection<Logs>,
227236
_ratelimit: RocketGovernor<'_, RateLimitGuard>,
228237
) -> (ContentType, String) {
@@ -235,7 +244,7 @@ async fn list_table_svg(
235244

236245
let (avg, max) = get_avg_max_rows_for_token(&mut db, &token, &start, &end, interval).await;
237246

238-
match print_table::to_svg_plot(avg, max) {
247+
match print_table::to_svg_plot(avg, max, &tz.0) {
239248
Ok(svg) => (ContentType::SVG, svg),
240249
Err(e) if e.downcast_ref::<NoRowsError>().is_some() => {
241250
(ContentType::Plain, "No data found for the given request".to_string())

0 commit comments

Comments
 (0)