forked from extern/nushell
Bugfix/into datetime ignores timezone with format (#15370)
Close #15119 when this is merged # Description > Note: my locale is +1 **Before the changes 🔴**  See the issue for more detailed description of the problem. **After the changes 🟢**  # User-Facing Changes The ``into datetime`` command will now work with formatting and time zones or offset together # Tests + Formatting Fmt + clippy OK **Note about the tests I added**: those tests don't really test my changes, as they were already passing before my changes. Nevertheless I thought I could push them # After Submitting I don't think anything is necessary
This commit is contained in:
@ -18,4 +18,4 @@ A base crate is one with minimal dependencies in our system so that other develo
|
|||||||
|
|
||||||
### Background on nu-cmd-lang
|
### Background on nu-cmd-lang
|
||||||
|
|
||||||
This crate was designed to be a small, concise set of tools or commands that serve as the *foundation layer* of both nu and nushell. These are the core commands needed to have a nice working version of the *nu language* without all of the support that the other commands provide inside nushell. Prior to the launch of this crate all of our commands were housed in the crate *nu-command*. Moving forward we would like to *slowly* break out the commands in nu-command into different crates; the naming and how this will work and where all the commands will be located is a "work in progress" especially now that the *standard library* is starting to become more popular as a location for commands. As time goes on some of our commands written in rust will be migrated to nu and when this happens they will be moved into the *standard library*.
|
This crate was designed to be a small, concise set of tools or commands that serve as the *foundation layer* of both nu and nushell. These are the core commands needed to have a nice working version of the *nu language* without all of the support that the other commands provide inside nushell. Prior to the launch of this crate all of our commands were housed in the crate *nu-command*. Moving forward we would like to *slowly* break out the commands in nu-command into different crates; the naming and how this will work and where all the commands will be located is a "work in progress" especially now that the *standard library* is starting to become more popular as a location for commands. As time goes on some of our commands written in rust will be migrated to nu and when this happens they will be moved into the *standard library*.
|
||||||
|
@ -4,6 +4,9 @@ use human_date_parser::{from_human_time, ParseResult};
|
|||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
const HOUR: i32 = 60 * 60;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
zone_options: Option<Spanned<Zone>>,
|
zone_options: Option<Spanned<Zone>>,
|
||||||
format_options: Option<DatetimeFormat>,
|
format_options: Option<DatetimeFormat>,
|
||||||
@ -272,7 +275,7 @@ impl Command for IntoDatetime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
struct DatetimeFormat(String);
|
struct DatetimeFormat(String);
|
||||||
|
|
||||||
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
@ -322,7 +325,6 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const HOUR: i32 = 60 * 60;
|
|
||||||
|
|
||||||
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
||||||
let timestamp = match input {
|
let timestamp = match input {
|
||||||
@ -403,10 +405,56 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
|
|
||||||
let parse_as_string = |val: &str| {
|
let parse_as_string = |val: &str| {
|
||||||
match dateformat {
|
match dateformat {
|
||||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
Some(dt_format) => match DateTime::parse_from_str(val, &dt_format.0) {
|
||||||
Ok(d) => Value::date ( d, head ),
|
Ok(dt) => {
|
||||||
|
match timezone {
|
||||||
|
None => {
|
||||||
|
Value::date ( dt, head )
|
||||||
|
},
|
||||||
|
Some(Spanned { item, span }) => match item {
|
||||||
|
Zone::Utc => {
|
||||||
|
Value::date ( dt, head )
|
||||||
|
}
|
||||||
|
Zone::Local => {
|
||||||
|
Value::date(dt.with_timezone(&Local).into(), *span)
|
||||||
|
}
|
||||||
|
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
|
||||||
|
Some(eastoffset) => {
|
||||||
|
Value::date(dt.with_timezone(&eastoffset), *span)
|
||||||
|
}
|
||||||
|
None => Value::error(
|
||||||
|
ShellError::DatetimeParseError {
|
||||||
|
msg: input.to_abbreviated_string(&nu_protocol::Config::default()),
|
||||||
|
span: *span,
|
||||||
|
},
|
||||||
|
*span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
|
||||||
|
Some(westoffset) => {
|
||||||
|
Value::date(dt.with_timezone(&westoffset), *span)
|
||||||
|
}
|
||||||
|
None => Value::error(
|
||||||
|
ShellError::DatetimeParseError {
|
||||||
|
msg: input.to_abbreviated_string(&nu_protocol::Config::default()),
|
||||||
|
span: *span,
|
||||||
|
},
|
||||||
|
*span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Zone::Error => Value::error(
|
||||||
|
// This is an argument error, not an input error
|
||||||
|
ShellError::TypeMismatch {
|
||||||
|
err_message: "Invalid timezone or offset".to_string(),
|
||||||
|
span: *span,
|
||||||
|
},
|
||||||
|
*span,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
match NaiveDateTime::parse_from_str(val, &dt.0) {
|
match NaiveDateTime::parse_from_str(val, &dt_format.0) {
|
||||||
Ok(d) => {
|
Ok(d) => {
|
||||||
let dt_fixed =
|
let dt_fixed =
|
||||||
Local.from_local_datetime(&d).single().unwrap_or_default();
|
Local.from_local_datetime(&d).single().unwrap_or_default();
|
||||||
@ -415,7 +463,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
Value::error (
|
Value::error (
|
||||||
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
|
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt_format.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
|
||||||
head,
|
head,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -629,6 +677,49 @@ mod tests {
|
|||||||
assert_eq!(actual, expected)
|
assert_eq!(actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn takes_timestamp_offset_as_int_with_formatting() {
|
||||||
|
let date_int = Value::test_int(1_614_434_140);
|
||||||
|
let timezone_option = Some(Spanned {
|
||||||
|
item: Zone::East(8),
|
||||||
|
span: Span::test_data(),
|
||||||
|
});
|
||||||
|
let fmt_options = Some(DatetimeFormat("%s".to_string()));
|
||||||
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_int, &args, Span::test_data());
|
||||||
|
let expected = Value::date(
|
||||||
|
DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z").unwrap(),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn takes_timestamp_offset_as_int_with_local_timezone() {
|
||||||
|
let date_int = Value::test_int(1_614_434_140);
|
||||||
|
let timezone_option = Some(Spanned {
|
||||||
|
item: Zone::Local,
|
||||||
|
span: Span::test_data(),
|
||||||
|
});
|
||||||
|
let fmt_options = Some(DatetimeFormat("%s".to_string()));
|
||||||
|
let args = Arguments {
|
||||||
|
zone_options: timezone_option,
|
||||||
|
format_options: fmt_options,
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&date_int, &args, Span::test_data());
|
||||||
|
let expected = Value::date(
|
||||||
|
Utc.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
||||||
|
Span::test_data(),
|
||||||
|
);
|
||||||
|
assert_eq!(actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn takes_timestamp() {
|
fn takes_timestamp() {
|
||||||
let date_str = Value::test_string("1614434140000000000");
|
let date_str = Value::test_string("1614434140000000000");
|
||||||
@ -643,7 +734,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let actual = action(&date_str, &args, Span::test_data());
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
let expected = Value::date(
|
let expected = Value::date(
|
||||||
Local.timestamp_opt(1614434140, 0).unwrap().into(),
|
Local.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -662,7 +753,7 @@ mod tests {
|
|||||||
cell_paths: None,
|
cell_paths: None,
|
||||||
};
|
};
|
||||||
let expected = Value::date(
|
let expected = Value::date(
|
||||||
Local.timestamp_opt(1614434140, 0).unwrap().into(),
|
Local.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
let actual = action(&expected, &args, Span::test_data());
|
let actual = action(&expected, &args, Span::test_data());
|
||||||
@ -681,7 +772,7 @@ mod tests {
|
|||||||
let actual = action(&date_str, &args, Span::test_data());
|
let actual = action(&date_str, &args, Span::test_data());
|
||||||
|
|
||||||
let expected = Value::date(
|
let expected = Value::date(
|
||||||
Utc.timestamp_opt(1614434140, 0).unwrap().into(),
|
Utc.timestamp_opt(1_614_434_140, 0).unwrap().into(),
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user