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
40pub(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 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 let example = now
297 .format("%:z") .to_string()
299 .get(0..3) .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}