Skip to content

Commit ee2acc5

Browse files
committed
Improve timestamp formatting with local timezone support
- Change format from '2025-11-11T22:32:58Z' to '2025-11-29 14:32:58' - Add local timezone support using time crate - Implement WASM support via js-sys for browser timezone detection - Create shared to_local_datetime() helper function - Update LogView, TraceView, and GraphSelect widgets Closes #132
1 parent fee184b commit ee2acc5

File tree

7 files changed

+87
-5
lines changed

7 files changed

+87
-5
lines changed

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ gantz_egui = { version = "0.1.0", path = "crates/gantz_egui" }
5555
gantz_std = { version = "0.1.0", path = "crates/gantz_std" }
5656
hex = "0.4"
5757
humantime = "2.3"
58+
js-sys = "0.3"
5859
log = { version = "0.4", features = ["serde"] }
5960
petgraph = { version = "0.8", features = ["serde-1"] }
6061
ron = "0.10"
@@ -64,6 +65,7 @@ steel-core = "0.7"
6465
steel-derive = "0.6"
6566
sublime_fuzzy = "0.7"
6667
thiserror = "2"
68+
time = { version = "0.3", features = ["local-offset", "formatting", "wasm-bindgen"] }
6769
tracing = "0.1"
6870
tracing-subscriber = "0.3"
6971
typetag = "0.2"

crates/gantz_egui/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,18 @@ gantz_core.workspace = true
2424
gantz_std.workspace = true
2525
hex.workspace = true
2626
humantime.workspace = true
27+
js-sys.workspace = true
2728
log.workspace = true
2829
petgraph.workspace = true
2930
serde.workspace = true
3031
steel-core.workspace = true
3132
sublime_fuzzy.workspace = true
33+
time.workspace = true
3234
tracing = { workspace = true, optional = true }
3335
tracing-subscriber = { workspace = true, optional = true }
3436
web-time.workspace = true
3537

38+
3639
[dev-dependencies]
3740
dyn-clone.workspace = true
3841
eframe.workspace = true

crates/gantz_egui/src/widget.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
//! A collection of useful widgets for gantz.
2+
#[cfg(target_arch = "wasm32")]
3+
use js_sys::Date;
4+
use time::{OffsetDateTime, UtcOffset};
25

36
pub use command_palette::CommandPalette;
47
pub use gantz::{Gantz, GantzState, update_graph_pane_head};
@@ -30,6 +33,32 @@ pub mod perf_view;
3033
#[cfg(feature = "tracing")]
3134
pub mod trace_view;
3235

36+
/// Convert a UTC datetime to local timezone, with fallback to UTC if unavailable.
37+
pub(crate) fn to_local_datetime(datetime: OffsetDateTime) -> OffsetDateTime {
38+
#[cfg(not(target_arch = "wasm32"))]
39+
{
40+
// Native platforms: use time crate's built-in method
41+
UtcOffset::current_local_offset()
42+
.map(|offset| datetime.to_offset(offset))
43+
.unwrap_or(datetime)
44+
}
45+
46+
#[cfg(target_arch = "wasm32")]
47+
{
48+
// WASM: get timezone offset from JavaScript Date API
49+
use js_sys::Date;
50+
51+
let js_date = Date::new_0();
52+
let offset_minutes = js_date.get_timezone_offset();
53+
let offset_seconds = -(offset_minutes as i32 * 60);
54+
55+
match UtcOffset::from_whole_seconds(offset_seconds) {
56+
Ok(offset) => datetime.to_offset(offset),
57+
Err(_) => datetime,
58+
}
59+
}
60+
}
61+
3362
/// Simple shorthand for viewing steel code.
3463
pub fn steel_view(ui: &mut egui::Ui, code: &str) {
3564
let language = "scm";

crates/gantz_egui/src/widget/graph_select.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! A simple widget for selecting between, naming and creating new graphs.
22
33
use super::head_row::{HeadRowType, head_row};
4-
use std::collections::HashSet;
4+
use std::collections::{BTreeMap, HashSet};
5+
use time::{OffsetDateTime, UtcOffset, format_description};
56

67
/// A widget for selecting between, naming, and creating new graphs.
78
pub struct GraphSelect<'a> {
@@ -203,3 +204,18 @@ impl<'a> GraphSelect<'a> {
203204
response
204205
}
205206
}
207+
208+
// Format the commit as a timestamp for listing unnamed commits.
209+
pub(super) fn fmt_commit_timestamp(timestamp: gantz_ca::Timestamp) -> String {
210+
std::time::UNIX_EPOCH
211+
.checked_add(timestamp)
212+
.and_then(|system_time| {
213+
let datetime = OffsetDateTime::from(system_time);
214+
let local_datetime = crate::widget::to_local_datetime(datetime);
215+
216+
let format =
217+
format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]").ok()?;
218+
local_datetime.format(&format).ok()
219+
})
220+
.unwrap_or_else(|| "<invalid-timestamp>".to_string())
221+
}

crates/gantz_egui/src/widget/log_view.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
collections::VecDeque,
55
sync::{Arc, Mutex},
66
};
7+
use time::{OffsetDateTime, format_description};
78
use web_time::SystemTime;
89

910
/// A table presenting the
@@ -30,8 +31,16 @@ pub struct LogEntry {
3031

3132
impl LogEntry {
3233
fn format_timestamp(&self) -> String {
33-
let time = crate::system_time_from_web(self.timestamp).expect("failed to convert");
34-
humantime::format_rfc3339_seconds(time).to_string()
34+
let system_time = crate::system_time_from_web(self.timestamp).expect("failed to convert");
35+
let datetime = OffsetDateTime::from(system_time);
36+
let local_datetime = crate::widget::to_local_datetime(datetime);
37+
38+
let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")
39+
.expect("invalid format");
40+
41+
local_datetime
42+
.format(&format)
43+
.unwrap_or_else(|_| "<invalid-timestamp>".to_string())
3544
}
3645

3746
fn freshness(&self) -> f32 {

crates/gantz_egui/src/widget/trace_view.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::{
33
collections::VecDeque,
44
sync::{Arc, Mutex},
55
};
6+
use time::{OffsetDateTime, format_description};
67
use tracing::{Level, level_filters::LevelFilter};
78
use tracing_subscriber::Layer;
89
use web_time::SystemTime;
@@ -47,8 +48,16 @@ struct MessageVisitor {
4748

4849
impl TraceEntry {
4950
fn format_timestamp(&self) -> String {
50-
let time = crate::system_time_from_web(self.timestamp).expect("failed to convert");
51-
humantime::format_rfc3339_seconds(time).to_string()
51+
let system_time = crate::system_time_from_web(self.timestamp).expect("failed to convert");
52+
let datetime = OffsetDateTime::from(system_time);
53+
let local_datetime = crate::widget::to_local_datetime(datetime);
54+
55+
let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")
56+
.expect("invalid format");
57+
58+
local_datetime
59+
.format(&format)
60+
.unwrap_or_else(|_| "<invalid-timestamp>".to_string())
5261
}
5362

5463
fn freshness(&self) -> f32 {

0 commit comments

Comments
 (0)