datetime/
interval.rs

1//! Elapsed time (duration) between two fixed [`DateTime`]s.
2//!
3//! A [`TimeInterval`] represents elapsed time, or "duration", and is the struct provided for doing
4//! "timestamp math".
5
6use std::ops::Add;
7use std::ops::AddAssign;
8use std::ops::Div;
9use std::ops::Mul;
10use std::ops::Sub;
11use std::ops::SubAssign;
12
13use crate::DateTime;
14
15#[cfg(feature = "macros")]
16#[doc(hidden)]
17pub mod __private_api {
18  pub use datetime_rs_macros::nanoseconds;
19}
20
21/// Construct a [`TimeInterval`] from a domain-specific language.
22///
23/// The language is approximately: `[+-]? ([0-9]+d)? ([0-9]+h)/ [0-9]+m)? ([0-9]+(\.[0-9]+)?s)?`.
24///
25/// ## Examples
26///
27/// ```
28/// use datetime::interval::TimeInterval;
29/// use datetime::time_interval;
30///
31/// assert_eq!(time_interval!(5m 30s), TimeInterval::new(330, 0));
32/// assert_eq!(time_interval!(-1h 30m), TimeInterval::new(-5_400, 0));
33/// assert_eq!(time_interval!(10.5s), TimeInterval::new(10, 500_000_000));
34/// ```
35#[macro_export]
36#[cfg(feature = "macros")]
37#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
38macro_rules! time_interval {
39  ($($interval:tt)+) => { const {
40    $crate::interval::TimeInterval::from_nanoseconds(
41      $crate::interval::__private_api::nanoseconds!($($interval)*)
42    )
43  }}
44}
45
46/// An interval or duration of time between two [`DateTime`]s.
47///
48/// The easiest way to create a [`TimeInterval`] is often with the [`time_interval`] macro, which
49/// uses a domain-specific language:
50///
51/// ## Examples
52///
53/// ```
54/// use datetime::interval::TimeInterval;
55/// use datetime::time_interval;
56///
57/// assert_eq!(time_interval!(5m 30s), TimeInterval::new(330, 0));
58/// assert_eq!(time_interval!(-1h 30m), TimeInterval::new(-5_400, 0));
59/// assert_eq!(time_interval!(10.5s), TimeInterval::new(10, 500_000_000));
60/// ```
61#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
62pub struct TimeInterval {
63  seconds: i64,
64  nanos: u32,
65}
66
67impl TimeInterval {
68  /// Create a new [`TimeInterval`] from seconds and nanoseconds.
69  pub const fn new(seconds: i64, nanos: u32) -> Self {
70    Self { seconds, nanos }
71  }
72
73  /// Create a new [`TimeInterval`] from a value in milliseconds.
74  pub const fn from_milliseconds(millis: i64) -> Self {
75    Self::new(millis.div_euclid(1_000), millis.rem_euclid(1_000) as u32 * 1_000_000)
76  }
77
78  /// Create a new [`TimeInterval`] from a value in microseconds.
79  pub const fn from_microseconds(micros: i64) -> Self {
80    Self::new(micros.div_euclid(1_000_000), micros.rem_euclid(1_000_000) as u32 * 1_000)
81  }
82
83  /// Create a new [`TimeInterval`] from a value in nanoseconds.
84  pub const fn from_nanoseconds(nanos: i128) -> Self {
85    Self::new(nanos.div_euclid(1_000_000_000) as i64, nanos.rem_euclid(1_000_000_000) as u32)
86  }
87
88  /// The number of seconds this interval represents.
89  ///
90  /// Note that the nanoseconds value is always positive, even if seconds is negative. For example,
91  /// an interval representing -2.5 seconds will be represented as -3 seconds and 500,000,000
92  /// nanos.
93  pub const fn seconds(&self) -> i64 {
94    self.seconds
95  }
96
97  /// The number of nanoseconds this interval represents.
98  ///
99  /// Note that the nanoseconds value is always positive, even if seconds is negative. For example,
100  /// an interval representing -2.5 seconds will be represented as -3 seconds and 500,000,000
101  /// nanos.
102  pub const fn nanoseconds(&self) -> u32 {
103    self.nanos
104  }
105
106  /// The number of milliseconds this interval represents.
107  pub const fn as_milliseconds(&self) -> i64 {
108    self.seconds * 1_000 + (self.nanos / 1_000_000) as i64
109  }
110
111  /// The number of microseconds this interval represents.
112  pub const fn as_microseconds(&self) -> i64 {
113    self.seconds * 1_000_000 + (self.nanos / 1_000) as i64
114  }
115
116  /// The number of nanoseconds this interval represents.
117  pub const fn as_nanoseconds(&self) -> i128 {
118    self.seconds as i128 * 1_000_000_000 + self.nanos as i128
119  }
120}
121
122impl Add<TimeInterval> for DateTime {
123  type Output = DateTime;
124
125  fn add(self, rhs: TimeInterval) -> Self::Output {
126    let seconds = self.seconds + rhs.seconds + ((self.nanos + rhs.nanos) / 1_000_000_000) as i64;
127    let nanos = (self.nanos + rhs.nanos) % 1_000_000_000;
128    Self {
129      seconds,
130      nanos,
131      #[cfg(feature = "tz")]
132      tz: self.tz,
133    }
134  }
135}
136
137impl AddAssign<TimeInterval> for DateTime {
138  fn add_assign(&mut self, rhs: TimeInterval) {
139    self.seconds += rhs.seconds;
140    self.nanos += rhs.nanos;
141    while self.nanos >= 1_000_000_000 {
142      self.seconds += 1;
143      self.nanos -= 1_000_000_000;
144    }
145  }
146}
147
148impl Sub<TimeInterval> for DateTime {
149  type Output = DateTime;
150
151  #[allow(clippy::suspicious_arithmetic_impl)]
152  fn sub(self, rhs: TimeInterval) -> Self::Output {
153    let mut seconds = self.seconds - rhs.seconds;
154    let nanos = self.nanos.checked_sub(rhs.nanos).unwrap_or_else(|| {
155      seconds -= 1;
156      self.nanos + 1_000_000_000 - rhs.nanos
157    });
158    Self {
159      seconds,
160      nanos,
161      #[cfg(feature = "tz")]
162      tz: self.tz,
163    }
164  }
165}
166
167impl SubAssign<TimeInterval> for DateTime {
168  fn sub_assign(&mut self, rhs: TimeInterval) {
169    self.seconds -= rhs.seconds;
170    match self.nanos >= rhs.nanos {
171      true => self.nanos -= rhs.nanos,
172      false => {
173        self.nanos += 1_000_000_000;
174        self.seconds -= 1;
175        self.nanos -= rhs.nanos;
176      },
177    }
178  }
179}
180
181impl Sub for DateTime {
182  type Output = TimeInterval;
183
184  #[allow(clippy::suspicious_arithmetic_impl)]
185  fn sub(self, rhs: Self) -> Self::Output {
186    let mut seconds = self.seconds - rhs.seconds;
187    let nanos = self.nanos.checked_sub(rhs.nanos).unwrap_or_else(|| {
188      seconds -= 1;
189      self.nanos + 1_000_000_000 - rhs.nanos
190    });
191    TimeInterval { seconds, nanos }
192  }
193}
194
195impl<I: Into<i128>> Mul<I> for TimeInterval {
196  type Output = Self;
197
198  fn mul(self, rhs: I) -> Self::Output {
199    Self::from_nanoseconds(self.as_nanoseconds() * rhs.into())
200  }
201}
202
203impl<I: Into<i128>> Div<I> for TimeInterval {
204  type Output = Self;
205
206  fn div(self, rhs: I) -> Self::Output {
207    Self::from_nanoseconds(self.as_nanoseconds() / rhs.into())
208  }
209}
210
211impl Div for TimeInterval {
212  type Output = f64;
213
214  fn div(self, rhs: Self) -> Self::Output {
215    self.as_nanoseconds() as f64 / rhs.as_nanoseconds() as f64
216  }
217}
218
219#[cfg(feature = "syn")]
220mod syn {
221  use datetime_rs_codegen::Delta;
222  use syn::Result;
223  use syn::parse::Parse;
224  use syn::parse::ParseStream;
225
226  use super::TimeInterval;
227
228  #[cfg_attr(docsrs, doc(cfg(feature = "syn")))]
229  impl Parse for TimeInterval {
230    fn parse(input: ParseStream) -> Result<Self> {
231      let delta = Delta::parse(input)?;
232      Ok(Self::new(delta.seconds(), delta.nanos()))
233    }
234  }
235
236  #[cfg(test)]
237  mod tests {
238    use assert2::check;
239    use quote::quote;
240
241    use super::*;
242
243    #[test]
244    fn test_parse() -> Result<()> {
245      check!(syn::parse2::<TimeInterval>(quote! { 15m 30s })? == time_interval!(15m 30s));
246      check!(syn::parse2::<TimeInterval>(quote! { -1h 15m })? == time_interval!(-1h 15m));
247      Ok(())
248    }
249  }
250}
251
252#[cfg(test)]
253mod tests {
254  use assert2::check;
255
256  use super::*;
257  use crate::DateTime;
258  use crate::datetime;
259  use crate::time_interval;
260
261  #[test]
262  fn test_interval_macro() {
263    check!(time_interval!(1d) == TimeInterval::new(86_400, 0));
264    check!(time_interval!(2h) == TimeInterval::new(7_200, 0));
265    check!(time_interval!(1m) == TimeInterval::new(60, 0));
266    check!(time_interval!(1h30m) == TimeInterval::new(5_400, 0));
267    check!(time_interval!(1h 30m) == TimeInterval::new(5_400, 0));
268    check!(time_interval!(-1h 30m) == TimeInterval::new(-5_400, 0));
269    check!(time_interval!(1m 30s) == TimeInterval::new(90, 0));
270    check!(time_interval!(-1m 20s) == TimeInterval::new(-80, 0));
271    check!(time_interval!(-20s) == TimeInterval::new(-20, 0));
272    check!(time_interval!(-1.25s) == TimeInterval::new(-2, 750_000_000));
273    check!(time_interval!(1m 1.5s) == TimeInterval::new(61, 500_000_000));
274    check!(time_interval!(-0.5s) == TimeInterval::new(-1, 500_000_000));
275    check!(time_interval!(0.5s) == TimeInterval::new(0, 500_000_000));
276  }
277
278  #[test]
279  fn test_from_fractionals() {
280    for (millis, secs, nanos) in [(2_400, 2, 400_000_000), (-2_400, -3, 600_000_000)] {
281      let interval = TimeInterval::from_milliseconds(millis);
282      check!(interval.seconds() == secs);
283      check!(interval.nanoseconds() == nanos);
284    }
285    for (micros, secs, nanos) in [(2_400_000, 2, 400_000_000), (-2_400_000, -3, 600_000_000)] {
286      let interval = TimeInterval::from_microseconds(micros);
287      check!(interval.seconds() == secs);
288      check!(interval.nanoseconds() == nanos);
289    }
290    for (ns, s, n) in [(2_400_000_000, 2, 400_000_000), (-2_400_000_000, -3, 600_000_000)] {
291      let interval = TimeInterval::from_nanoseconds(ns);
292      check!(interval.seconds() == s);
293      check!(interval.nanoseconds() == n);
294    }
295  }
296
297  #[test]
298  fn test_add() {
299    check!(
300      datetime! { 2012-04-21 11:00:00 } + TimeInterval::new(3600, 0)
301        == datetime! { 2012-04-21 12:00:00 }
302    );
303    check!(
304      datetime! { 2012-04-21 11:00:00 } + TimeInterval::new(1800, 0)
305        == datetime! { 2012-04-21 11:30:00 }
306    );
307    check!(
308      datetime! { 2012-04-21 11:00:00 } + TimeInterval::new(0, 500_000_000)
309        == DateTime::ymd(2012, 4, 21).hms(11, 0, 0).nanos(500_000_000).build()
310    );
311    let incr = datetime! { 2012-04-21 11:00:00 }
312      + TimeInterval::new(0, 500_000_000)
313      + TimeInterval::new(0, 500_000_000);
314    check!(incr.seconds % 10 == 1);
315    check!(incr.nanos == 0);
316  }
317
318  #[test]
319  fn test_add_assign() {
320    let mut dt = datetime! { 2012-04-21 11:00:00 };
321    dt += TimeInterval::new(3600, 0);
322    check!(dt == datetime! { 2012-04-21 12:00:00 });
323    dt += TimeInterval::new(0, 750_000_000);
324    dt += TimeInterval::new(0, 250_000_000);
325    check!(dt == datetime! { 2012-04-21 12:00:01 });
326  }
327
328  #[test]
329  fn test_sub() {
330    check!(
331      datetime! { 2012-04-21 11:00:00 } - TimeInterval::new(3600, 0)
332        == datetime! { 2012-04-21 10:00:00 }
333    );
334    check!(
335      datetime! { 2012-04-21 11:00:00 } - TimeInterval::new(0, 500_000_000)
336        == DateTime::ymd(2012, 4, 21).hms(10, 59, 59).nanos(500_000_000).build()
337    );
338  }
339
340  #[test]
341  fn test_sub_assign() {
342    let mut dt = datetime! { 2012-04-21 11:00:00 };
343    dt -= TimeInterval::new(3600, 0);
344    check!(dt == datetime! { 2012-04-21 10:00:00 });
345    dt -= TimeInterval::new(0, 750_000_000);
346    dt -= TimeInterval::new(0, 350_000_000);
347    dt -= TimeInterval::new(0, 900_000_000);
348    check!(dt == datetime! { 2012-04-21 09:59:58 });
349  }
350
351  #[test]
352  fn test_sub_dt() {
353    check!(
354      datetime! { 2012-04-21 11:00:00 } - datetime! { 2012-04-21 10:00:00 }
355        == TimeInterval::new(3600, 0)
356    );
357    check!(
358      datetime! { 2012-04-21 11:00:00 } - datetime! { 2012-04-21 12:00:00 }
359        == TimeInterval::new(-3600, 0)
360    );
361  }
362
363  #[test]
364  fn test_mul_int() {
365    let interval = TimeInterval::new(3, 500_000_000) * 3;
366    check!(interval.seconds() == 10);
367    check!(interval.nanoseconds() == 500_000_000);
368  }
369
370  #[test]
371  fn test_div_int() {
372    let interval = TimeInterval::new(4, 500_000_000) / 3;
373    check!(interval.seconds() == 1);
374    check!(interval.nanoseconds() == 500_000_000);
375  }
376
377  #[test]
378  fn test_div() {
379    check!(TimeInterval::new(3600, 0) / TimeInterval::new(1800, 0) == 2.0);
380    check!(TimeInterval::new(-1800, 0) / TimeInterval::new(-3600, 0) == 0.5);
381    check!(TimeInterval::new(-1800, 0) / TimeInterval::new(3600, 0) == -0.5);
382    check!(TimeInterval::new(0, 3600) / TimeInterval::new(0, 1800) == 2.0);
383  }
384
385  #[test]
386  fn test_as() {
387    let dur = TimeInterval::new(5, 0);
388    check!(dur.as_milliseconds() == 5_000);
389    check!(dur.as_microseconds() == 5_000_000);
390    check!(dur.as_nanoseconds() == 5_000_000_000);
391  }
392}