From e07a9e4ee7656a87d20d6cea53059bfe7ed2ccec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Andr=C3=A9=20Gagnon?= <32080686+pag4k@users.noreply.github.com> Date: Fri, 10 Jul 2020 13:48:11 -0400 Subject: [PATCH] 1747 add ns to duration (#2128) * Added nanos to Duration * Removed unwraps * Added nanos to Duration * Removed unwraps * Fixed errors * Removed unwraps * Changed serialization to String * Fixed Date and Duration comparison --- crates/nu-cli/src/commands/to_bson.rs | 2 +- crates/nu-cli/src/commands/to_json.rs | 4 +- crates/nu-cli/src/commands/to_sqlite.rs | 2 +- crates/nu-cli/src/commands/to_toml.rs | 2 +- crates/nu-cli/src/commands/to_yaml.rs | 12 +-- crates/nu-cli/src/data/base.rs | 24 ++--- crates/nu-cli/src/data/base/shape.rs | 11 ++- crates/nu-cli/src/data/value.rs | 26 ++--- crates/nu-cli/tests/commands/math/mod.rs | 26 ++++- crates/nu-parser/src/parse.rs | 3 + crates/nu-protocol/Cargo.toml | 2 +- crates/nu-protocol/src/hir.rs | 114 ++++++++++++++++------ crates/nu-protocol/src/value.rs | 4 +- crates/nu-protocol/src/value/debug.rs | 2 +- crates/nu-protocol/src/value/primitive.rs | 114 +++++++++++++++++++--- crates/nu_plugin_post/src/post.rs | 7 +- crates/nu_plugin_sys/Cargo.toml | 1 + crates/nu_plugin_sys/src/sys.rs | 5 +- 18 files changed, 263 insertions(+), 98 deletions(-) diff --git a/crates/nu-cli/src/commands/to_bson.rs b/crates/nu-cli/src/commands/to_bson.rs index 4f75416384..c4241d8b48 100644 --- a/crates/nu-cli/src/commands/to_bson.rs +++ b/crates/nu-cli/src/commands/to_bson.rs @@ -46,7 +46,7 @@ pub fn value_to_bson_value(v: &Value) -> Result { .to_f64() .expect("Unimplemented BUG: What about big decimals?"), ), - UntaggedValue::Primitive(Primitive::Duration(secs)) => Bson::I64(*secs as i64), + UntaggedValue::Primitive(Primitive::Duration(i)) => Bson::String(i.to_string()), UntaggedValue::Primitive(Primitive::Date(d)) => Bson::UtcDatetime(*d), UntaggedValue::Primitive(Primitive::EndOfStream) => Bson::Null, UntaggedValue::Primitive(Primitive::BeginningOfStream) => Bson::Null, diff --git a/crates/nu-cli/src/commands/to_json.rs b/crates/nu-cli/src/commands/to_json.rs index da2bced795..9a3a982063 100644 --- a/crates/nu-cli/src/commands/to_json.rs +++ b/crates/nu-cli/src/commands/to_json.rs @@ -65,8 +65,8 @@ pub fn value_to_json_value(v: &Value) -> Result { UntaggedValue::Primitive(Primitive::Bytes(b)) => serde_json::Value::Number( serde_json::Number::from(b.to_u64().expect("What about really big numbers")), ), - UntaggedValue::Primitive(Primitive::Duration(secs)) => { - serde_json::Value::Number(serde_json::Number::from(*secs)) + UntaggedValue::Primitive(Primitive::Duration(i)) => { + serde_json::Value::String(i.to_string()) } UntaggedValue::Primitive(Primitive::Date(d)) => serde_json::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => serde_json::Value::Null, diff --git a/crates/nu-cli/src/commands/to_sqlite.rs b/crates/nu-cli/src/commands/to_sqlite.rs index 9e6805c5fa..0fbbede2a5 100644 --- a/crates/nu-cli/src/commands/to_sqlite.rs +++ b/crates/nu-cli/src/commands/to_sqlite.rs @@ -91,7 +91,7 @@ fn nu_value_to_sqlite_string(v: Value) -> String { UntaggedValue::Primitive(p) => match p { Primitive::Nothing => "NULL".into(), Primitive::Int(i) => format!("{}", i), - Primitive::Duration(u) => format!("{}", u), + Primitive::Duration(i) => format!("{}", i), Primitive::Decimal(f) => format!("{}", f), Primitive::Bytes(u) => format!("{}", u), Primitive::Pattern(s) => format!("'{}'", s.replace("'", "''")), diff --git a/crates/nu-cli/src/commands/to_toml.rs b/crates/nu-cli/src/commands/to_toml.rs index f4c70b38c6..77880e6c2c 100644 --- a/crates/nu-cli/src/commands/to_toml.rs +++ b/crates/nu-cli/src/commands/to_toml.rs @@ -45,7 +45,7 @@ fn helper(v: &Value) -> Result { Ok(match &v.value { UntaggedValue::Primitive(Primitive::Boolean(b)) => toml::Value::Boolean(*b), UntaggedValue::Primitive(Primitive::Bytes(b)) => toml::Value::Integer(*b as i64), - UntaggedValue::Primitive(Primitive::Duration(d)) => toml::Value::Integer(*d as i64), + UntaggedValue::Primitive(Primitive::Duration(i)) => toml::Value::String(i.to_string()), UntaggedValue::Primitive(Primitive::Date(d)) => toml::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => { toml::Value::String("".to_string()) diff --git a/crates/nu-cli/src/commands/to_yaml.rs b/crates/nu-cli/src/commands/to_yaml.rs index 3c0109f190..4baa885dff 100644 --- a/crates/nu-cli/src/commands/to_yaml.rs +++ b/crates/nu-cli/src/commands/to_yaml.rs @@ -40,15 +40,9 @@ pub fn value_to_yaml_value(v: &Value) -> Result { ) })?)) } - UntaggedValue::Primitive(Primitive::Duration(secs)) => serde_yaml::Value::Number( - serde_yaml::Number::from(secs.to_f64().ok_or_else(|| { - ShellError::labeled_error( - "Could not convert to duration", - "could not convert to duration", - &v.tag, - ) - })?), - ), + UntaggedValue::Primitive(Primitive::Duration(i)) => { + serde_yaml::Value::String(i.to_string()) + } UntaggedValue::Primitive(Primitive::Date(d)) => serde_yaml::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => serde_yaml::Value::Null, UntaggedValue::Primitive(Primitive::BeginningOfStream) => serde_yaml::Value::Null, diff --git a/crates/nu-cli/src/data/base.rs b/crates/nu-cli/src/data/base.rs index 18bf50ec92..8b6f490834 100644 --- a/crates/nu-cli/src/data/base.rs +++ b/crates/nu-cli/src/data/base.rs @@ -7,13 +7,12 @@ use nu_errors::ShellError; use nu_protocol::{ hir, Primitive, ShellTypeName, SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value, }; -use nu_source::Tag; +use nu_source::{Span, Tag}; use nu_value_ext::ValueExt; use num_bigint::BigInt; use num_traits::Zero; use query_interface::{interfaces, vtable_for, ObjectHash}; use serde::{Deserialize, Serialize}; -use std::time::SystemTime; #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)] pub struct Operation { @@ -87,7 +86,7 @@ pub(crate) enum CompareValues { Decimals(BigDecimal, BigDecimal), String(String, String), Date(DateTime, DateTime), - DateDuration(DateTime, i64), + DateDuration(DateTime, BigInt), Booleans(bool, bool), } @@ -99,14 +98,15 @@ impl CompareValues { CompareValues::String(left, right) => left.cmp(right), CompareValues::Date(left, right) => left.cmp(right), CompareValues::DateDuration(left, right) => { - use std::time::Duration; - - // Create the datetime we're comparing against, as duration is an offset from now - let right: DateTime = if *right < 0 { - (SystemTime::now() + Duration::from_secs((*right * -1) as u64)).into() - } else { - (SystemTime::now() - Duration::from_secs(*right as u64)).into() - }; + // FIXME: Not sure if I could do something better with the Span. + let duration = Primitive::into_chrono_duration( + Primitive::Duration(right.clone()), + Span::unknown(), + ) + .expect("Could not convert nushell Duration into chrono Duration."); + let right: DateTime = Utc::now() + .checked_sub_signed(duration) + .expect("Data overflow"); right.cmp(left) } CompareValues::Booleans(left, right) => left.cmp(right), @@ -159,7 +159,7 @@ fn coerce_compare_primitive( (String(left), Line(right)) => CompareValues::String(left.clone(), right.clone()), (Line(left), Line(right)) => CompareValues::String(left.clone(), right.clone()), (Date(left), Date(right)) => CompareValues::Date(*left, *right), - (Date(left), Duration(right)) => CompareValues::DateDuration(*left, *right), + (Date(left), Duration(right)) => CompareValues::DateDuration(*left, right.clone()), (Boolean(left), Boolean(right)) => CompareValues::Booleans(*left, *right), _ => return Err((left.type_name(), right.type_name())), }) diff --git a/crates/nu-cli/src/data/base/shape.rs b/crates/nu-cli/src/data/base/shape.rs index 27e23eea01..d98680ba92 100644 --- a/crates/nu-cli/src/data/base/shape.rs +++ b/crates/nu-cli/src/data/base/shape.rs @@ -27,7 +27,7 @@ pub enum InlineShape { Pattern(String), Boolean(bool), Date(DateTime), - Duration(i64), + Duration(BigInt), Path(PathBuf), Binary(usize), @@ -71,7 +71,7 @@ impl InlineShape { Primitive::Pattern(pattern) => InlineShape::Pattern(pattern.clone()), Primitive::Boolean(boolean) => InlineShape::Boolean(*boolean), Primitive::Date(date) => InlineShape::Date(*date), - Primitive::Duration(duration) => InlineShape::Duration(*duration), + Primitive::Duration(duration) => InlineShape::Duration(duration.clone()), Primitive::Path(path) => InlineShape::Path(path.clone()), Primitive::Binary(b) => InlineShape::Binary(b.len()), Primitive::BeginningOfStream => InlineShape::BeginningOfStream, @@ -178,9 +178,10 @@ impl PrettyDebug for FormatInlineShape { .to_owned(), ), InlineShape::Date(date) => b::primitive(nu_protocol::format_date(date)), - InlineShape::Duration(duration) => { - b::description(format_primitive(&Primitive::Duration(*duration), None)) - } + InlineShape::Duration(duration) => b::description(format_primitive( + &Primitive::Duration(duration.clone()), + None, + )), InlineShape::Path(path) => b::primitive(path.display()), InlineShape::Binary(length) => b::opaque(format!("", length)), InlineShape::Row(row) => b::delimit( diff --git a/crates/nu-cli/src/data/value.rs b/crates/nu-cli/src/data/value.rs index 385adf9629..1dd9a90b42 100644 --- a/crates/nu-cli/src/data/value.rs +++ b/crates/nu-cli/src/data/value.rs @@ -6,7 +6,7 @@ use nu_errors::ShellError; use nu_protocol::hir::Operator; use nu_protocol::ShellTypeName; use nu_protocol::{Primitive, Type, UntaggedValue}; -use nu_source::{DebugDocBuilder, PrettyDebug, Tagged}; +use nu_source::{DebugDocBuilder, PrettyDebug, Span, Tagged}; use nu_table::TextStyle; use num_traits::Zero; @@ -118,18 +118,20 @@ pub fn compute_values( }?; Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) } - (Primitive::Date(x), Primitive::Date(y)) => { + (Primitive::Date(x), Primitive::Date(y)) => match operator { + Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::from( + x.signed_duration_since(*y), + ))), + _ => Err((left.type_name(), right.type_name())), + }, + (Primitive::Date(x), Primitive::Duration(_)) => { let result = match operator { - Operator::Minus => Ok(x.signed_duration_since(*y).num_seconds()), - _ => Err((left.type_name(), right.type_name())), - }?; - Ok(UntaggedValue::Primitive(Primitive::Duration(result))) - } - (Primitive::Date(x), Primitive::Duration(y)) => { - let result = match operator { - Operator::Plus => Ok(x - .checked_add_signed(chrono::Duration::seconds(*y as i64)) - .expect("Overflowing add of duration")), + Operator::Plus => { + // FIXME: Not sure if I could do something better with the Span. + let y = Primitive::into_chrono_duration(rhs.clone(), Span::unknown()) + .expect("Could not convert nushell Duration into chrono Duration."); + Ok(x.checked_add_signed(y).expect("Data overflow.")) + } _ => Err((left.type_name(), right.type_name())), }?; Ok(UntaggedValue::Primitive(Primitive::Date(result))) diff --git a/crates/nu-cli/tests/commands/math/mod.rs b/crates/nu-cli/tests/commands/math/mod.rs index 136d568723..75670ba053 100644 --- a/crates/nu-cli/tests/commands/math/mod.rs +++ b/crates/nu-cli/tests/commands/math/mod.rs @@ -168,7 +168,31 @@ fn duration_math() { "# )); - assert_eq!(actual.out, "8:00:00:00"); + assert_eq!(actual.out, "8:00:00:00.0"); +} + +#[test] +fn duration_math_with_nanoseconds() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + = 1w + 10ns + "# + )); + + assert_eq!(actual.out, "7:00:00:00.00000001"); +} + +#[test] +fn duration_math_with_negative() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + = 1d - 1w + "# + )); + + assert_eq!(actual.out, "-6:00:00:00.0"); } #[test] diff --git a/crates/nu-parser/src/parse.rs b/crates/nu-parser/src/parse.rs index 6d46e76234..a746b662d9 100644 --- a/crates/nu-parser/src/parse.rs +++ b/crates/nu-parser/src/parse.rs @@ -311,6 +311,9 @@ fn parse_unit(lite_arg: &Spanned) -> (SpannedExpression, Option for Number { } } +impl ToBigInt for Number { + fn to_bigint(&self) -> Option { + match self { + Number::Int(int) => Some(int.clone()), + // The BigDecimal to BigInt conversion always return Some(). + // FIXME: This conversion might not be want we want, it just remove the scale. + Number::Decimal(decimal) => decimal.to_bigint(), + } + } +} + impl PrettyDebug for Unit { fn pretty(&self) -> DebugDocBuilder { b::keyword(self.as_str()) @@ -458,25 +471,6 @@ pub fn convert_number_to_u64(number: &Number) -> u64 { } } -fn convert_number_to_i64(number: &Number) -> i64 { - match number { - Number::Int(big_int) => { - if let Some(x) = big_int.to_i64() { - x - } else { - unreachable!("Internal error: convert_number_to_u64 given incompatible number") - } - } - Number::Decimal(big_decimal) => { - if let Some(x) = big_decimal.to_i64() { - x - } else { - unreachable!("Internal error: convert_number_to_u64 given incompatible number") - } - } - } -} - impl Unit { pub fn as_str(self) -> &'static str { match self { @@ -486,6 +480,9 @@ impl Unit { Unit::Gigabyte => "GB", Unit::Terabyte => "TB", Unit::Petabyte => "PB", + Unit::Nanosecond => "ns", + Unit::Microsecond => "us", + Unit::Millisecond => "ms", Unit::Second => "s", Unit::Minute => "m", Unit::Hour => "h", @@ -508,13 +505,68 @@ impl Unit { Unit::Petabyte => { bytes(convert_number_to_u64(&size) * 1024 * 1024 * 1024 * 1024 * 1024) } - Unit::Second => duration(convert_number_to_i64(&size)), - Unit::Minute => duration(60 * convert_number_to_i64(&size)), - Unit::Hour => duration(60 * 60 * convert_number_to_i64(&size)), - Unit::Day => duration(24 * 60 * 60 * convert_number_to_i64(&size)), - Unit::Week => duration(7 * 24 * 60 * 60 * convert_number_to_i64(&size)), - Unit::Month => duration(30 * 24 * 60 * 60 * convert_number_to_i64(&size)), - Unit::Year => duration(365 * 24 * 60 * 60 * convert_number_to_i64(&size)), + Unit::Nanosecond => duration(size.to_bigint().expect("Conversion should never fail.")), + Unit::Microsecond => { + duration(size.to_bigint().expect("Conversion should never fail.") * 1000) + } + Unit::Millisecond => { + duration(size.to_bigint().expect("Conversion should never fail.") * 1000 * 1000) + } + Unit::Second => duration( + size.to_bigint().expect("Conversion should never fail.") * 1000 * 1000 * 1000, + ), + Unit::Minute => duration( + size.to_bigint().expect("Conversion should never fail.") * 60 * 1000 * 1000 * 1000, + ), + Unit::Hour => duration( + size.to_bigint().expect("Conversion should never fail.") + * 60 + * 60 + * 1000 + * 1000 + * 1000, + ), + Unit::Day => duration( + size.to_bigint().expect("Conversion should never fail.") + * 24 + * 60 + * 60 + * 1000 + * 1000 + * 1000, + ), + Unit::Week => duration( + size.to_bigint().expect("Conversion should never fail.") + * 7 + * 24 + * 60 + * 60 + * 1000 + * 1000 + * 1000, + ), + // FIXME: Number of days per month should not always be 30. + Unit::Month => duration( + size.to_bigint().expect("Conversion should never fail.") + * 30 + * 24 + * 60 + * 60 + * 1000 + * 1000 + * 1000, + ), + // FIXME: Number of days per year should not be 365. + Unit::Year => duration( + size.to_bigint().expect("Conversion should never fail.") + * 365 + * 24 + * 60 + * 60 + * 1000 + * 1000 + * 1000, + ), } } } @@ -523,8 +575,8 @@ pub fn bytes(size: u64) -> UntaggedValue { UntaggedValue::Primitive(Primitive::Bytes(size)) } -pub fn duration(secs: i64) -> UntaggedValue { - UntaggedValue::Primitive(Primitive::Duration(secs)) +pub fn duration(nanos: BigInt) -> UntaggedValue { + UntaggedValue::Primitive(Primitive::Duration(nanos)) } #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)] diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index c192d29cc5..5335acc622 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -212,8 +212,8 @@ impl UntaggedValue { } /// Helper for creating date duration values - pub fn duration(secs: i64) -> UntaggedValue { - UntaggedValue::Primitive(Primitive::Duration(secs)) + pub fn duration(nanos: BigInt) -> UntaggedValue { + UntaggedValue::Primitive(Primitive::Duration(nanos)) } /// Helper for creating datatime values diff --git a/crates/nu-protocol/src/value/debug.rs b/crates/nu-protocol/src/value/debug.rs index 87c3410ce5..7a46d098c1 100644 --- a/crates/nu-protocol/src/value/debug.rs +++ b/crates/nu-protocol/src/value/debug.rs @@ -81,7 +81,7 @@ impl PrettyDebug for Primitive { false => b::primitive("$no"), }, Primitive::Date(date) => primitive_doc(date, "date"), - Primitive::Duration(duration) => primitive_doc(duration, "seconds"), + Primitive::Duration(duration) => primitive_doc(duration, "nanoseconds"), Primitive::Path(path) => primitive_doc(path, "path"), Primitive::Binary(_) => b::opaque("binary"), Primitive::BeginningOfStream => b::keyword("beginning-of-stream"), diff --git a/crates/nu-protocol/src/value/primitive.rs b/crates/nu-protocol/src/value/primitive.rs index 94b6bcaeeb..67a0921bba 100644 --- a/crates/nu-protocol/src/value/primitive.rs +++ b/crates/nu-protocol/src/value/primitive.rs @@ -7,11 +7,15 @@ use chrono::{DateTime, Utc}; use nu_errors::{ExpectedRange, ShellError}; use nu_source::{PrettyDebug, Span, SpannedItem}; use num_bigint::BigInt; +use num_integer::Integer; use num_traits::cast::{FromPrimitive, ToPrimitive}; use num_traits::identities::Zero; +use num_traits::sign::Signed; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +const NANOS_PER_SEC: u32 = 1000000000; + /// The most fundamental of structured values in Nu are the Primitive values. These values represent types like integers, strings, booleans, dates, etc that are then used /// as the buildig blocks to build up more complex structures. /// @@ -40,8 +44,9 @@ pub enum Primitive { Boolean(bool), /// A date value, in UTC Date(DateTime), - /// A count in the number of seconds - Duration(i64), + /// A count in the number of nanoseconds + #[serde(with = "serde_bigint")] + Duration(BigInt), /// A range of values Range(Box), /// A file path @@ -75,6 +80,40 @@ impl Primitive { } } + // FIXME: This is a bad name, but no other way to differentiate with our own Duration. + pub fn into_chrono_duration(self, span: Span) -> Result { + match self { + Primitive::Duration(duration) => { + let (secs, nanos) = duration.div_rem(&BigInt::from(NANOS_PER_SEC)); + let secs = match secs.to_i64() { + Some(secs) => secs, + None => { + return Err(ShellError::labeled_error( + "Internal duration conversion overflow.", + "duration overflow", + span, + )) + } + }; + // This should never fail since nanos < 10^9. + let nanos = match nanos.to_i64() { + Some(nanos) => nanos, + None => return Err(ShellError::unexpected("Unexpected i64 overflow")), + }; + let nanos = chrono::Duration::nanoseconds(nanos); + // This should also never fail since we are adding less than NANOS_PER_SEC. + match chrono::Duration::seconds(secs).checked_add(&nanos) { + Some(duration) => Ok(duration), + None => Err(ShellError::unexpected("Unexpected duration overflow")), + } + } + other => Err(ShellError::type_error( + "duration", + other.type_name().spanned(span), + )), + } + } + pub fn into_string(self, span: Span) -> Result { match self { Primitive::String(s) => Ok(s), @@ -209,6 +248,20 @@ impl From for Primitive { } } +impl From for Primitive { + fn from(duration: chrono::Duration) -> Primitive { + // FIXME: This is a hack since chrono::Duration does not give access to its 'nanos' field. + let secs: i64 = duration.num_seconds(); + // This will never fail. + let nanos: u32 = duration + .checked_sub(&chrono::Duration::seconds(secs)) + .expect("Unexpected overflow") + .num_nanoseconds() + .expect("Unexpected overflow") as u32; + Primitive::Duration(BigInt::from(secs) * NANOS_PER_SEC + nanos) + } +} + impl ShellTypeName for Primitive { /// Get the name of the type of a Primitive value fn type_name(&self) -> &'static str { @@ -254,7 +307,7 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S _ => byte.format(1), } } - Primitive::Duration(sec) => format_duration(*sec), + Primitive::Duration(duration) => format_duration(duration), Primitive::Int(i) => i.to_string(), Primitive::Decimal(decimal) => format!("{:.4}", decimal), Primitive::Range(range) => format!( @@ -297,18 +350,49 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S } } -/// Format a duration in seconds into a string -pub fn format_duration(sec: i64) -> String { - let (minutes, seconds) = (sec / 60, sec % 60); - let (hours, minutes) = (minutes / 60, minutes % 60); - let (days, hours) = (hours / 24, hours % 24); - - match (days, hours, minutes, seconds) { - (0, 0, 0, 1) => "1 sec".to_owned(), - (0, 0, 0, s) => format!("{} secs", s), - (0, 0, m, s) => format!("{}:{:02}", m, s), - (0, h, m, s) => format!("{}:{:02}:{:02}", h, m, s), - (d, h, m, s) => format!("{}:{:02}:{:02}:{:02}", d, h, m, s), +/// Format a duration in nanoseconds into a string +pub fn format_duration(duration: &BigInt) -> String { + // FIXME: This involves a lot of allocation, but it seems inevitable with BigInt. + let big_int_1000 = BigInt::from(1000); + let big_int_60 = BigInt::from(60); + let big_int_24 = BigInt::from(24); + // We only want the biggest subvidision to have the negative sign. + let (sign, duration) = if duration.is_zero() || duration.is_positive() { + (1, duration.clone()) + } else { + (-1, -duration) + }; + let (micros, nanos): (BigInt, BigInt) = duration.div_rem(&big_int_1000); + let (millis, micros): (BigInt, BigInt) = micros.div_rem(&big_int_1000); + let (secs, millis): (BigInt, BigInt) = millis.div_rem(&big_int_1000); + let (mins, secs): (BigInt, BigInt) = secs.div_rem(&big_int_60); + let (hours, mins): (BigInt, BigInt) = mins.div_rem(&big_int_60); + let (days, hours): (BigInt, BigInt) = hours.div_rem(&big_int_24); + let decimals = if millis.is_zero() && micros.is_zero() && nanos.is_zero() { + String::from("0") + } else { + format!("{:03}{:03}{:03}", millis, micros, nanos) + .trim_end_matches('0') + .to_string() + }; + match ( + days.is_zero(), + hours.is_zero(), + mins.is_zero(), + secs.is_zero(), + ) { + (true, true, true, true) => format!("{}.{}", if sign == 1 { "0" } else { "-0" }, decimals), + (true, true, true, _) => format!("{}.{}", sign * secs, decimals), + (true, true, _, _) => format!("{}:{:02}.{}", sign * mins, secs, decimals), + (true, _, _, _) => format!("{}:{:02}:{:02}.{}", sign * hours, mins, secs, decimals), + _ => format!( + "{}:{:02}:{:02}:{:02}.{}", + sign * days, + hours, + mins, + secs, + decimals + ), } } diff --git a/crates/nu_plugin_post/src/post.rs b/crates/nu_plugin_post/src/post.rs index e005e51a4a..78090a51e3 100644 --- a/crates/nu_plugin_post/src/post.rs +++ b/crates/nu_plugin_post/src/post.rs @@ -350,8 +350,11 @@ pub fn value_to_json_value(v: &Value) -> Result { UntaggedValue::Primitive(Primitive::Bytes(b)) => serde_json::Value::Number( serde_json::Number::from(b.to_u64().expect("What about really big numbers")), ), - UntaggedValue::Primitive(Primitive::Duration(secs)) => { - serde_json::Value::Number(serde_json::Number::from(*secs)) + UntaggedValue::Primitive(Primitive::Duration(i)) => { + serde_json::Value::Number(serde_json::Number::from(CoerceInto::::coerce_into( + i.tagged(&v.tag), + "converting to JSON number", + )?)) } UntaggedValue::Primitive(Primitive::Date(d)) => serde_json::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => serde_json::Value::Null, diff --git a/crates/nu_plugin_sys/Cargo.toml b/crates/nu_plugin_sys/Cargo.toml index 75ddb012d6..41230ffe1e 100644 --- a/crates/nu_plugin_sys/Cargo.toml +++ b/crates/nu_plugin_sys/Cargo.toml @@ -18,6 +18,7 @@ nu-source = {path = "../nu-source", version = "0.16.1"} battery = "0.7.5" futures = {version = "0.3", features = ["compat", "io-compat"]} futures-util = "0.3.5" +num-bigint = "0.2.6" [dependencies.heim] default-features = false diff --git a/crates/nu_plugin_sys/src/sys.rs b/crates/nu_plugin_sys/src/sys.rs index 99116e9179..827bf49ee3 100644 --- a/crates/nu_plugin_sys/src/sys.rs +++ b/crates/nu_plugin_sys/src/sys.rs @@ -4,6 +4,7 @@ use heim::{disk, host, memory, net, sensors}; use nu_errors::ShellError; use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value}; use nu_source::Tag; +use num_bigint::BigInt; use std::ffi::OsStr; #[derive(Default)] @@ -98,9 +99,9 @@ async fn host(tag: Tag) -> Result { // Uptime if let Ok(uptime) = uptime_result { - let uptime = uptime.get::().round() as i64; + let uptime = uptime.get::().round() as i64; - dict.insert_untagged("uptime", UntaggedValue::duration(uptime)); + dict.insert_untagged("uptime", UntaggedValue::duration(BigInt::from(uptime))); } // Sessions