nu_command/date/
to_timezone.rs

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) // https://github.com/nushell/nushell/issues/7032
18            .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        // This doesn't match explicit nulls
53        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}