Skip to main content

surrealdb_expr/val/
duration.rs

1use std::iter::Sum;
2use std::ops::Deref;
3use std::str::FromStr;
4use std::{fmt, ops, time};
5
6use anyhow::Result;
7use common::duration::{
8	NANOSECONDS_PER_MICROSECOND, NANOSECONDS_PER_MILLISECOND, SECONDS_PER_DAY, SECONDS_PER_HOUR,
9	SECONDS_PER_MINUTE, SECONDS_PER_WEEK, SECONDS_PER_YEAR,
10};
11use revision::revisioned;
12use storekey::{BorrowDecode, Encode};
13use surrealdb_types::{SqlFormat, ToSql};
14
15use crate::expr::Error;
16use crate::expr::statements::info::InfoStructure;
17use crate::syn;
18use crate::val::{Datetime, IndexFormat, TryAdd, TrySub, Value};
19
20#[revisioned(revision = 1)]
21#[derive(
22	Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Hash, Ord, Encode, BorrowDecode,
23)]
24#[storekey(format = "()")]
25#[storekey(format = "IndexFormat")]
26pub struct Duration(pub time::Duration);
27
28impl Duration {
29	pub const MAX: Duration = Duration(time::Duration::MAX);
30	pub const ZERO: Duration = Duration(time::Duration::ZERO);
31}
32
33impl From<time::Duration> for Duration {
34	fn from(v: time::Duration) -> Self {
35		Self(v)
36	}
37}
38
39impl From<Duration> for time::Duration {
40	fn from(s: Duration) -> Self {
41		s.0
42	}
43}
44
45impl From<time::Duration> for Value {
46	fn from(value: time::Duration) -> Self {
47		Self::Duration(value.into())
48	}
49}
50
51impl From<Duration> for crate::types::PublicDuration {
52	fn from(value: Duration) -> Self {
53		Self::from(value.0)
54	}
55}
56
57impl From<crate::types::PublicDuration> for Duration {
58	fn from(value: crate::types::PublicDuration) -> Self {
59		Self(value.into_inner())
60	}
61}
62
63impl FromStr for Duration {
64	type Err = ();
65	fn from_str(s: &str) -> Result<Self, Self::Err> {
66		match syn::duration(s) {
67			Ok(v) => Ok(v.into()),
68			_ => Err(()),
69		}
70	}
71}
72
73impl Deref for Duration {
74	type Target = time::Duration;
75	fn deref(&self) -> &Self::Target {
76		&self.0
77	}
78}
79
80impl Duration {
81	/// Create a duration from both seconds and nanoseconds components
82	pub fn new(secs: u64, nanos: u32) -> Duration {
83		time::Duration::new(secs, nanos).into()
84	}
85
86	/// Get the total number of nanoseconds
87	pub fn nanos(&self) -> u128 {
88		self.0.as_nanos()
89	}
90	/// Get the total number of microseconds
91	pub fn micros(&self) -> u128 {
92		self.0.as_micros()
93	}
94	/// Get the total number of milliseconds
95	pub fn millis(&self) -> u128 {
96		self.0.as_millis()
97	}
98	/// Get the total number of seconds
99	pub fn secs(&self) -> u64 {
100		self.0.as_secs()
101	}
102	/// Get the total number of minutes
103	pub fn mins(&self) -> u64 {
104		self.0.as_secs() / SECONDS_PER_MINUTE
105	}
106	/// Get the total number of hours
107	pub fn hours(&self) -> u64 {
108		self.0.as_secs() / SECONDS_PER_HOUR
109	}
110	/// Get the total number of dats
111	pub fn days(&self) -> u64 {
112		self.0.as_secs() / SECONDS_PER_DAY
113	}
114	/// Get the total number of months
115	pub fn weeks(&self) -> u64 {
116		self.0.as_secs() / SECONDS_PER_WEEK
117	}
118	/// Get the total number of years
119	pub fn years(&self) -> u64 {
120		self.0.as_secs() / SECONDS_PER_YEAR
121	}
122	/// Create a duration from nanoseconds
123	pub fn from_nanos(nanos: u64) -> Duration {
124		time::Duration::from_nanos(nanos).into()
125	}
126	/// Create a duration from microseconds
127	pub fn from_micros(micros: u64) -> Duration {
128		time::Duration::from_micros(micros).into()
129	}
130	/// Create a duration from milliseconds
131	pub fn from_millis(millis: u64) -> Duration {
132		time::Duration::from_millis(millis).into()
133	}
134	/// Create a duration from seconds
135	pub fn from_secs(secs: u64) -> Duration {
136		time::Duration::from_secs(secs).into()
137	}
138	/// Create a duration from minutes
139	pub fn from_mins(mins: u64) -> Option<Duration> {
140		mins.checked_mul(SECONDS_PER_MINUTE).map(time::Duration::from_secs).map(|x| x.into())
141	}
142	/// Create a duration from hours
143	pub fn from_hours(hours: u64) -> Option<Duration> {
144		hours.checked_mul(SECONDS_PER_HOUR).map(time::Duration::from_secs).map(|x| x.into())
145	}
146	/// Create a duration from days
147	pub fn from_days(days: u64) -> Option<Duration> {
148		days.checked_mul(SECONDS_PER_DAY).map(time::Duration::from_secs).map(|x| x.into())
149	}
150	/// Create a duration from weeks
151	pub fn from_weeks(weeks: u64) -> Option<Duration> {
152		weeks.checked_mul(SECONDS_PER_WEEK).map(time::Duration::from_secs).map(|x| x.into())
153	}
154}
155
156impl fmt::Display for Duration {
157	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158		// Split up the duration
159		let secs = self.0.as_secs();
160		let nano = self.0.subsec_nanos();
161		// Ensure no empty output
162		if secs == 0 && nano == 0 {
163			return write!(f, "0ns");
164		}
165		// Calculate the total years
166		let year = secs / SECONDS_PER_YEAR;
167		let secs = secs % SECONDS_PER_YEAR;
168		// Calculate the total weeks
169		let week = secs / SECONDS_PER_WEEK;
170		let secs = secs % SECONDS_PER_WEEK;
171		// Calculate the total days
172		let days = secs / SECONDS_PER_DAY;
173		let secs = secs % SECONDS_PER_DAY;
174		// Calculate the total hours
175		let hour = secs / SECONDS_PER_HOUR;
176		let secs = secs % SECONDS_PER_HOUR;
177		// Calculate the total minutes
178		let mins = secs / SECONDS_PER_MINUTE;
179		let secs = secs % SECONDS_PER_MINUTE;
180		// Calculate the total milliseconds
181		let msec = nano / NANOSECONDS_PER_MILLISECOND;
182		let nano = nano % NANOSECONDS_PER_MILLISECOND;
183		// Calculate the total microseconds
184		let usec = nano / NANOSECONDS_PER_MICROSECOND;
185		let nano = nano % NANOSECONDS_PER_MICROSECOND;
186		// Write the different parts
187		if year > 0 {
188			write!(f, "{year}y")?;
189		}
190		if week > 0 {
191			write!(f, "{week}w")?;
192		}
193		if days > 0 {
194			write!(f, "{days}d")?;
195		}
196		if hour > 0 {
197			write!(f, "{hour}h")?;
198		}
199		if mins > 0 {
200			write!(f, "{mins}m")?;
201		}
202		if secs > 0 {
203			write!(f, "{secs}s")?;
204		}
205		if msec > 0 {
206			write!(f, "{msec}ms")?;
207		}
208		if usec > 0 {
209			write!(f, "{usec}µs")?;
210		}
211		if nano > 0 {
212			write!(f, "{nano}ns")?;
213		}
214		Ok(())
215	}
216}
217
218impl ToSql for Duration {
219	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
220		self.to_string().fmt_sql(f, sql_fmt)
221	}
222}
223
224impl ops::Add for Duration {
225	type Output = Self;
226	fn add(self, other: Self) -> Self {
227		match self.0.checked_add(other.0) {
228			Some(v) => Duration::from(v),
229			None => Duration::from(time::Duration::MAX),
230		}
231	}
232}
233
234impl TryAdd for Duration {
235	type Output = Self;
236	fn try_add(self, other: Self) -> Result<Self> {
237		self.0
238			.checked_add(other.0)
239			.ok_or_else(|| Error::ArithmeticOverflow(format!("{self} + {other}")))
240			.map_err(anyhow::Error::new)
241			.map(Duration::from)
242	}
243}
244
245impl<'b> ops::Add<&'b Duration> for &Duration {
246	type Output = Duration;
247	fn add(self, other: &'b Duration) -> Duration {
248		match self.0.checked_add(other.0) {
249			Some(v) => Duration::from(v),
250			None => Duration::from(time::Duration::MAX),
251		}
252	}
253}
254
255impl<'b> TryAdd<&'b Duration> for &Duration {
256	type Output = Duration;
257	fn try_add(self, other: &'b Duration) -> Result<Duration> {
258		self.0
259			.checked_add(other.0)
260			.ok_or_else(|| Error::ArithmeticOverflow(format!("{self} + {other}")))
261			.map_err(anyhow::Error::new)
262			.map(Duration::from)
263	}
264}
265
266impl ops::Sub for Duration {
267	type Output = Self;
268	fn sub(self, other: Self) -> Self {
269		match self.0.checked_sub(other.0) {
270			Some(v) => Duration::from(v),
271			None => Duration::default(),
272		}
273	}
274}
275
276impl TrySub for Duration {
277	type Output = Self;
278	fn try_sub(self, other: Self) -> Result<Self> {
279		self.0
280			.checked_sub(other.0)
281			.ok_or_else(|| Error::ArithmeticNegativeOverflow(format!("{self} - {other}")))
282			.map_err(anyhow::Error::new)
283			.map(Duration::from)
284	}
285}
286
287impl<'b> ops::Sub<&'b Duration> for &Duration {
288	type Output = Duration;
289	fn sub(self, other: &'b Duration) -> Duration {
290		match self.0.checked_sub(other.0) {
291			Some(v) => Duration::from(v),
292			None => Duration::default(),
293		}
294	}
295}
296
297impl<'b> TrySub<&'b Duration> for &Duration {
298	type Output = Duration;
299	fn try_sub(self, other: &'b Duration) -> Result<Duration> {
300		self.0
301			.checked_sub(other.0)
302			.ok_or_else(|| Error::ArithmeticNegativeOverflow(format!("{self} - {other}")))
303			.map_err(anyhow::Error::new)
304			.map(Duration::from)
305	}
306}
307
308impl ops::Add<Datetime> for Duration {
309	type Output = Datetime;
310	fn add(self, other: Datetime) -> Datetime {
311		match chrono::Duration::from_std(self.0) {
312			Ok(d) => match other.0.checked_add_signed(d) {
313				Some(v) => Datetime::from(v),
314				None => Datetime::MAX_UTC,
315			},
316			Err(_) => Datetime::MAX_UTC,
317		}
318	}
319}
320
321impl TryAdd<Datetime> for Duration {
322	type Output = Datetime;
323	fn try_add(self, other: Datetime) -> Result<Datetime> {
324		match chrono::Duration::from_std(self.0) {
325			Ok(d) => match other.0.checked_add_signed(d) {
326				Some(v) => Ok(Datetime::from(v)),
327				None => {
328					Err(anyhow::Error::new(Error::ArithmeticOverflow(format!("{self} + {other}"))))
329				}
330			},
331			Err(_) => {
332				Err(anyhow::Error::new(Error::ArithmeticOverflow(format!("{self} + {other}"))))
333			}
334		}
335	}
336}
337
338impl ops::Sub<Datetime> for Duration {
339	type Output = Datetime;
340	fn sub(self, other: Datetime) -> Datetime {
341		match chrono::Duration::from_std(self.0) {
342			Ok(d) => match other.0.checked_sub_signed(d) {
343				Some(v) => Datetime::from(v),
344				None => Datetime::MIN_UTC,
345			},
346			Err(_) => Datetime::MIN_UTC,
347		}
348	}
349}
350
351impl TrySub<Datetime> for Duration {
352	type Output = Datetime;
353	fn try_sub(self, other: Datetime) -> Result<Datetime> {
354		match chrono::Duration::from_std(self.0) {
355			Ok(d) => match other.0.checked_sub_signed(d) {
356				Some(v) => Ok(Datetime::from(v)),
357				None => Err(anyhow::Error::new(Error::ArithmeticNegativeOverflow(format!(
358					"{self} - {other}"
359				)))),
360			},
361			Err(_) => Err(anyhow::Error::new(Error::ArithmeticNegativeOverflow(format!(
362				"{self} - {other}"
363			)))),
364		}
365	}
366}
367
368impl Sum<Self> for Duration {
369	fn sum<I>(iter: I) -> Duration
370	where
371		I: Iterator<Item = Self>,
372	{
373		iter.fold(Duration::default(), |a, b| a + b)
374	}
375}
376
377impl<'a> Sum<&'a Self> for Duration {
378	fn sum<I>(iter: I) -> Duration
379	where
380		I: Iterator<Item = &'a Self>,
381	{
382		iter.fold(Duration::default(), |a, b| &a + b)
383	}
384}
385
386impl InfoStructure for Duration {
387	fn structure(self) -> Value {
388		self.to_string().into()
389	}
390}