Skip to main content

oanda_client/
timestamp.rs

1//! Exact OANDA timestamp boundary.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::{fmt, str::FromStr};
6
7/// UTC instant accepted from RFC3339 or OANDA's fractional Unix-seconds form.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub struct Timestamp(DateTime<Utc>);
10
11impl Timestamp {
12    /// Return the parsed UTC instant.
13    #[must_use]
14    pub fn into_inner(self) -> DateTime<Utc> {
15        self.0
16    }
17}
18
19impl fmt::Display for Timestamp {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(
22            f,
23            "{}",
24            self.0.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)
25        )
26    }
27}
28
29impl FromStr for Timestamp {
30    type Err = String;
31    fn from_str(value: &str) -> Result<Self, Self::Err> {
32        if let Ok(parsed) = DateTime::parse_from_rfc3339(value) {
33            return Ok(Self(parsed.with_timezone(&Utc)));
34        }
35        let (whole, fraction) = value.split_once('.').unwrap_or((value, ""));
36        let seconds = whole
37            .parse::<i64>()
38            .map_err(|_| "invalid OANDA timestamp".to_owned())?;
39        if fraction.len() > 9 || !fraction.bytes().all(|b| b.is_ascii_digit()) {
40            return Err("invalid OANDA timestamp".to_owned());
41        }
42        let mut nanos = fraction.to_owned();
43        nanos.extend(std::iter::repeat_n('0', 9 - fraction.len()));
44        let nanos = nanos
45            .parse::<u32>()
46            .map_err(|_| "invalid OANDA timestamp".to_owned())?;
47        DateTime::from_timestamp(seconds, nanos)
48            .map(Self)
49            .ok_or_else(|| "OANDA timestamp out of range".to_owned())
50    }
51}
52
53impl Serialize for Timestamp {
54    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
55        serializer.serialize_str(&self.to_string())
56    }
57}
58
59impl<'de> Deserialize<'de> for Timestamp {
60    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
61        let value = String::deserialize(deserializer)?;
62        value.parse().map_err(serde::de::Error::custom)
63    }
64}