From c8c018452f301fb86eacec908dd24e60bdbc349b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Riegel?= <96702577+LoicRiegel@users.noreply.github.com> Date: Fri, 11 Apr 2025 18:52:42 +0200 Subject: [PATCH] Bugfix chrono panic + hotifx PR15544 (#15549) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #13972 # Description First commit: a hotfix concerning my last PR #15544! I had a ``unwrap_or_default`` that resulted in all years before ~1800 being considered as "now", because the ``num_nanoseconds()`` overflowed. Cc @fdncred Second: about #13972 Negative years are not allowed with RFC 2822 formatting, so I fallback RTC 3339 in such cases. If you want you might Rebase and Merge, and not squash. # User-Facing Changes On master 🔴 : ```nu ~> {year: 1900} | into datetime Mon, 1 Jan 1900 00:00:00 +0200 (125 years ago) # OK ~> {year: 1000} | into datetime Wed, 1 Jan 1000 00:00:00 +0200 (now) # NOT OK: now? ~> {year: -1000} | into datetime -1000-01-01T00:00:00+02:00 (now) # NOT OK: now? ~> {year: -1000} | into datetime | format date Error: × Main thread panicked. ├─▶ at C:\Users\RIL1RT\.cargo\registry\src\index.crates.io-6f17d22bba15001f\chrono-0.4.39\src\datetime\mod.rs:626:14 ╰─▶ writing rfc2822 datetime to string should never fail: Error help: set the `RUST_BACKTRACE=1` environment variable to display a backtrace. # NOT OK: panics ``` On this branch 🟢 : ```nu ~> {year: 1900} | into datetime Mon, 1 Jan 1900 00:00:00 +0200 (in 125 years) ~> {year: 1000} | into datetime Wed, 1 Jan 1000 00:00:00 +0200 (1025 years ago) ~> {year: -1000} | into datetime -1000-01-01T00:00:00+02:00 (3025 years ago) ~> {year: -1000} | into datetime | format date -1000-01-01T00:00:00+02:00 ~> '3000 years ago' | date from-human | format date -0975-04-11T18:18:24.301641100+02:00 ``` # Tests + Formatting # After Submitting Nothing required IMO --- crates/nu-command/src/formats/to/text.rs | 13 +++++++++- crates/nu-command/src/strings/format/date.rs | 24 ++++++++++++++++--- .../tests/commands/into_datetime.rs | 10 ++++++++ crates/nu-protocol/src/value/mod.rs | 17 +++++++++---- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 858c5fce29..393de80ac3 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -1,3 +1,4 @@ +use chrono::Datelike; use chrono_humanize::HumanTime; use nu_engine::command_prelude::*; use nu_protocol::{format_duration, shell_error::io::IoError, ByteStream, PipelineMetadata}; @@ -167,7 +168,17 @@ fn local_into_string( Value::Filesize { val, .. } => val.to_string(), Value::Duration { val, .. } => format_duration(val), Value::Date { val, .. } => { - format!("{} ({})", val.to_rfc2822(), HumanTime::from(val)) + format!( + "{} ({})", + { + if val.year() >= 0 { + val.to_rfc2822() + } else { + val.to_rfc3339() + } + }, + HumanTime::from(val) + ) } Value::Range { val, .. } => val.to_string(), Value::String { val, .. } => val, diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index fa7911361c..ad668ca510 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -1,5 +1,5 @@ use crate::{generate_strftime_list, parse_date_from_string}; -use chrono::{DateTime, Locale, TimeZone}; +use chrono::{DateTime, Datelike, Locale, TimeZone}; use nu_engine::command_prelude::*; use nu_utils::locale::{get_system_locale_string, LOCALE_OVERRIDE_ENV_VAR}; @@ -228,11 +228,29 @@ fn format_helper( fn format_helper_rfc2822(value: Value, span: Span) -> Value { let val_span = value.span(); match value { - Value::Date { val, .. } => Value::string(val.to_rfc2822(), span), + Value::Date { val, .. } => Value::string( + { + if val.year() >= 0 { + val.to_rfc2822() + } else { + val.to_rfc3339() + } + }, + span, + ), Value::String { val, .. } => { let dt = parse_date_from_string(&val, val_span); match dt { - Ok(x) => Value::string(x.to_rfc2822(), span), + Ok(x) => Value::string( + { + if x.year() >= 0 { + x.to_rfc2822() + } else { + x.to_rfc3339() + } + }, + span, + ), Err(e) => e, } } diff --git a/crates/nu-command/tests/commands/into_datetime.rs b/crates/nu-command/tests/commands/into_datetime.rs index 83f36a7c26..8ad096ef0a 100644 --- a/crates/nu-command/tests/commands/into_datetime.rs +++ b/crates/nu-command/tests/commands/into_datetime.rs @@ -14,6 +14,16 @@ fn into_datetime_from_record() { assert_eq!(expected.out, actual.out); } +#[test] +fn into_datetime_from_record_very_old() { + let actual = nu!(r#"{year: -100, timezone: '+02:00'} | into datetime | into record"#); + let expected = nu!( + r#"{year: -100, month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0, timezone: '+02:00'}"# + ); + + assert_eq!(expected.out, actual.out); +} + #[test] fn into_datetime_from_record_defaults() { let actual = nu!(r#"{year: 2025, timezone: '+02:00'} | into datetime | into record"#); diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 2d820b044a..0aac797c8d 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -4020,10 +4020,19 @@ 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)) + match delta.num_nanoseconds() { + Some(num_nanoseconds) => { + let delta_seconds = num_nanoseconds as f64 / 1_000_000_000.0; + let delta_seconds_rounded = delta_seconds.round() as i64; + HumanTime::from(Duration::seconds(delta_seconds_rounded)) + } + None => { + // Happens if the total number of nanoseconds exceeds what fits in an i64 + // Note: not using delta.num_days() because it results is wrong for years before ~936: a extra year is added + let delta_years = val.year() - now.year(); + HumanTime::from(Duration::days(delta_years as i64 * 365)) + } + } } #[cfg(test)]