vrl/compiler/
datetime.rs

1use std::fmt::Debug;
2
3use chrono::format::{Parsed, StrftimeItems, parse};
4use chrono::{DateTime, FixedOffset, Local, Offset, ParseError, TimeZone as _, Utc};
5use chrono_tz::Tz;
6use serde::{Deserialize, Serialize};
7
8/// Timezone reference.
9///
10/// This can refer to any valid timezone as defined in the [TZ database][tzdb], or "local" which
11/// refers to the system local timezone.
12///
13/// [tzdb]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
14#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
15#[serde(try_from = "String", into = "String")]
16pub enum TimeZone {
17    /// System local timezone.
18    #[default]
19    Local,
20
21    /// A named timezone.
22    ///
23    /// Must be a valid name in the [TZ database][tzdb].
24    ///
25    /// [tzdb]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
26    Named(Tz),
27}
28
29/// This is a wrapper trait to allow `TimeZone` types to be passed generically.
30impl TimeZone {
31    /// Parse a date/time string into `DateTime<Utc>`.
32    ///
33    /// # Errors
34    ///
35    /// Returns parse errors from the underlying time parsing functions.
36    pub fn datetime_from_str(&self, s: &str, format: &str) -> Result<DateTime<Utc>, ParseError> {
37        let mut parsed = Parsed::new();
38        parse(&mut parsed, s, StrftimeItems::new(format))?;
39
40        match self {
41            Self::Local => {
42                let local_datetime = parsed.to_datetime_with_timezone(&Local)?;
43                Ok(datetime_to_utc(&local_datetime))
44            }
45            Self::Named(tz) => {
46                let tz_datetime = parsed.to_datetime_with_timezone(tz)?;
47                Ok(datetime_to_utc(&tz_datetime))
48            }
49        }
50    }
51
52    #[must_use]
53    pub fn parse(s: &str) -> Option<Self> {
54        match s {
55            "" | "local" => Some(Self::Local),
56            _ => s.parse::<Tz>().ok().map(Self::Named),
57        }
58    }
59}
60
61/// Convert a timestamp with a non-UTC time zone into UTC
62pub(super) fn datetime_to_utc<TZ: chrono::TimeZone>(ts: &DateTime<TZ>) -> DateTime<Utc> {
63    Utc.timestamp_opt(ts.timestamp(), ts.timestamp_subsec_nanos())
64        .single()
65        .expect("invalid timestamp")
66}
67
68impl From<TimeZone> for String {
69    fn from(tz: TimeZone) -> Self {
70        match tz {
71            TimeZone::Local => "local".to_string(),
72            TimeZone::Named(tz) => tz.name().to_string(),
73        }
74    }
75}
76
77impl TryFrom<String> for TimeZone {
78    type Error = String;
79
80    fn try_from(value: String) -> Result<Self, Self::Error> {
81        match TimeZone::parse(&value) {
82            Some(tz) => Ok(tz),
83            None => Err("No such time zone".to_string()),
84        }
85    }
86}
87
88impl From<TimeZone> for FixedOffset {
89    fn from(tz: TimeZone) -> Self {
90        match tz {
91            TimeZone::Local => *Utc::now().with_timezone(&Local).offset(),
92            TimeZone::Named(tz) => Utc::now().with_timezone(&tz).offset().fix(),
93        }
94    }
95}