surrealdb_types/value/
datetime.rs1use std::fmt::{self, Display, Formatter};
2use std::ops::Deref;
3use std::str::FromStr;
4
5use chrono::offset::LocalResult;
6use chrono::{DateTime, SecondsFormat, TimeZone, Utc};
7use serde::{Deserialize, Serialize};
8use surrealdb_types_derive::write_sql;
9
10use crate::sql::{SqlFormat, ToSql};
11use crate::utils::escape::QuoteStr;
12
13#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
19pub struct Datetime(pub(crate) DateTime<Utc>);
20
21impl Default for Datetime {
22 fn default() -> Self {
23 Self(Utc::now())
24 }
25}
26
27impl Datetime {
28 pub const MIN_UTC: Self = Datetime(DateTime::<Utc>::MIN_UTC);
30 pub const MAX_UTC: Self = Datetime(DateTime::<Utc>::MAX_UTC);
32
33 pub fn now() -> Self {
35 Self(Utc::now())
36 }
37
38 pub fn into_inner(self) -> DateTime<Utc> {
40 self.0
41 }
42
43 pub fn from_timestamp(seconds: i64, nanos: u32) -> Option<Self> {
45 match Utc.timestamp_opt(seconds, nanos) {
46 LocalResult::Single(v) => Some(Self(v)),
47 LocalResult::Ambiguous(_, _) => None,
48 LocalResult::None => None,
49 }
50 }
51}
52
53impl FromStr for Datetime {
54 type Err = anyhow::Error;
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 Ok(Self(DateTime::parse_from_rfc3339(s)?.to_utc()))
57 }
58}
59
60impl From<DateTime<Utc>> for Datetime {
61 fn from(v: DateTime<Utc>) -> Self {
62 Self(v)
63 }
64}
65
66impl From<Datetime> for DateTime<Utc> {
67 fn from(x: Datetime) -> Self {
68 x.0
69 }
70}
71
72impl Display for Datetime {
73 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
74 self.0.to_rfc3339_opts(SecondsFormat::AutoSi, true).fmt(f)
75 }
76}
77
78impl ToSql for Datetime {
79 fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
80 use crate as surrealdb_types;
81 write_sql!(f, fmt, "d{}", QuoteStr(&self.to_string()));
82 }
83}
84
85impl TryFrom<(i64, u32)> for Datetime {
86 type Error = anyhow::Error;
87
88 fn try_from(v: (i64, u32)) -> Result<Self, Self::Error> {
89 match Utc.timestamp_opt(v.0, v.1) {
90 LocalResult::Single(v) => Ok(Self(v)),
91 err => match err {
92 LocalResult::Single(v) => Ok(Self(v)),
93 LocalResult::Ambiguous(_, _) => {
94 Err(anyhow::anyhow!("Ambiguous timestamp: {}, {}", v.0, v.1))
95 }
96 LocalResult::None => Err(anyhow::anyhow!("Invalid timestamp: {}, {}", v.0, v.1)),
97 },
98 }
99 }
100}
101
102impl Deref for Datetime {
103 type Target = DateTime<Utc>;
104 fn deref(&self) -> &Self::Target {
105 &self.0
106 }
107}
108
109#[cfg(feature = "arbitrary")]
110mod arb {
111 use arbitrary::Arbitrary;
112 use chrono::{FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset, Timelike};
113
114 use super::*;
115
116 impl<'a> Arbitrary<'a> for Datetime {
117 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
118 let date = u.arbitrary::<NaiveDate>()?;
119 let time = u.arbitrary::<NaiveTime>()?;
120 let time = time.with_second(time.second() % 60).expect("0 to 59 is a valid second");
122 let time = time
123 .with_nanosecond(time.nanosecond() % 1_000_000_000)
124 .expect("0 to 999_999_999 is a valid nanosecond");
125
126 let offset = if u.arbitrary()? {
127 Utc.fix()
128 } else {
129 let hour = u.int_in_range(0..=23)?;
130 let minute = u.int_in_range(0..=59)?;
131 if u.arbitrary()? {
132 FixedOffset::west_opt(hour * 3600 + minute * 60)
133 .expect("valid because range was ensured")
134 } else {
135 FixedOffset::east_opt(hour * 3600 + minute * 60)
136 .expect("valid because range was ensured")
137 }
138 };
139
140 let datetime = NaiveDateTime::new(date, time);
141
142 let Some(x) = offset.from_local_datetime(&datetime).earliest() else {
143 return Err(arbitrary::Error::IncorrectFormat);
144 };
145
146 Ok(Datetime(x.with_timezone(&Utc)))
147 }
148 }
149}