mirror of
https://github.com/nushell/nushell.git
synced 2025-01-12 09:18:56 +01:00
Parse timestamps as UTC by default (#5488)
* Parse timestamps as UTC by default * Fix up flags and examples
This commit is contained in:
parent
23b467061b
commit
14d80d54fe
@ -59,29 +59,29 @@ impl Command for SubCommand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into datetime")
|
Signature::build("into datetime")
|
||||||
.switch(
|
|
||||||
"list",
|
|
||||||
"lists strftime cheatsheet",
|
|
||||||
Some('l'),
|
|
||||||
)
|
|
||||||
.named(
|
.named(
|
||||||
"timezone",
|
"timezone",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"Specify timezone if the input is timestamp, like 'UTC/u' or 'LOCAL/l'",
|
"Specify timezone if the input is a Unix timestamp. Valid options: 'UTC' ('u') or 'LOCAL' ('l')",
|
||||||
Some('z'),
|
Some('z'),
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"offset",
|
"offset",
|
||||||
SyntaxShape::Int,
|
SyntaxShape::Int,
|
||||||
"Specify timezone by offset if the input is timestamp, like '+8', '-4', prior than timezone",
|
"Specify timezone by offset from UTC if the input is a Unix timestamp, like '+8', '-4'",
|
||||||
Some('o'),
|
Some('o'),
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"format",
|
"format",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"Specify date and time formatting",
|
"Specify an expected format for parsing strings to datetimes. Use --list to see all possible options",
|
||||||
Some('f'),
|
Some('f'),
|
||||||
)
|
)
|
||||||
|
.switch(
|
||||||
|
"list",
|
||||||
|
"Show all possible variables for use with the --format flag",
|
||||||
|
Some('l'),
|
||||||
|
)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
@ -112,28 +112,40 @@ impl Command for SubCommand {
|
|||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime",
|
description: "Convert to datetime",
|
||||||
example: "'16.11.1984 8:00 am +0000' | into datetime",
|
example: "'27.02.2021 1:55 pm +0000' | into datetime",
|
||||||
result: None,
|
result: Some(Value::Date {
|
||||||
|
val: Utc.timestamp(1614434100, 0).into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime",
|
description: "Convert to datetime",
|
||||||
example: "'2020-08-04T16:39:18+00:00' | into datetime",
|
example: "'2021-02-27T13:55:40+00:00' | into datetime",
|
||||||
result: None,
|
result: Some(Value::Date {
|
||||||
|
val: Utc.timestamp(1614434140, 0).into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert to datetime using a custom format",
|
description: "Convert to datetime using a custom format",
|
||||||
example: "'20200904_163918+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
|
example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
|
||||||
result: None,
|
result: Some(Value::Date {
|
||||||
|
val: Utc.timestamp(1614434140, 0).into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Convert timestamp (no larger than 8e+12) to datetime using a specified timezone",
|
description: "Convert timestamp (no larger than 8e+12) to a UTC datetime",
|
||||||
example: "'1614434140' | into datetime -z 'UTC'",
|
example: "1614434140 | into datetime",
|
||||||
result: None,
|
result: Some(Value::Date {
|
||||||
|
val: Utc.timestamp(1614434140, 0).into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description:
|
description:
|
||||||
"Convert timestamp (no larger than 8e+12) to datetime using a specified timezone offset (between -12 and 12)",
|
"Convert timestamp (no larger than 8e+12) to datetime using a specified timezone offset (between -12 and 12)",
|
||||||
example: "'1614434140' | into datetime -o +9",
|
example: "1614434140 | into datetime -o +9",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -209,69 +221,74 @@ fn action(
|
|||||||
dateformat: &Option<DatetimeFormat>,
|
dateformat: &Option<DatetimeFormat>,
|
||||||
head: Span,
|
head: Span,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
// if timezone is specified, first check if the input is a timestamp.
|
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
||||||
if let Some(tz) = timezone {
|
let timestamp = match input {
|
||||||
|
Value::Int { val, .. } => Ok(*val),
|
||||||
|
Value::String { val, .. } => val.parse::<i64>(),
|
||||||
|
other => {
|
||||||
|
return Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!("Expected string or int, got {} instead", other.get_type()),
|
||||||
|
head,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(ts) = timestamp {
|
||||||
const TIMESTAMP_BOUND: i64 = 8.2e+12 as i64;
|
const TIMESTAMP_BOUND: i64 = 8.2e+12 as i64;
|
||||||
|
const HOUR: i32 = 3600;
|
||||||
|
|
||||||
let ts = match input {
|
if ts.abs() > TIMESTAMP_BOUND {
|
||||||
Value::Int { val, .. } => Ok(*val),
|
return Value::Error {
|
||||||
Value::String { val, .. } => val.parse::<i64>(),
|
error: ShellError::UnsupportedInput(
|
||||||
other => {
|
"Given timestamp is out of range, it should between -8e+12 and 8e+12"
|
||||||
return Value::Error {
|
.to_string(),
|
||||||
error: ShellError::UnsupportedInput(
|
head,
|
||||||
format!("Expected string or int, got {} instead", other.get_type()),
|
),
|
||||||
head,
|
};
|
||||||
),
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(t) = ts {
|
return match timezone {
|
||||||
if t.abs() > TIMESTAMP_BOUND {
|
// default to UTC
|
||||||
return Value::Error {
|
None => Value::Date {
|
||||||
error: ShellError::UnsupportedInput(
|
val: Utc.timestamp(ts, 0).into(),
|
||||||
"Given timestamp is out of range, it should between -8e+12 and 8e+12"
|
span: head,
|
||||||
.to_string(),
|
},
|
||||||
head,
|
Some(Spanned { item, span }) => match item {
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const HOUR: i32 = 3600;
|
|
||||||
let stampout = match tz.item {
|
|
||||||
Zone::Utc => Value::Date {
|
Zone::Utc => Value::Date {
|
||||||
val: Utc.timestamp(t, 0).into(),
|
val: Utc.timestamp(ts, 0).into(),
|
||||||
span: head,
|
span: head,
|
||||||
},
|
},
|
||||||
Zone::Local => Value::Date {
|
Zone::Local => Value::Date {
|
||||||
val: Local.timestamp(t, 0).into(),
|
val: Local.timestamp(ts, 0).into(),
|
||||||
span: head,
|
span: head,
|
||||||
},
|
},
|
||||||
Zone::East(i) => {
|
Zone::East(i) => {
|
||||||
let eastoffset = FixedOffset::east((i as i32) * HOUR);
|
let eastoffset = FixedOffset::east((*i as i32) * HOUR);
|
||||||
Value::Date {
|
Value::Date {
|
||||||
val: eastoffset.timestamp(t, 0),
|
val: eastoffset.timestamp(ts, 0),
|
||||||
span: head,
|
span: head,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Zone::West(i) => {
|
Zone::West(i) => {
|
||||||
let westoffset = FixedOffset::west((i as i32) * HOUR);
|
let westoffset = FixedOffset::west((*i as i32) * HOUR);
|
||||||
Value::Date {
|
Value::Date {
|
||||||
val: westoffset.timestamp(t, 0),
|
val: westoffset.timestamp(ts, 0),
|
||||||
span: head,
|
span: head,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Zone::Error => Value::Error {
|
Zone::Error => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
"Cannot convert given timezone or offset to timestamp".to_string(),
|
"Cannot convert given timezone or offset to timestamp".to_string(),
|
||||||
tz.span,
|
*span,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
return stampout;
|
};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// if it's not, continue and default to the system's local timezone.
|
// If input is not a timestamp, try parsing it as a string
|
||||||
match input {
|
match input {
|
||||||
Value::String { val, span } => {
|
Value::String { val, span } => {
|
||||||
match dateformat {
|
match dateformat {
|
||||||
@ -300,15 +317,9 @@ fn action(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Int { .. } => Value::Error {
|
|
||||||
error: ShellError::UnsupportedInput(
|
|
||||||
"Received integer input but timezone not specified. Did you forget to specify a timezone?".to_string(),
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
other => Value::Error {
|
other => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::UnsupportedInput(
|
||||||
format!("Expected string or int, got {} instead", other.get_type()),
|
format!("Expected string, got {} instead", other.get_type()),
|
||||||
head,
|
head,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -403,6 +414,20 @@ mod tests {
|
|||||||
assert_eq!(actual, expected)
|
assert_eq!(actual, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn takes_timestamp_without_timezone() {
|
||||||
|
let date_str = Value::test_string("1614434140");
|
||||||
|
let timezone_option = None;
|
||||||
|
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
|
||||||
|
|
||||||
|
let expected = Value::Date {
|
||||||
|
val: Utc.timestamp(1614434140, 0).into(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn takes_invalid_timestamp() {
|
fn takes_invalid_timestamp() {
|
||||||
let date_str = Value::test_string("10440970000000");
|
let date_str = Value::test_string("10440970000000");
|
||||||
|
Loading…
Reference in New Issue
Block a user