From d034aa8f7f3d36b36dfc519d1d621e0d18f3a470 Mon Sep 17 00:00:00 2001 From: Udaya Tejas Date: Thu, 1 Oct 2026 19:25:43 -0400 Subject: [PATCH] fix(cli): render rule-history timestamps as absolute dates The sandbox rule-history view rendered each entry's event_time with format_timestamp_ms, which divided the epoch milliseconds by 86_400_000 to produce a days component. Because event_time is an absolute google.protobuf.Timestamp, that days component was days-since-1970, so every entry printed a nonsensical count such as "19675d 22:13" instead of a wall-clock date. Drop the buggy helper and format the timestamp with the existing format_optional_epoch_ms, matching how provider history and other absolute-time columns are rendered (and preserving the "-" placeholder for a missing/zero timestamp). Signed-off-by: Udaya Tejas --- crates/openshell-cli/src/commands/common.rs | 30 ++++++++++----------- crates/openshell-cli/src/run.rs | 10 +++---- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/openshell-cli/src/commands/common.rs b/crates/openshell-cli/src/commands/common.rs index 49fd66a9a8..99db81eca5 100644 --- a/crates/openshell-cli/src/commands/common.rs +++ b/crates/openshell-cli/src/commands/common.rs @@ -140,22 +140,6 @@ pub fn format_timestamp(d: Duration) -> String { format!("[{secs:.1}s]") } -/// Format a millisecond timestamp into a readable string. -pub fn format_timestamp_ms(ms: i64) -> String { - if ms <= 0 { - return "-".to_string(); - } - let secs = ms / 1000; - let mins = (secs / 60) % 60; - let hours = (secs / 3600) % 24; - let days = secs / 86400; - if days > 0 { - format!("{days}d {hours:02}:{mins:02}") - } else { - format!("{hours:02}:{mins:02}") - } -} - pub fn truncate_status_field(value: &str, max_chars: usize) -> String { if value.is_empty() { return "-".to_string(); @@ -1072,6 +1056,20 @@ pub fn scrub_git_env(command: &mut Command) -> &mut Command { mod tests { use super::*; + #[test] + fn optional_epoch_ms_renders_absolute_date_not_day_count() { + // Rule-history entries carry an absolute `google.protobuf.Timestamp` + // (epoch ms), so the rendered string must be a readable wall-clock + // date, never a days-since-epoch count. 1_700_000_000_000 ms is + // 2023-11-14T22:13:20Z. + assert_eq!( + format_optional_epoch_ms(1_700_000_000_000), + "2023-11-14 22:13:20" + ); + // A missing timestamp (proto default -> 0 ms) renders as a placeholder. + assert_eq!(format_optional_epoch_ms(0), "-"); + } + #[test] fn parse_duration_to_ms_parses_supported_units() { assert_eq!(parse_duration_to_ms("30s").expect("parse"), 30_000); diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index 3aa775685f..c188b7b10d 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -9,10 +9,10 @@ pub use crate::commands::common::{ }; use crate::commands::common::{ ProvisioningDisplay, ProvisioningStep, confirm_global_setting_delete, - confirm_global_setting_takeover, format_epoch_ms, format_setting_value, format_timestamp, - format_timestamp_ms, handle_platform_progress_event, is_provisioning_progress_event, - non_empty_or, parse_cli_setting_value, parse_duration_to_ms, phase_name, - print_policy_merge_warnings, print_sandbox_header, print_sandbox_policy, + confirm_global_setting_takeover, format_epoch_ms, format_optional_epoch_ms, + format_setting_value, format_timestamp, handle_platform_progress_event, + is_provisioning_progress_event, non_empty_or, parse_cli_setting_value, parse_duration_to_ms, + phase_name, print_policy_merge_warnings, print_sandbox_header, print_sandbox_policy, provisioning_timeout_message, ready_false_condition_message, scrub_git_env, short_hash, truncate_status_field, }; @@ -6588,7 +6588,7 @@ pub async fn sandbox_draft_history( println!( " {} {} [{}] {}", - format_timestamp_ms(proto_timestamp_ms(entry.event_time.as_ref())).dimmed(), + format_optional_epoch_ms(proto_timestamp_ms(entry.event_time.as_ref())).dimmed(), event_colored, entry.chunk_id.get(..8).unwrap_or(&entry.chunk_id), entry.description,