Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 190 additions & 137 deletions package-lock.json

Large diffs are not rendered by default.

49 changes: 39 additions & 10 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ time = "0.3.37"
tokio = { version = "1.47.1", features = ["sync"] }
tokio-stream = "0.1.17"
uuid = { version = "1.0", features = ["v4"] }
chrono-tz = "0.10.4"

[target.'cfg(target_os = "windows")'.dependencies]
windows-native-keyring-store = "1.0"
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/electricity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use super::ApiError;

#[derive(Serialize, PartialEq, Debug)]
pub struct ElectricityConsumption {
#[serde(serialize_with = "crate::serde_utils::serialize_naive_as_utc")]
pub timestamp: NaiveDateTime,
pub value: f64,
}
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{

#[derive(Serialize, PartialEq, Debug)]
pub struct GasConsumption {
#[serde(serialize_with = "crate::serde_utils::serialize_naive_as_utc")]
pub timestamp: NaiveDateTime,
pub value: f64,
}
Expand Down
25 changes: 13 additions & 12 deletions src-tauri/src/data/consumption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use diesel::{prelude::*, upsert::excluded};
use log::error;

use crate::schema::{electricity_consumption, gas_consumption};
use crate::utils::london_midnight_as_utc;

use super::RepositoryError;

Expand Down Expand Up @@ -130,8 +131,8 @@ impl ConsumptionRepository<ElectricityConsumptionValue, ElectricityConsumptionRe
let mut conn = self.get_connection()?;

Ok(electricity_consumption
.filter(timestamp.ge(NaiveDateTime::from(start)))
.filter(timestamp.lt(NaiveDateTime::from(end)))
.filter(timestamp.ge(london_midnight_as_utc(&start)))
.filter(timestamp.lt(london_midnight_as_utc(&end)))
.load::<ElectricityConsumptionRecord>(&mut *conn)?)
}

Expand All @@ -145,8 +146,8 @@ impl ConsumptionRepository<ElectricityConsumptionValue, ElectricityConsumptionRe
let mut connection = self.get_connection()?;

Ok(electricity_consumption
.filter(timestamp.ge(NaiveDateTime::from(start)))
.filter(timestamp.lt(NaiveDateTime::from(end)))
.filter(timestamp.ge(london_midnight_as_utc(&start)))
.filter(timestamp.lt(london_midnight_as_utc(&end)))
.select((
sql::<Date>("DATE(timestamp)"),
sql::<Double>("COALESCE(SUM(energy_consumption_kwh), 0.0)"),
Expand All @@ -166,8 +167,8 @@ impl ConsumptionRepository<ElectricityConsumptionValue, ElectricityConsumptionRe
let mut connection = self.get_connection()?;

Ok(electricity_consumption
.filter(timestamp.ge(NaiveDateTime::from(start)))
.filter(timestamp.lt(NaiveDateTime::from(end)))
.filter(timestamp.ge(london_midnight_as_utc(&start)))
.filter(timestamp.lt(london_midnight_as_utc(&end)))
.select((
sql::<Date>("DATE(timestamp, 'start of month')"),
sql::<Double>("COALESCE(SUM(energy_consumption_kwh), 0.0)"),
Expand Down Expand Up @@ -240,8 +241,8 @@ impl ConsumptionRepository<GasConsumptionValue, GasConsumptionRecord>
let mut conn = self.get_connection()?;

Ok(gas_consumption
.filter(timestamp.ge(NaiveDateTime::from(start)))
.filter(timestamp.lt(NaiveDateTime::from(end)))
.filter(timestamp.ge(london_midnight_as_utc(&start)))
.filter(timestamp.lt(london_midnight_as_utc(&end)))
.filter(energy_consumption_kwh.ne(ENERGY_CONSUMPTION_KWH_ERROR_CODE))
.load::<GasConsumptionRecord>(&mut *conn)?)
}
Expand All @@ -256,8 +257,8 @@ impl ConsumptionRepository<GasConsumptionValue, GasConsumptionRecord>
let mut conn = self.get_connection()?;

Ok(gas_consumption
.filter(timestamp.ge(NaiveDateTime::from(start)))
.filter(timestamp.lt(NaiveDateTime::from(end)))
.filter(timestamp.ge(london_midnight_as_utc(&start)))
.filter(timestamp.lt(london_midnight_as_utc(&end)))
.filter(energy_consumption_kwh.ne(ENERGY_CONSUMPTION_KWH_ERROR_CODE))
.select((
sql::<Date>("DATE(timestamp)"),
Expand All @@ -278,8 +279,8 @@ impl ConsumptionRepository<GasConsumptionValue, GasConsumptionRecord>
let mut conn = self.get_connection()?;

Ok(gas_consumption
.filter(timestamp.ge(NaiveDateTime::from(start)))
.filter(timestamp.lt(NaiveDateTime::from(end)))
.filter(timestamp.ge(london_midnight_as_utc(&start)))
.filter(timestamp.lt(london_midnight_as_utc(&end)))
.filter(energy_consumption_kwh.ne(ENERGY_CONSUMPTION_KWH_ERROR_CODE))
.select((
sql::<Date>("DATE(timestamp, 'start of month')"),
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ mod download;
mod mqtt;
mod retry;
mod schema;
mod serde_utils;
mod utils;

struct AppState {
Expand Down
63 changes: 63 additions & 0 deletions src-tauri/src/serde_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use chrono::{NaiveDateTime, TimeZone, Utc};
use serde::Serializer;

pub fn serialize_naive_as_utc<S>(date: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let utc_date = Utc.from_utc_datetime(date);
serializer.serialize_str(&utc_date.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
}

#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
use serde::Serialize;

#[derive(Serialize)]
struct TestWrapper {
#[serde(serialize_with = "serialize_naive_as_utc")]
pub timestamp: NaiveDateTime,
}

#[test]
fn test_serialize_naive_as_utc_standard() {
let naive_dt = NaiveDate::from_ymd_opt(2026, 6, 26)
.unwrap()
.and_hms_opt(10, 45, 30)
.unwrap();

let wrapper = TestWrapper {
timestamp: naive_dt,
};

let json_result = serde_json::to_string(&wrapper);

assert!(json_result.is_ok());
assert_eq!(
json_result.unwrap(),
r#"{"timestamp":"2026-06-26T10:45:30Z"}"#
);
}

#[test]
fn test_serialize_naive_as_utc_midnight() {
let naive_dt = NaiveDate::from_ymd_opt(2026, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();

let wrapper = TestWrapper {
timestamp: naive_dt,
};

let json_result = serde_json::to_string(&wrapper);

assert!(json_result.is_ok());
assert_eq!(
json_result.unwrap(),
r#"{"timestamp":"2026-01-01T00:00:00Z"}"#
);
}
}
Loading
Loading