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,