|
1 | 1 | use crate::{ValR2, ValT}; |
2 | 2 | use alloc::string::{String, ToString}; |
| 3 | +use chrono::DateTime; |
3 | 4 | use jaq_interpret::Error; |
4 | 5 |
|
5 | | -/// Parse an ISO-8601 timestamp string to a number holding the equivalent UNIX timestamp |
| 6 | +/// Parse an ISO 8601 timestamp string to a number holding the equivalent UNIX timestamp |
6 | 7 | /// (seconds elapsed since 1970/01/01). |
| 8 | +/// |
| 9 | +/// Actually, this parses RFC 3339; see |
| 10 | +/// <https://ijmacd.github.io/rfc3339-iso8601/> for differences. |
| 11 | +/// jq also only parses a very restricted subset of ISO 8601. |
7 | 12 | pub fn from_iso8601<V: ValT>(s: &str) -> ValR2<V> { |
8 | | - use time::format_description::well_known::Iso8601; |
9 | | - use time::OffsetDateTime; |
10 | | - let datetime = OffsetDateTime::parse(s, &Iso8601::DEFAULT) |
| 13 | + let dt = DateTime::parse_from_rfc3339(s) |
11 | 14 | .map_err(|e| Error::str(format_args!("cannot parse {s} as ISO-8601 timestamp: {e}")))?; |
12 | | - let epoch_s = datetime.unix_timestamp(); |
13 | 15 | if s.contains('.') { |
14 | | - let seconds = epoch_s as f64 + (f64::from(datetime.nanosecond()) * 1e-9_f64); |
15 | | - Ok(seconds.into()) |
| 16 | + Ok((dt.timestamp_micros() as f64 * 1e-6_f64).into()) |
16 | 17 | } else { |
17 | | - isize::try_from(epoch_s) |
| 18 | + let seconds = dt.timestamp(); |
| 19 | + isize::try_from(seconds) |
18 | 20 | .map(V::from) |
19 | | - .or_else(|_| V::from_num(&epoch_s.to_string())) |
| 21 | + .or_else(|_| V::from_num(&seconds.to_string())) |
20 | 22 | } |
21 | 23 | } |
22 | 24 |
|
23 | | -/// Format a number as an ISO-8601 timestamp string. |
| 25 | +/// Format a number as an ISO 8601 timestamp string. |
24 | 26 | pub fn to_iso8601<V: ValT>(v: &V) -> Result<String, Error<V>> { |
25 | | - use time::format_description::well_known::iso8601; |
26 | | - use time::OffsetDateTime; |
27 | | - const SECONDS_CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT |
28 | | - .set_time_precision(iso8601::TimePrecision::Second { |
29 | | - decimal_digits: None, |
30 | | - }) |
31 | | - .encode(); |
32 | | - |
33 | | - let fail1 = |e| Error::str(format_args!("cannot format {v} as ISO-8601 timestamp: {e}")); |
34 | | - let fail2 = |e| Error::str(format_args!("cannot format {v} as ISO-8601 timestamp: {e}")); |
35 | | - |
| 27 | + let fail = || Error::str(format_args!("cannot format {v} as ISO-8601 timestamp")); |
36 | 28 | if let Some(i) = v.as_isize() { |
37 | | - let iso8601_fmt_s = iso8601::Iso8601::<SECONDS_CONFIG>; |
38 | | - OffsetDateTime::from_unix_timestamp(i as i64) |
39 | | - .map_err(fail1)? |
40 | | - .format(&iso8601_fmt_s) |
41 | | - .map_err(fail2) |
| 29 | + let dt = DateTime::from_timestamp(i as i64, 0).ok_or_else(fail)?; |
| 30 | + Ok(dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()) |
42 | 31 | } else { |
43 | 32 | let f = v.as_f64()?; |
44 | | - let f_ns = (f * 1_000_000_000_f64).round() as i128; |
45 | | - OffsetDateTime::from_unix_timestamp_nanos(f_ns) |
46 | | - .map_err(fail1)? |
47 | | - .format(&iso8601::Iso8601::DEFAULT) |
48 | | - .map_err(fail2) |
| 33 | + let dt = DateTime::from_timestamp_micros((f * 1e6_f64) as i64).ok_or_else(fail)?; |
| 34 | + Ok(dt.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string()) |
49 | 35 | } |
50 | 36 | } |
0 commit comments