Skip to main content

nu_command/date/
utils.rs

1use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone};
2use nu_protocol::{ShellError, Span, Value, record};
3
4pub(crate) fn parse_date_from_string(
5    input: &str,
6    span: Span,
7) -> Result<DateTime<FixedOffset>, Value> {
8    match dtparse::parse(input) {
9        Ok((native_dt, fixed_offset)) => {
10            let offset = match fixed_offset {
11                Some(offset) => offset,
12                None => *Local
13                    .from_local_datetime(&native_dt)
14                    .single()
15                    .unwrap_or_default()
16                    .offset(),
17            };
18            match offset.from_local_datetime(&native_dt) {
19                LocalResult::Single(d) => Ok(d),
20                LocalResult::Ambiguous(d, _) => Ok(d),
21                LocalResult::None => Err(Value::error(
22                    ShellError::DatetimeParseError {
23                        msg: input.into(),
24                        span,
25                    },
26                    span,
27                )),
28            }
29        }
30        Err(_) => Err(Value::error(
31            ShellError::DatetimeParseError {
32                msg: input.into(),
33                span,
34            },
35            span,
36        )),
37    }
38}
39
40/// Generates a table containing available datetime format specifiers
41///
42/// # Arguments
43/// * `head` - use the call's head
44/// * `show_parse_only_formats` - whether parse-only format specifiers (that can't be outputted) should be shown. Should only be used for `into datetime`, not `format date`
45pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool) -> Value {
46    let now = Local::now();
47
48    struct FormatSpecification<'a> {
49        spec: &'a str,
50        description: &'a str,
51    }
52
53    let specifications = [
54        FormatSpecification {
55            spec: "%Y",
56            description: "The full proleptic Gregorian year, zero-padded to 4 digits.",
57        },
58        FormatSpecification {
59            spec: "%C",
60            description: "The proleptic Gregorian year divided by 100, zero-padded to 2 digits.",
61        },
62        FormatSpecification {
63            spec: "%y",
64            description: "The proleptic Gregorian year modulo 100, zero-padded to 2 digits.",
65        },
66        FormatSpecification {
67            spec: "%m",
68            description: "Month number (01--12), zero-padded to 2 digits.",
69        },
70        FormatSpecification {
71            spec: "%b",
72            description: "Abbreviated month name. Always 3 letters.",
73        },
74        FormatSpecification {
75            spec: "%B",
76            description: "Full month name. Also accepts corresponding abbreviation in parsing.",
77        },
78        FormatSpecification {
79            spec: "%h",
80            description: "Same as %b.",
81        },
82        FormatSpecification {
83            spec: "%d",
84            description: "Day number (01--31), zero-padded to 2 digits.",
85        },
86        FormatSpecification {
87            spec: "%e",
88            description: "Same as %d but space-padded. Same as %_d.",
89        },
90        FormatSpecification {
91            spec: "%a",
92            description: "Abbreviated weekday name. Always 3 letters.",
93        },
94        FormatSpecification {
95            spec: "%A",
96            description: "Full weekday name. Also accepts corresponding abbreviation in parsing.",
97        },
98        FormatSpecification {
99            spec: "%w",
100            description: "Sunday = 0, Monday = 1, ..., Saturday = 6.",
101        },
102        FormatSpecification {
103            spec: "%u",
104            description: "Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)",
105        },
106        FormatSpecification {
107            spec: "%U",
108            description: "Week number starting with Sunday (00--53), zero-padded to 2 digits.",
109        },
110        FormatSpecification {
111            spec: "%W",
112            description: "Same as %U, but week 1 starts with the first Monday in that year instead.",
113        },
114        FormatSpecification {
115            spec: "%G",
116            description: "Same as %Y but uses the year number in ISO 8601 week date.",
117        },
118        FormatSpecification {
119            spec: "%g",
120            description: "Same as %y but uses the year number in ISO 8601 week date.",
121        },
122        FormatSpecification {
123            spec: "%V",
124            description: "Same as %U but uses the week number in ISO 8601 week date (01--53).",
125        },
126        FormatSpecification {
127            spec: "%j",
128            description: "Day of the year (001--366), zero-padded to 3 digits.",
129        },
130        FormatSpecification {
131            spec: "%D",
132            description: "Month-day-year format. Same as %m/%d/%y.",
133        },
134        FormatSpecification {
135            spec: "%x",
136            description: "Locale's date representation (e.g., 12/31/99).",
137        },
138        FormatSpecification {
139            spec: "%F",
140            description: "Year-month-day format (ISO 8601). Same as %Y-%m-%d.",
141        },
142        FormatSpecification {
143            spec: "%v",
144            description: "Day-month-year format. Same as %e-%b-%Y.",
145        },
146        FormatSpecification {
147            spec: "%H",
148            description: "Hour number (00--23), zero-padded to 2 digits.",
149        },
150        FormatSpecification {
151            spec: "%k",
152            description: "Same as %H but space-padded. Same as %_H.",
153        },
154        FormatSpecification {
155            spec: "%I",
156            description: "Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.",
157        },
158        FormatSpecification {
159            spec: "%l",
160            description: "Same as %I but space-padded. Same as %_I.",
161        },
162        FormatSpecification {
163            spec: "%P",
164            description: "am or pm in 12-hour clocks.",
165        },
166        FormatSpecification {
167            spec: "%p",
168            description: "AM or PM in 12-hour clocks.",
169        },
170        FormatSpecification {
171            spec: "%M",
172            description: "Minute number (00--59), zero-padded to 2 digits.",
173        },
174        FormatSpecification {
175            spec: "%S",
176            description: "Second number (00--60), zero-padded to 2 digits.",
177        },
178        FormatSpecification {
179            spec: "%f",
180            description: "The fractional seconds (in nanoseconds) since last whole second.",
181        },
182        FormatSpecification {
183            spec: "%.f",
184            description: "Similar to .%f but left-aligned. These all consume the leading dot.",
185        },
186        FormatSpecification {
187            spec: "%.3f",
188            description: "Similar to .%f but left-aligned but fixed to a length of 3.",
189        },
190        FormatSpecification {
191            spec: "%.6f",
192            description: "Similar to .%f but left-aligned but fixed to a length of 6.",
193        },
194        FormatSpecification {
195            spec: "%.9f",
196            description: "Similar to .%f but left-aligned but fixed to a length of 9.",
197        },
198        FormatSpecification {
199            spec: "%3f",
200            description: "Similar to %.3f but without the leading dot.",
201        },
202        FormatSpecification {
203            spec: "%6f",
204            description: "Similar to %.6f but without the leading dot.",
205        },
206        FormatSpecification {
207            spec: "%9f",
208            description: "Similar to %.9f but without the leading dot.",
209        },
210        FormatSpecification {
211            spec: "%R",
212            description: "Hour-minute format. Same as %H:%M.",
213        },
214        FormatSpecification {
215            spec: "%T",
216            description: "Hour-minute-second format. Same as %H:%M:%S.",
217        },
218        FormatSpecification {
219            spec: "%X",
220            description: "Locale's time representation (e.g., 23:13:48).",
221        },
222        FormatSpecification {
223            spec: "%r",
224            description: "Hour-minute-second format in 12-hour clocks. Same as %I:%M:%S %p.",
225        },
226        FormatSpecification {
227            spec: "%Z",
228            description: "Local time zone name. Skips all non-whitespace characters during parsing.",
229        },
230        FormatSpecification {
231            spec: "%z",
232            description: "Offset from the local time to UTC (with UTC being +0000).",
233        },
234        FormatSpecification {
235            spec: "%:z",
236            description: "Same as %z but with a colon.",
237        },
238        FormatSpecification {
239            spec: "%c",
240            description: "Locale's date and time (e.g., Thu Mar 3 23:05:25 2005).",
241        },
242        FormatSpecification {
243            spec: "%+",
244            description: "ISO 8601 / RFC 3339 date & time format.",
245        },
246        FormatSpecification {
247            spec: "%s",
248            description: "UNIX timestamp, the number of seconds since 1970-01-01",
249        },
250        FormatSpecification {
251            spec: "%J",
252            description: "Joined date format. Same as %Y%m%d.",
253        },
254        FormatSpecification {
255            spec: "%Q",
256            description: "Sequential time format. Same as %H%M%S.",
257        },
258        FormatSpecification {
259            spec: "%t",
260            description: "Literal tab (\\t).",
261        },
262        FormatSpecification {
263            spec: "%n",
264            description: "Literal newline (\\n).",
265        },
266        FormatSpecification {
267            spec: "%%",
268            description: "Literal percent sign.",
269        },
270    ];
271
272    let mut records = specifications
273        .iter()
274        .map(|s| {
275            // Handle custom format specifiers that aren't supported by chrono
276            let example = match s.spec {
277                "%J" => now.format("%Y%m%d").to_string(),
278                "%Q" => now.format("%H%M%S").to_string(),
279                _ => now.format(s.spec).to_string(),
280            };
281
282            Value::record(
283                record! {
284                    "Specification" => Value::string(s.spec, head),
285                    "Example" => Value::string(example, head),
286                    "Description" => Value::string(s.description, head),
287                },
288                head,
289            )
290        })
291        .collect::<Vec<Value>>();
292
293    if show_parse_only_formats {
294        // now.format("%#z") will panic since it is parse-only
295        // so here we emulate how it will look:
296        let example = now
297            .format("%:z") // e.g. +09:30
298            .to_string()
299            .get(0..3) // +09:30 -> +09
300            .unwrap_or("")
301            .to_string();
302
303        let description = "Parsing only: Same as %z but allows minutes to be missing or present.";
304
305        records.push(Value::record(
306            record! {
307                "Specification" => Value::string("%#z", head),
308                "Example" => Value::string(example, head),
309                "Description" => Value::string(description, head),
310            },
311            head,
312        ));
313    }
314
315    Value::list(records, head)
316}