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