unix_ts/
lib.rs

1//! `unix-ts` provides lightweight representations of Unix timestamps.
2//!
3//! Unix timestamps are one of the most common ways to exchange time data. A Unix timestamp is
4//! simply the number of seconds (and, optionally, fractions of a second) that have elapsed since
5//! January 1, 1970 at midnight UTC.
6
7use std::time::SystemTime;
8
9mod display;
10mod integers;
11mod std_duration;
12
13pub use unix_ts_macros::ts;
14
15/// A representation of a timestamp (seconds and nanos since the Unix epoch).
16///
17/// Timestamps are able to be easily converted into chrono DateTimes.
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
19pub struct Timestamp {
20  /// The number of seconds since the Unix epoch.
21  pub(crate) seconds: i64,
22
23  /// The number of nanoseconds since second.
24  pub(crate) nanos: u32,
25}
26
27/// Creation of timestamps.
28impl Timestamp {
29  /// Create a new timestamp from the given number of `seconds` and `nanos` (nanoseconds).
30  ///
31  /// The use of the `ts!()` macro in the `unix-ts-macros` crate is advised in lieu of calling this
32  /// method directly for most situations.
33  ///
34  /// **Note:** For negative timestamps, the `nanos` argument is _always_ a positive offset.
35  /// Therefore, the correct way to represent a timestamp of `-0.25 seconds` is to call `new(-1,
36  /// 750_000_000)`.
37  pub const fn new(mut seconds: i64, mut nanos: u32) -> Self {
38    while nanos >= 1_000_000_000 {
39      seconds += 1;
40      nanos -= 1_000_000_000;
41    }
42    Timestamp { seconds, nanos }
43  }
44
45  /// Create a timestamp from the given number of nanoseconds.
46  pub const fn from_nanos(nanos: i128) -> Self {
47    let seconds: i64 = (nanos / 1_000_000_000) as i64;
48    // .try_into()
49    // .expect("Timestamp value out of range.");
50    let nanos = if seconds >= 0 {
51      (nanos % 1_000_000_000) as u32
52    }
53    else {
54      (1_000_000_000 - (nanos % 1_000_000_000).abs()) as u32
55    };
56    Timestamp { seconds, nanos }
57  }
58
59  /// Create a timestamp from the given number of microseconds.
60  pub const fn from_micros(micros: i64) -> Self {
61    Timestamp::from_nanos(micros as i128 * 1_000)
62  }
63
64  /// Create a timestamp from the given number of milliseconds.
65  pub const fn from_millis(millis: i64) -> Self {
66    Timestamp::from_nanos(millis as i128 * 1_000_000)
67  }
68
69  /// The timestamp representing "right now".
70  ///
71  /// ## Panic
72  ///
73  /// Panics if the system clock is set to a time prior to the Unix epoch (January 1, 1970).
74  pub fn now() -> Self {
75    let now_dur = SystemTime::now()
76      .duration_since(SystemTime::UNIX_EPOCH)
77      .expect("System clock set prior to January 1, 1970");
78    Self { seconds: now_dur.as_secs() as i64, nanos: now_dur.subsec_nanos() }
79  }
80}
81
82/// Inspection of timestamps.
83impl Timestamp {
84  /// Return the seconds since the Unix epoch.
85  /// Sub-second values are discarded.
86  ///
87  /// # Examples
88  ///
89  /// ```
90  /// # use unix_ts::Timestamp;
91  /// let t = Timestamp::from(1335020400);
92  /// assert_eq!(t.seconds(), 1335020400);
93  /// ```
94  pub const fn seconds(&self) -> i64 {
95    self.seconds
96  }
97
98  /// Return the time since the Unix epoch, as an integer, with the given
99  /// precision.
100  ///
101  /// ## Arguments
102  ///
103  /// - `e` (`u8`) - The precision for the returned integer, as a power of 10. (ex. 3 for
104  ///   milliseconds, 6 for microseconds, etc.). Must be a value between 0 and 9.
105  ///
106  /// ## Examples
107  ///
108  /// ```
109  /// # use unix_ts::Timestamp;
110  /// let t = Timestamp::from(1335020400);
111  /// assert_eq!(t.at_precision(3), 1335020400_000);
112  /// assert_eq!(t.at_precision(6), 1335020400_000_000);
113  /// ```
114  pub const fn at_precision(&self, e: u8) -> i128 {
115    (self.seconds as i128) * 10i128.pow(e as u32)
116      + (self.nanos as i128) / 10i128.pow(9 - (e as u32))
117  }
118
119  /// Return the subsecond component at the specified precision (ex. 3 for milliseconds, 6 for
120  /// microseconds); max precision is 9.
121  ///
122  /// # Arguments
123  ///
124  /// - `e` (`u8`) - The precision for the returned subsecond value, as a power of 10 (ex. 3 for
125  ///   milliseconds, 6 for microseconds, etc.). Must be a value between 0 and 9.
126  ///
127  /// # Examples
128  ///
129  /// ```
130  /// # use unix_ts::Timestamp;
131  /// let t = Timestamp::new(1335020400, 500_000_000);
132  /// assert_eq!(t.subsec(1), 5);
133  /// assert_eq!(t.subsec(3), 500);
134  /// ```
135  pub const fn subsec(&self, e: u8) -> u32 {
136    self.nanos / 10u32.pow(9 - e as u32)
137  }
138}
139
140#[cfg(test)]
141#[allow(clippy::inconsistent_digit_grouping)]
142mod tests {
143  use std::time::Duration;
144
145  use assert2::check;
146
147  use super::*;
148
149  #[test]
150  fn test_cmp() {
151    check!(Timestamp::from(1335020400) < Timestamp::from(1335024000));
152    check!(Timestamp::from(1335020400) == Timestamp::from(1335020400));
153    check!(Timestamp::new(1335020400, 500_000_000) < Timestamp::new(1335020400, 750_000_000));
154    check!(Timestamp::new(1, 999_999_999) < Timestamp::from(2));
155  }
156
157  #[test]
158  fn test_from_nanos() {
159    check!(Timestamp::from_nanos(1335020400_000_000_000i128) == Timestamp::new(1335020400, 0));
160    check!(
161      Timestamp::from_nanos(1335020400_500_000_000i128) == Timestamp::new(1335020400, 500_000_000)
162    );
163    check!(Timestamp::from_nanos(-1_750_000_000) == Timestamp::new(-1, 250_000_000));
164  }
165
166  #[test]
167  fn test_from_micros() {
168    check!(Timestamp::from_micros(1335020400_000_000i64) == Timestamp::new(1335020400, 0));
169    check!(
170      Timestamp::from_micros(1335020400_500_000i64) == Timestamp::new(1335020400, 500_000_000)
171    );
172    check!(Timestamp::from_micros(-1_750_000) == Timestamp::new(-1, 250_000_000));
173  }
174
175  #[test]
176  fn test_from_millis() {
177    check!(Timestamp::from_millis(1335020400_000i64) == Timestamp::new(1335020400, 0));
178    check!(Timestamp::from_millis(1335020400_500i64) == Timestamp::new(1335020400, 500_000_000));
179    check!(Timestamp::from_millis(-1_750) == Timestamp::new(-1, 250_000_000));
180  }
181
182  #[test]
183  fn test_seconds() {
184    assert_eq!(Timestamp::from(1335020400).seconds, 1335020400);
185  }
186
187  #[test]
188  fn test_at_precision() {
189    let ts = Timestamp::new(1335020400, 123456789);
190    assert_eq!(ts.at_precision(3), 1335020400123);
191    assert_eq!(ts.at_precision(6), 1335020400123456);
192    assert_eq!(ts.at_precision(9), 1335020400123456789);
193  }
194
195  #[test]
196  fn test_subsec() {
197    let ts = Timestamp::new(1335020400, 123456789);
198    assert_eq!(ts.subsec(3), 123);
199    assert_eq!(ts.subsec(6), 123456);
200    assert_eq!(ts.subsec(9), 123456789);
201  }
202
203  #[test]
204  fn test_add() {
205    let ts = Timestamp::from(1335020400) + Duration::new(86400, 1_000_000);
206    assert_eq!(ts.seconds(), 1335020400 + 86400);
207    assert_eq!(ts.subsec(3), 1);
208  }
209
210  #[test]
211  fn test_sub() {
212    let ts = Timestamp::from(1335020400) - Duration::new(86400, 0);
213    assert_eq!(ts.seconds(), 1335020400 - 86400);
214    assert_eq!(ts.nanos, 0);
215  }
216
217  #[test]
218  fn test_sub_nano_overflow() {
219    let ts = Timestamp::from(1335020400) - Duration::new(0, 500_000_000);
220    assert_eq!(ts.seconds(), 1335020399);
221    assert_eq!(ts.subsec(1), 5);
222  }
223}