diff --git a/web/src/model.rs b/web/src/model.rs index 3af51aa..38a75f3 100644 --- a/web/src/model.rs +++ b/web/src/model.rs @@ -291,6 +291,13 @@ pub fn fmt_points(points: f64) -> String { } } +/// Render a calendar instant as `YYYY-MM-DD HH:MM`. `month` is 0-based +/// (the JavaScript `Date` convention); the other components are as +/// returned by `Date`'s local-time getters. +pub fn fmt_datetime_parts(year: u32, month: u32, day: u32, hour: u32, minute: u32) -> String { + format!("{year:04}-{:02}-{day:02} {hour:02}:{minute:02}", month + 1) +} + /// The caller's Elo rating for one game type (GET /api/me/ratings). #[derive(Debug, Clone, Deserialize)] #[allow(dead_code)] @@ -658,6 +665,12 @@ mod tests { assert_eq!("2.5", fmt_points(2.5)); } + #[test] + fn fmt_datetime_parts_pads_and_offsets_month() { + assert_eq!("2026-09-19 05:02", fmt_datetime_parts(2026, 8, 19, 5, 2)); + assert_eq!("2026-01-01 00:00", fmt_datetime_parts(2026, 0, 1, 0, 0)); + } + #[test] fn leaderboard_points_accept_floats() { let e: LeaderboardEntry = serde_json::from_value(json!({ diff --git a/web/src/pages/history.rs b/web/src/pages/history.rs index c70a150..7522929 100644 --- a/web/src/pages/history.rs +++ b/web/src/pages/history.rs @@ -68,6 +68,23 @@ fn game_name(game_types: &[GameTypeInfo], game_type: &str) -> String { .unwrap_or_else(|| game_type.to_string()) } +/// The match's finish instant in the browser's local timezone, as +/// `YYYY-MM-DD HH:MM`. Falls back to the raw UTC prefix when the browser +/// cannot parse the timestamp. +fn local_finished(iso: &str) -> String { + let date = js_sys::Date::new(&wasm_bindgen::JsValue::from_str(iso)); + if date.get_time().is_nan() { + return iso.replace('T', " ").chars().take(16).collect(); + } + crate::model::fmt_datetime_parts( + date.get_full_year(), + date.get_month(), + date.get_date(), + date.get_hours(), + date.get_minutes(), + ) +} + #[component] pub fn HistoryPage() -> View { let page = create_signal(Option::::None); @@ -131,12 +148,7 @@ pub fn HistoryPage() -> View { .get_clone() .into_iter() .map(|m| { - let finished = m - .finished_at - .replace('T', " ") - .chars() - .take(16) - .collect::(); + let finished = local_finished(&m.finished_at); let game = game_name(&names, &m.game_type); let mut cells: Vec = team_columns(&m) .iter()