From 2d0a60ac678543a964fc578df3ff552c22127b86 Mon Sep 17 00:00:00 2001 From: Andrej Kolchin Date: Fri, 7 Jun 2024 15:43:30 +0300 Subject: [PATCH] Use native toml datetime type in `to toml` (#13018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Makes `to toml` use the `toml::value::Datetime` type, so that `to toml` serializes dates properly. # User-Facing Changes `to toml` will now encode dates differently, in a native format instead of a string. This could, in theory, break some workflows: ```Nushell # Before: ~> {datetime: 2024-05-31} | to toml | from toml | get datetime | into datetime Fri, 31 May 2024 00:00:00 +0000 (10 hours ago) # After: ~> {datetime: 2024-05-31} | to toml | from toml | get datetime | into datetime Error: nu::shell::only_supports_this_input_type × Input type not supported. ╭─[entry #13:1:36] 1 │ {datetime: 2024-05-31} | to toml | from toml | get datetime | into datetime · ────┬──── ──────┬────── · │ ╰── only string and int input data is supported · ╰── input type: date ╰──── ``` Fix #11751 --- crates/nu-command/src/formats/to/toml.rs | 58 ++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/formats/to/toml.rs b/crates/nu-command/src/formats/to/toml.rs index 187304a7c0..ed1490231c 100644 --- a/crates/nu-command/src/formats/to/toml.rs +++ b/crates/nu-command/src/formats/to/toml.rs @@ -1,4 +1,4 @@ -use chrono::SecondsFormat; +use chrono::{DateTime, Datelike, FixedOffset, Timelike}; use nu_engine::command_prelude::*; use nu_protocol::ast::PathMember; @@ -49,9 +49,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result toml::Value::Integer(*val), Value::Filesize { val, .. } => toml::Value::Integer(*val), Value::Duration { val, .. } => toml::Value::String(val.to_string()), - Value::Date { val, .. } => { - toml::Value::String(val.to_rfc3339_opts(SecondsFormat::AutoSi, false)) - } + Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)), Value::Range { .. } => toml::Value::String("".to_string()), Value::Float { val, .. } => toml::Value::Float(*val), Value::String { val, .. } | Value::Glob { val, .. } => toml::Value::String(val.clone()), @@ -157,6 +155,43 @@ fn to_toml( } } +/// Convert chrono datetime into a toml::Value datetime. The latter uses its +/// own ad-hoc datetime types, which makes this somewhat convoluted. +fn to_toml_datetime(datetime: &DateTime) -> toml::value::Datetime { + let date = toml::value::Date { + // TODO: figure out what to do with BC dates, because the toml + // crate doesn't support them. Same for large years, which + // don't fit in u16. + year: datetime.year_ce().1 as u16, + // Panic: this is safe, because chrono guarantees that the month + // value will be between 1 and 12 and the day will be between 1 + // and 31 + month: datetime.month() as u8, + day: datetime.day() as u8, + }; + + let time = toml::value::Time { + // Panic: same as before, chorono guarantees that all of the following 3 + // methods return values less than 65'000 + hour: datetime.hour() as u8, + minute: datetime.minute() as u8, + second: datetime.second() as u8, + nanosecond: datetime.nanosecond(), + }; + + let offset = toml::value::Offset::Custom { + // Panic: minute timezone offset fits into i16 (that's more than + // 1000 hours) + minutes: (-datetime.timezone().utc_minus_local() / 60) as i16, + }; + + toml::value::Datetime { + date: Some(date), + time: Some(time), + offset: Some(offset), + } +} + #[cfg(test)] mod tests { use super::*; @@ -181,7 +216,20 @@ mod tests { Span::test_data(), ); - let reference_date = toml::Value::String(String::from("1980-10-12T10:12:44+02:00")); + let reference_date = toml::Value::Datetime(toml::value::Datetime { + date: Some(toml::value::Date { + year: 1980, + month: 10, + day: 12, + }), + time: Some(toml::value::Time { + hour: 10, + minute: 12, + second: 44, + nanosecond: 0, + }), + offset: Some(toml::value::Offset::Custom { minutes: 120 }), + }); let result = helper(&engine_state, &test_date);