From 08940ba4f86bcd8d4b97fa3204a5f522115bfc32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Riegel?= <96702577+LoicRiegel@users.noreply.github.com> Date: Tue, 8 Apr 2025 13:29:16 +0200 Subject: [PATCH] bugfix: wrong display of human readable string (#15522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I think after that we can close #14790 # Description So the issue was the tiny time delta between the moment the "date form-human" command is executed, and the moment the value gets displayed, using chrono_humanize. When in inputing "in 30 seconds", we currently get: ``` [crates\nu-protocol\src\value\mod.rs:950:21] HumanTime::from(*val) = HumanTime( TimeDelta { secs: 29, nanos: 992402700, }, )``` And with "now": ``` crates\nu-protocol\src\value\mod.rs:950:21] HumanTime::from(*val) = HumanTime( TimeDelta { secs: -1, nanos: 993393200, }, ) ``` My solution is to round this timedelta to seconds and pass this to chrono_humanize. Example: instead of passing (-1s + 993393200ns), we pass 0s. Example: instead of passing (29s + 992402700ns), we pass 30s # User-Facing Changes Before 🔴 ```nushell ~> "in 3 days" | date from-human Fri, 11 Apr 2025 09:06:36 +0200 (in 2 days) ~> "in 30 seconds" | date from-human Tue, 8 Apr 2025 09:07:09 +0200 (in 29 seconds) ``` After those changes 🟢 ```nushell ~> "in 3 days" | date from-human Fri, 11 Apr 2025 09:03:47 +0200 (in 3 days) ~> "in 30 seconds" | date from-human Tue, 8 Apr 2025 09:04:28 +0200 (in 30 seconds) ``` # Tests + Formatting # After Submitting --- crates/nu-command/src/date/from_human.rs | 26 +++++++++++++----------- crates/nu-protocol/src/value/mod.rs | 15 +++++++++++--- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/crates/nu-command/src/date/from_human.rs b/crates/nu-command/src/date/from_human.rs index f924b4d6d5..ff99dff064 100644 --- a/crates/nu-command/src/date/from_human.rs +++ b/crates/nu-command/src/date/from_human.rs @@ -103,19 +103,21 @@ fn helper(value: Value, head: Span) -> Value { } }; - if let Ok(date) = from_human_time(&input_val, Local::now().naive_local()) { + let now = Local::now(); + + if let Ok(date) = from_human_time(&input_val, now.naive_local()) { match date { ParseResult::Date(date) => { - let time = Local::now().time(); + let time = now.time(); let combined = date.and_time(time); - let local_offset = *Local::now().offset(); + let local_offset = *now.offset(); let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined) .single() .unwrap_or_default(); return Value::date(dt_fixed, span); } ParseResult::DateTime(date) => { - let local_offset = *Local::now().offset(); + let local_offset = *now.offset(); let dt_fixed = match local_offset.from_local_datetime(&date) { chrono::LocalResult::Single(dt) => dt, chrono::LocalResult::Ambiguous(_, _) => { @@ -140,9 +142,9 @@ fn helper(value: Value, head: Span) -> Value { return Value::date(dt_fixed, span); } ParseResult::Time(time) => { - let date = Local::now().date_naive(); + let date = now.date_naive(); let combined = date.and_time(time); - let local_offset = *Local::now().offset(); + let local_offset = *now.offset(); let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined) .single() .unwrap_or_default(); @@ -151,19 +153,19 @@ fn helper(value: Value, head: Span) -> Value { } } - match from_human_time(&input_val, Local::now().naive_local()) { + match from_human_time(&input_val, now.naive_local()) { Ok(date) => match date { ParseResult::Date(date) => { - let time = Local::now().time(); + let time = now.time(); let combined = date.and_time(time); - let local_offset = *Local::now().offset(); + let local_offset = *now.offset(); let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined) .single() .unwrap_or_default(); Value::date(dt_fixed, span) } ParseResult::DateTime(date) => { - let local_offset = *Local::now().offset(); + let local_offset = *now.offset(); let dt_fixed = match local_offset.from_local_datetime(&date) { chrono::LocalResult::Single(dt) => dt, chrono::LocalResult::Ambiguous(_, _) => { @@ -188,9 +190,9 @@ fn helper(value: Value, head: Span) -> Value { Value::date(dt_fixed, span) } ParseResult::Time(time) => { - let date = Local::now().date_naive(); + let date = now.date_naive(); let combined = date.and_time(time); - let local_offset = *Local::now().offset(); + let local_offset = *now.offset(); let dt_fixed = TimeZone::from_local_datetime(&local_offset, &combined) .single() .unwrap_or_default(); diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 4969daf28e..2d820b044a 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -25,7 +25,7 @@ use crate::{ engine::{Closure, EngineState}, BlockId, Config, ShellError, Signals, Span, Type, }; -use chrono::{DateTime, Datelike, FixedOffset, Locale, TimeZone}; +use chrono::{DateTime, Datelike, Duration, FixedOffset, Local, Locale, TimeZone}; use chrono_humanize::HumanTime; use fancy_regex::Regex; use nu_utils::{ @@ -953,7 +953,7 @@ impl Value { } else { val.to_rfc3339() }, - HumanTime::from(*val), + human_time_from_now(val), ) } }, @@ -999,7 +999,7 @@ impl Value { match self { Value::Date { val, .. } => match &config.datetime_format.table { Some(format) => self.format_datetime(val, format), - None => HumanTime::from(*val).to_string(), + None => human_time_from_now(val).to_string(), }, Value::List { ref vals, .. } => { if !vals.is_empty() && vals.iter().all(|x| matches!(x, Value::Record { .. })) { @@ -4017,6 +4017,15 @@ fn operator_type_error( } } +fn human_time_from_now(val: &DateTime) -> HumanTime { + let now = Local::now().with_timezone(val.offset()); + let delta = *val - now; + let delta_seconds = delta.num_nanoseconds().unwrap_or(0) as f64 / 1_000_000_000.0; + let delta_seconds_rounded = delta_seconds.round() as i64; + + HumanTime::from(Duration::seconds(delta_seconds_rounded)) +} + #[cfg(test)] mod tests { use super::{Record, Value};