mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Date utility commands (#2780)
* updated & added date related commands based on the new design * added proper error handling when date format string is invalid * fixed format issue * fixed an issue caused due to the change in primitive Date type * added `date list-timezone` command to list all supported time zones and updated `date to-timezone` accordingly
This commit is contained in:
parent
e000ed47cd
commit
83c874666a
21 changed files with 538 additions and 187 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -3144,6 +3144,7 @@ dependencies = [
|
|||
"bytes 0.5.6",
|
||||
"calamine",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"clap",
|
||||
"clipboard",
|
||||
"codespan-reporting",
|
||||
|
@ -3217,6 +3218,7 @@ dependencies = [
|
|||
"term",
|
||||
"term_size",
|
||||
"termcolor",
|
||||
"titlecase",
|
||||
"toml",
|
||||
"trash",
|
||||
"umask",
|
||||
|
@ -5624,6 +5626,16 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "titlecase"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f565e410cfc24c2f2a89960b023ca192689d7f77d3f8d4f4af50c2d8affe1117"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0",
|
||||
"regex 1.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.1.22"
|
||||
|
|
|
@ -31,6 +31,7 @@ byte-unit = "4.0.9"
|
|||
bytes = "0.5.6"
|
||||
calamine = "0.16.1"
|
||||
chrono = {version = "0.4.15", features = ["serde"]}
|
||||
chrono-tz = "0.5.3"
|
||||
clap = "2.33.3"
|
||||
clipboard = {version = "0.5.0", optional = true}
|
||||
codespan-reporting = "0.9.5"
|
||||
|
@ -91,6 +92,7 @@ tempfile = "3.1.0"
|
|||
term = {version = "0.6.1", optional = true}
|
||||
term_size = "0.3.2"
|
||||
termcolor = "1.1.0"
|
||||
titlecase = "1.0"
|
||||
toml = "0.5.6"
|
||||
trash = {version = "1.2.0", optional = true}
|
||||
unicode-segmentation = "1.6.0"
|
||||
|
|
|
@ -86,8 +86,10 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||
whole_stream_command(Touch),
|
||||
whole_stream_command(Cpy),
|
||||
whole_stream_command(Date),
|
||||
whole_stream_command(DateListTimeZone),
|
||||
whole_stream_command(DateNow),
|
||||
whole_stream_command(DateUTC),
|
||||
whole_stream_command(DateToTable),
|
||||
whole_stream_command(DateToTimeZone),
|
||||
whole_stream_command(DateFormat),
|
||||
whole_stream_command(Cal),
|
||||
whole_stream_command(Mkdir),
|
||||
|
|
|
@ -154,7 +154,7 @@ pub(crate) use config::{
|
|||
};
|
||||
pub(crate) use count::Count;
|
||||
pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::{Date, DateFormat, DateNow, DateUTC};
|
||||
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use describe::Describe;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::{date_to_value, date_to_value_raw};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
Dictionary, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tagged;
|
||||
use std::fmt::{self, write};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FormatArgs {
|
||||
format: Tagged<String>,
|
||||
raw: Option<bool>,
|
||||
table: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -24,11 +24,11 @@ impl WholeStreamCommand for Date {
|
|||
fn signature(&self) -> Signature {
|
||||
Signature::build("date format")
|
||||
.required("format", SyntaxShape::String, "strftime format")
|
||||
.switch("raw", "print date without tables", Some('r'))
|
||||
.switch("table", "print date in a table", Some('t'))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"format the current date using the given format string."
|
||||
"Format a given date using the given format string."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -38,6 +38,21 @@ impl WholeStreamCommand for Date {
|
|||
) -> Result<OutputStream, ShellError> {
|
||||
format(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Format the current date",
|
||||
example: "date now | date format '%Y.%m.%d_%H %M %S,%z'",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Format the current date and print in a table",
|
||||
example: "date now | date format -t '%Y-%m-%d_%H:%M:%S %z'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn format(
|
||||
|
@ -46,20 +61,46 @@ pub async fn format(
|
|||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (FormatArgs { format, raw }, _) = args.process(®istry).await?;
|
||||
let (FormatArgs { format, table }, input) = args.process(®istry).await?;
|
||||
|
||||
let dt_fmt = format.to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
if let Some(true) = raw {
|
||||
UntaggedValue::string(date_to_value_raw(local, dt_fmt)).into_untagged_value()
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||
..
|
||||
} => {
|
||||
let mut output = String::new();
|
||||
if let Err(fmt::Error) =
|
||||
write(&mut output, format_args!("{}", dt.format(&format.item)))
|
||||
{
|
||||
Err(ShellError::labeled_error(
|
||||
"The date format is invalid",
|
||||
"invalid strftime format",
|
||||
&format.tag,
|
||||
))
|
||||
} else {
|
||||
date_to_value(local, tag, dt_fmt)
|
||||
}
|
||||
let value = if table {
|
||||
let mut indexmap = IndexMap::new();
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(&output).into_value(&tag),
|
||||
);
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
} else {
|
||||
UntaggedValue::string(&output).into_value(&tag)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
ReturnSuccess::value(value)
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a date from pipeline",
|
||||
"requires date input",
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
82
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
82
crates/nu-cli/src/commands/date/list_timezone.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use chrono_tz::TZ_VARIANTS;
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date list-timezone"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date list-timezone")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"List supported time zones."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
list_timezone(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "List all supported time zones",
|
||||
example: "date list-timezone",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "List all supported European time zones",
|
||||
example: "date list-timezone | where timezone =~ Europe",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_timezone(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let list = TZ_VARIANTS.iter().map(move |tz| {
|
||||
let mut entries = IndexMap::new();
|
||||
|
||||
entries.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(tz.name()).into_value(&tag),
|
||||
);
|
||||
|
||||
Ok(ReturnSuccess::Value(
|
||||
UntaggedValue::Row(Dictionary { entries }).into_value(&tag),
|
||||
))
|
||||
});
|
||||
|
||||
Ok(futures::stream::iter(list).to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
pub mod command;
|
||||
pub mod format;
|
||||
pub mod list_timezone;
|
||||
pub mod now;
|
||||
pub mod utc;
|
||||
pub mod to_table;
|
||||
pub mod to_timezone;
|
||||
|
||||
mod utils;
|
||||
mod parser;
|
||||
|
||||
pub use command::Command as Date;
|
||||
pub use format::Date as DateFormat;
|
||||
pub use list_timezone::Date as DateListTimeZone;
|
||||
pub use now::Date as DateNow;
|
||||
pub use utc::Date as DateUTC;
|
||||
pub use to_table::Date as DateToTable;
|
||||
pub use to_timezone::Date as DateToTimeZone;
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
use nu_protocol::{Signature, UntaggedValue};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
|
@ -19,7 +17,7 @@ impl WholeStreamCommand for Date {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date."
|
||||
"Get the current date."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -35,16 +33,12 @@ pub async fn now(
|
|||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
let now: DateTime<Local> = Local::now();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Local> = Local::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
let value = UntaggedValue::date(now.with_timezone(now.offset())).into_value(&tag);
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
|
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
107
crates/nu-cli/src/commands/date/parser.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Modified from chrono::format::scan
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Local, Offset, TimeZone};
|
||||
use chrono_tz::Tz;
|
||||
use titlecase::titlecase;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||
pub enum ParseErrorKind {
|
||||
/// Given field is out of permitted range.
|
||||
OutOfRange,
|
||||
|
||||
/// The input string has some invalid character sequence for given formatting items.
|
||||
Invalid,
|
||||
|
||||
/// The input string has been prematurely ended.
|
||||
TooShort,
|
||||
}
|
||||
|
||||
pub fn datetime_in_timezone(
|
||||
dt: &DateTime<FixedOffset>,
|
||||
s: &str,
|
||||
) -> Result<DateTime<FixedOffset>, ParseErrorKind> {
|
||||
match timezone_offset_internal(s, true, true) {
|
||||
Ok(offset) => match FixedOffset::east_opt(offset) {
|
||||
Some(offset) => Ok(dt.with_timezone(&offset)),
|
||||
None => Err(ParseErrorKind::OutOfRange),
|
||||
},
|
||||
Err(ParseErrorKind::Invalid) => {
|
||||
if s.to_lowercase() == "local" {
|
||||
Ok(dt.with_timezone(Local::now().offset()))
|
||||
} else {
|
||||
let tz: Tz = parse_timezone_internal(s)?;
|
||||
let offset = tz.offset_from_utc_datetime(&dt.naive_utc()).fix();
|
||||
Ok(dt.with_timezone(&offset))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_timezone_internal(s: &str) -> Result<Tz, ParseErrorKind> {
|
||||
if let Ok(tz) = s.parse() {
|
||||
Ok(tz)
|
||||
} else if let Ok(tz) = titlecase(s).parse() {
|
||||
Ok(tz)
|
||||
} else if let Ok(tz) = s.to_uppercase().parse() {
|
||||
Ok(tz)
|
||||
} else {
|
||||
Err(ParseErrorKind::Invalid)
|
||||
}
|
||||
}
|
||||
|
||||
fn timezone_offset_internal(
|
||||
mut s: &str,
|
||||
consume_colon: bool,
|
||||
allow_missing_minutes: bool,
|
||||
) -> Result<i32, ParseErrorKind> {
|
||||
fn digits(s: &str) -> Result<(u8, u8), ParseErrorKind> {
|
||||
let b = s.as_bytes();
|
||||
if b.len() < 2 {
|
||||
Err(ParseErrorKind::TooShort)
|
||||
} else {
|
||||
Ok((b[0], b[1]))
|
||||
}
|
||||
}
|
||||
let negative = match s.as_bytes().first() {
|
||||
Some(&b'+') => false,
|
||||
Some(&b'-') => true,
|
||||
Some(_) => return Err(ParseErrorKind::Invalid),
|
||||
None => return Err(ParseErrorKind::TooShort),
|
||||
};
|
||||
s = &s[1..];
|
||||
|
||||
// hours (00--99)
|
||||
let hours = match digits(s)? {
|
||||
(h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
|
||||
_ => return Err(ParseErrorKind::Invalid),
|
||||
};
|
||||
s = &s[2..];
|
||||
|
||||
// colons (and possibly other separators)
|
||||
if consume_colon {
|
||||
s = s.trim_start_matches(|c: char| c == ':' || c.is_whitespace());
|
||||
}
|
||||
|
||||
// minutes (00--59)
|
||||
// if the next two items are digits then we have to add minutes
|
||||
let minutes = if let Ok(ds) = digits(s) {
|
||||
match ds {
|
||||
(m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
|
||||
(b'6'..=b'9', b'0'..=b'9') => return Err(ParseErrorKind::OutOfRange),
|
||||
_ => return Err(ParseErrorKind::Invalid),
|
||||
}
|
||||
} else if allow_missing_minutes {
|
||||
0
|
||||
} else {
|
||||
return Err(ParseErrorKind::TooShort);
|
||||
};
|
||||
match s.len() {
|
||||
len if len >= 2 => &s[2..],
|
||||
len if len == 0 => s,
|
||||
_ => return Err(ParseErrorKind::TooShort),
|
||||
};
|
||||
|
||||
let seconds = hours * 3600 + minutes * 60;
|
||||
Ok(if negative { -seconds } else { seconds })
|
||||
}
|
113
crates/nu-cli/src/commands/date/to_table.rs
Normal file
113
crates/nu-cli/src/commands/date/to_table.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use chrono::{Datelike, Timelike};
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date to-table"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date to-table")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Print the date in a structured table."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_table(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Print the current date in a table",
|
||||
example: "date now | date to-table",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_table(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let input = args.input;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||
..
|
||||
} => {
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
|
||||
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
|
||||
|
||||
ReturnSuccess::value(value)
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a date from pipeline",
|
||||
"requires date input",
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
118
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
118
crates/nu-cli/src/commands/date/to_timezone.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
use crate::commands::date::parser::{datetime_in_timezone, ParseErrorKind};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DateToTimeZoneArgs {
|
||||
timezone: Tagged<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date to-timezone"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date to-timezone").required(
|
||||
"time zone",
|
||||
SyntaxShape::String,
|
||||
"time zone description",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert a date to a given time zone.
|
||||
|
||||
Use `date list-timezone` to list all supported time zones.
|
||||
"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
to_timezone(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current date in UTC+05:00",
|
||||
example: "date now | date to-timezone +0500",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current local date",
|
||||
example: "date now | date to-timezone local",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the current date in Hawaii",
|
||||
example: "date now | date to-timezone US/Hawaii",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_timezone(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DateToTimeZoneArgs { timezone }, input) = args.process(®istry).await?;
|
||||
|
||||
Ok(input
|
||||
.map(move |value| match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(dt)),
|
||||
..
|
||||
} => match datetime_in_timezone(&dt, &timezone.item) {
|
||||
Ok(dt) => {
|
||||
let value = UntaggedValue::date(dt).into_value(&tag);
|
||||
|
||||
ReturnSuccess::value(value)
|
||||
}
|
||||
Err(e) => Err(ShellError::labeled_error(
|
||||
error_message(e),
|
||||
"invalid time zone",
|
||||
&timezone.tag,
|
||||
)),
|
||||
},
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a date from pipeline",
|
||||
"requires date input",
|
||||
&tag,
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn error_message(err: ParseErrorKind) -> &'static str {
|
||||
match err {
|
||||
ParseErrorKind::Invalid => "The time zone description is invalid",
|
||||
ParseErrorKind::OutOfRange => "The time zone offset is out of range",
|
||||
ParseErrorKind::TooShort => "The format of the time zone is invalid",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Utc};
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::date::utils::date_to_value;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use nu_protocol::Signature;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Date {
|
||||
fn name(&self) -> &str {
|
||||
"date utc"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date utc")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"return the current date in utc."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
utc(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn utc(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let args = args.evaluate_once(®istry).await?;
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
|
||||
let no_fmt = "".to_string();
|
||||
|
||||
let value = {
|
||||
let local: DateTime<Utc> = Utc::now();
|
||||
date_to_value(local, tag, no_fmt)
|
||||
};
|
||||
|
||||
Ok(OutputStream::one(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Date;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Date {})?)
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
use chrono::DateTime;
|
||||
use nu_protocol::{Dictionary, Value};
|
||||
|
||||
use chrono::{Datelike, TimeZone, Timelike};
|
||||
use core::fmt::Display;
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::UntaggedValue;
|
||||
|
||||
pub fn date_to_value_raw<T: TimeZone>(dt: DateTime<T>, dt_format: String) -> String
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let result = dt.format(&dt_format);
|
||||
format!("{}", result)
|
||||
}
|
||||
|
||||
pub fn date_to_value<T: TimeZone>(dt: DateTime<T>, tag: Tag, dt_format: String) -> Value
|
||||
where
|
||||
T::Offset: Display,
|
||||
{
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if dt_format.is_empty() {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
UntaggedValue::int(dt.year()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"month".to_string(),
|
||||
UntaggedValue::int(dt.month()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"day".to_string(),
|
||||
UntaggedValue::int(dt.day()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"hour".to_string(),
|
||||
UntaggedValue::int(dt.hour()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"minute".to_string(),
|
||||
UntaggedValue::int(dt.minute()).into_value(&tag),
|
||||
);
|
||||
indexmap.insert(
|
||||
"second".to_string(),
|
||||
UntaggedValue::int(dt.second()).into_value(&tag),
|
||||
);
|
||||
|
||||
let tz = dt.offset();
|
||||
indexmap.insert(
|
||||
"timezone".to_string(),
|
||||
UntaggedValue::string(format!("{}", tz)).into_value(&tag),
|
||||
);
|
||||
} else {
|
||||
let result = dt.format(&dt_format);
|
||||
indexmap.insert(
|
||||
"formatted".to_string(),
|
||||
UntaggedValue::string(format!("{}", result)).into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
pub(crate) mod shape;
|
||||
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, FixedOffset, Utc};
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
|
@ -75,8 +75,8 @@ pub enum CompareValues {
|
|||
Ints(BigInt, BigInt),
|
||||
Decimals(BigDecimal, BigDecimal),
|
||||
String(String, String),
|
||||
Date(DateTime<Utc>, DateTime<Utc>),
|
||||
DateDuration(DateTime<Utc>, BigInt),
|
||||
Date(DateTime<FixedOffset>, DateTime<FixedOffset>),
|
||||
DateDuration(DateTime<FixedOffset>, BigInt),
|
||||
Booleans(bool, bool),
|
||||
}
|
||||
|
||||
|
@ -94,9 +94,10 @@ impl CompareValues {
|
|||
Span::unknown(),
|
||||
)
|
||||
.expect("Could not convert nushell Duration into chrono Duration.");
|
||||
let right: DateTime<Utc> = Utc::now()
|
||||
let right: DateTime<FixedOffset> = Utc::now()
|
||||
.checked_sub_signed(duration)
|
||||
.expect("Data overflow");
|
||||
.expect("Data overflow")
|
||||
.into();
|
||||
right.cmp(left)
|
||||
}
|
||||
CompareValues::Booleans(left, right) => left.cmp(right),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// use crate::config::{Conf, NuConfig};
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use indexmap::map::IndexMap;
|
||||
use nu_protocol::RangeInclusion;
|
||||
use nu_protocol::{format_primitive, ColumnPath, Dictionary, Primitive, UntaggedValue, Value};
|
||||
|
@ -31,7 +31,7 @@ pub enum InlineShape {
|
|||
ColumnPath(ColumnPath),
|
||||
Pattern(String),
|
||||
Boolean(bool),
|
||||
Date(DateTime<Utc>),
|
||||
Date(DateTime<FixedOffset>),
|
||||
Duration(BigInt),
|
||||
Path(PathBuf),
|
||||
Binary(usize),
|
||||
|
|
|
@ -25,7 +25,7 @@ impl Date {
|
|||
|
||||
let date = date.with_timezone(&chrono::offset::Utc);
|
||||
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(date)))
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(date.into())))
|
||||
}
|
||||
|
||||
pub fn naive_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
|
||||
|
@ -38,7 +38,7 @@ impl Date {
|
|||
})?;
|
||||
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(
|
||||
DateTime::<Utc>::from_utc(date.and_hms(12, 34, 56), Utc),
|
||||
DateTime::<Utc>::from_utc(date.and_hms(12, 34, 56), Utc).into(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use crate::value::range::{Range, RangeInclusion};
|
|||
use crate::ColumnPath;
|
||||
use bigdecimal::BigDecimal;
|
||||
use bigdecimal::FromPrimitive;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, FixedOffset, Utc};
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_source::{AnchorLocation, HasSpan, Span, Spanned, SpannedItem, Tag};
|
||||
|
@ -248,10 +248,11 @@ impl UntaggedValue {
|
|||
|
||||
/// Helper for creating datatime values
|
||||
pub fn system_date(s: SystemTime) -> UntaggedValue {
|
||||
UntaggedValue::Primitive(Primitive::Date(s.into()))
|
||||
let utc: DateTime<Utc> = s.into();
|
||||
UntaggedValue::Primitive(Primitive::Date(utc.into()))
|
||||
}
|
||||
|
||||
pub fn date(d: impl Into<DateTime<Utc>>) -> UntaggedValue {
|
||||
pub fn date(d: impl Into<DateTime<FixedOffset>>) -> UntaggedValue {
|
||||
UntaggedValue::Primitive(Primitive::Date(d.into()))
|
||||
}
|
||||
|
||||
|
@ -924,7 +925,7 @@ pub trait DateTimeExt {
|
|||
fn to_value_create_tag(&self) -> Value;
|
||||
}
|
||||
|
||||
impl DateTimeExt for DateTime<Utc> {
|
||||
impl DateTimeExt for DateTime<FixedOffset> {
|
||||
fn to_value(&self, the_tag: Tag) -> Value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Date(*self)),
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::value::column_path::ColumnPath;
|
|||
use crate::value::range::{Range, RangeInclusion};
|
||||
use crate::value::{serde_bigdecimal, serde_bigint};
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, FixedOffset, Utc};
|
||||
use nu_errors::{ExpectedRange, ShellError};
|
||||
use nu_source::{PrettyDebug, Span, SpannedItem};
|
||||
use num_bigint::BigInt;
|
||||
|
@ -42,8 +42,8 @@ pub enum Primitive {
|
|||
Pattern(String),
|
||||
/// A boolean value
|
||||
Boolean(bool),
|
||||
/// A date value, in UTC
|
||||
Date(DateTime<Utc>),
|
||||
/// A date value
|
||||
Date(DateTime<FixedOffset>),
|
||||
/// A count in the number of nanoseconds
|
||||
#[serde(with = "serde_bigint")]
|
||||
Duration(BigInt),
|
||||
|
@ -385,8 +385,8 @@ pub fn format_duration(duration: &BigInt) -> String {
|
|||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
/// Format a UTC date value into a humanized string (eg "1 week ago" instead of a formal date string)
|
||||
pub fn format_date(d: &DateTime<Utc>) -> String {
|
||||
/// Format a date value into a humanized string (eg "1 week ago" instead of a formal date string)
|
||||
pub fn format_date(d: &DateTime<FixedOffset>) -> String {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
|
||||
let duration = utc.signed_duration_since(*d);
|
||||
|
|
|
@ -40,10 +40,9 @@ pub fn date(input: impl Into<String>) -> Value {
|
|||
let date = NaiveDate::parse_from_str(key.borrow_tagged().item, "%Y-%m-%d")
|
||||
.expect("date from string failed");
|
||||
|
||||
UntaggedValue::Primitive(Primitive::Date(DateTime::<Utc>::from_utc(
|
||||
date.and_hms(12, 34, 56),
|
||||
Utc,
|
||||
)))
|
||||
UntaggedValue::Primitive(Primitive::Date(
|
||||
DateTime::<Utc>::from_utc(date.and_hms(12, 34, 56), Utc).into(),
|
||||
))
|
||||
.into_untagged_value()
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,9 @@ fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Result<Value
|
|||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::UtcDatetime(dt) => UntaggedValue::Primitive(Primitive::Date(*dt)).into_value(&tag),
|
||||
Bson::UtcDatetime(dt) => {
|
||||
UntaggedValue::Primitive(Primitive::Date((*dt).into())).into_value(&tag)
|
||||
}
|
||||
Bson::Symbol(s) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
|
|
|
@ -29,7 +29,7 @@ pub fn value_to_bson_value(v: &Value) -> Result<Bson, ShellError> {
|
|||
.expect("Unimplemented BUG: What about big decimals?"),
|
||||
),
|
||||
UntaggedValue::Primitive(Primitive::Duration(i)) => Bson::String(i.to_string()),
|
||||
UntaggedValue::Primitive(Primitive::Date(d)) => Bson::UtcDatetime(*d),
|
||||
UntaggedValue::Primitive(Primitive::Date(d)) => Bson::UtcDatetime((*d).into()),
|
||||
UntaggedValue::Primitive(Primitive::EndOfStream) => Bson::Null,
|
||||
UntaggedValue::Primitive(Primitive::BeginningOfStream) => Bson::Null,
|
||||
UntaggedValue::Primitive(Primitive::Decimal(d)) => {
|
||||
|
|
Loading…
Reference in a new issue