1use super::parser::datetime_in_timezone;
2use crate::date::utils::parse_date_from_string;
3use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone};
4use nu_engine::command_prelude::*;
5
6#[derive(Clone)]
7pub struct DateToTimezone;
8
9impl Command for DateToTimezone {
10 fn name(&self) -> &str {
11 "date to-timezone"
12 }
13
14 fn signature(&self) -> Signature {
15 Signature::build("date to-timezone")
16 .input_output_types(vec![(Type::Date, Type::Date), (Type::String, Type::Date)])
17 .allow_variants_without_examples(true) .required("time zone", SyntaxShape::String, "Time zone description.")
19 .category(Category::Date)
20 }
21
22 fn description(&self) -> &str {
23 "Convert a date to a given time zone."
24 }
25
26 fn extra_description(&self) -> &str {
27 "Use 'date list-timezone' to list all supported time zones."
28 }
29
30 fn search_terms(&self) -> Vec<&str> {
31 vec![
32 "tz",
33 "transform",
34 "convert",
35 "UTC",
36 "GMT",
37 "list",
38 "list-timezone",
39 ]
40 }
41
42 fn run(
43 &self,
44 engine_state: &EngineState,
45 stack: &mut Stack,
46 call: &Call,
47 input: PipelineData,
48 ) -> Result<PipelineData, ShellError> {
49 let head = call.head;
50 let timezone: Spanned<String> = call.req(engine_state, stack, 0)?;
51
52 if matches!(input, PipelineData::Empty) {
54 return Err(ShellError::PipelineEmpty { dst_span: head });
55 }
56 input.map(
57 move |value| helper(value, head, &timezone),
58 engine_state.signals(),
59 )
60 }
61
62 fn examples(&self) -> Vec<Example> {
63 let example_result_1 = || match FixedOffset::east_opt(5 * 3600)
64 .expect("to timezone: help example is invalid")
65 .with_ymd_and_hms(2020, 10, 10, 13, 00, 00)
66 {
67 LocalResult::Single(dt) => Some(Value::date(dt, Span::test_data())),
68 _ => panic!("to timezone: help example is invalid"),
69 };
70
71 vec![
72 Example {
73 description: "Get the current date in UTC+05:00.",
74 example: "date now | date to-timezone '+0500'",
75 result: None,
76 },
77 Example {
78 description: "Get the current date in the local time zone.",
79 example: "date now | date to-timezone local",
80 result: None,
81 },
82 Example {
83 description: "Get the current date in Hawaii.",
84 example: "date now | date to-timezone US/Hawaii",
85 result: None,
86 },
87 Example {
88 description: "Get a date in a different time zone, from a string.",
89 example: r#""2020-10-10 10:00:00 +02:00" | date to-timezone "+0500""#,
90 result: example_result_1(),
91 },
92 Example {
93 description: "Get a date in a different time zone, from a datetime.",
94 example: r#""2020-10-10 10:00:00 +02:00" | into datetime | date to-timezone "+0500""#,
95 result: example_result_1(),
96 },
97 ]
98 }
99}
100
101fn helper(value: Value, head: Span, timezone: &Spanned<String>) -> Value {
102 let val_span = value.span();
103 match value {
104 Value::Date { val, .. } => _to_timezone(val, timezone, head),
105 Value::String { val, .. } => {
106 let time = parse_date_from_string(&val, val_span);
107 match time {
108 Ok(dt) => _to_timezone(dt, timezone, head),
109 Err(e) => e,
110 }
111 }
112
113 Value::Nothing { .. } => {
114 let dt = Local::now();
115 _to_timezone(dt.with_timezone(dt.offset()), timezone, head)
116 }
117 _ => Value::error(
118 ShellError::OnlySupportsThisInputType {
119 exp_input_type: "date, string (that represents datetime), or nothing".into(),
120 wrong_type: value.get_type().to_string(),
121 dst_span: head,
122 src_span: val_span,
123 },
124 head,
125 ),
126 }
127}
128
129fn _to_timezone(dt: DateTime<FixedOffset>, timezone: &Spanned<String>, span: Span) -> Value {
130 match datetime_in_timezone(&dt, timezone.item.as_str()) {
131 Ok(dt) => Value::date(dt, span),
132 Err(_) => Value::error(
133 ShellError::TypeMismatch {
134 err_message: String::from("invalid time zone"),
135 span: timezone.span,
136 },
137 timezone.span,
138 ),
139 }
140}
141
142#[cfg(test)]
143mod test {
144 use super::*;
145
146 #[test]
147 fn test_examples() {
148 use crate::test_examples;
149
150 test_examples(DateToTimezone {})
151 }
152}