unisecs/
lib.rs

1//! Provides a way of representing [unix time](https://en.wikipedia.org/wiki/Unix_time)
2//! in terms of seconds with fractional subseconds.
3//!
4//! # Examples
5//!
6//! The following is roughly equivalent with `date -v+1S +%s`
7//!
8//! ```rust
9//! use std::time::Duration;
10//! use unisecs::Seconds;
11//!
12//! fn main() {
13//!   println!(
14//!     "{}",
15//!     Seconds::now() + Duration::from_secs(5)
16//!   );
17//! }
18//! ```
19//!
20//! # Features
21//!
22//! ## serde
23//!
24//! Adds ability to serialize and deserialize seconds with serde. This is
25//! enabled by default. To turn if off add the following to your `Cargo.toml`
26//! file
27//!
28//! ```toml
29//! [dependencies.unisecs]
30//!  version = "..."
31//!  default-features = false
32//! ```
33#[cfg(feature = "serde")]
34use serde::{de, ser, Serializer};
35
36use std::{
37    fmt,
38    ops::{Add, Sub},
39    time::{Duration, SystemTime, UNIX_EPOCH},
40};
41
42/// Represents fractional seconds since the [unix epoch](https://en.wikipedia.org/wiki/Unix_time)
43/// These can be derived from [`std::time::Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) and be converted
44/// into [`std::time::Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html)
45///
46/// A `Default` implementation is provided which yields the number of seconds since the epoch from
47/// the system time's `now` value
48///
49/// You can also and and subtract durations from Seconds.
50#[derive(Debug, PartialEq, Copy, Clone)]
51pub struct Seconds(f64);
52
53impl fmt::Display for Seconds {
54    fn fmt(
55        &self,
56        f: &mut fmt::Formatter,
57    ) -> fmt::Result {
58        write!(f, "{}", self.0)
59    }
60}
61
62impl Seconds {
63    /// return the current time in seconds since the unix epoch (1-1-1970 midnight)
64    pub fn now() -> Self {
65        Self::from_duration(
66            SystemTime::now()
67                .duration_since(UNIX_EPOCH)
68                .unwrap_or_default(),
69        )
70    }
71
72    /// truncate epoc time to remove fractional seconds
73    pub fn trunc(self) -> Self {
74        Self(self.0.trunc())
75    }
76
77    /// transformation is kept private as we can make no guarantees
78    /// about whether a provided duration is anchored in any way to
79    /// unix time
80    fn from_duration(dur: Duration) -> Self {
81        Seconds(dur.as_secs() as f64 + (f64::from(dur.subsec_nanos()) / 1.0e9))
82    }
83}
84
85impl Default for Seconds {
86    fn default() -> Self {
87        Seconds::now()
88    }
89}
90
91impl Into<f64> for Seconds {
92    fn into(self) -> f64 {
93        let Seconds(secs) = self;
94        secs
95    }
96}
97
98/// Similar to `date -v+1S +%s`
99impl Add<Duration> for Seconds {
100    type Output = Seconds;
101    fn add(
102        self,
103        rhs: Duration,
104    ) -> Self::Output {
105        let lhs: Duration = self.into();
106        Seconds::from_duration(lhs + rhs)
107    }
108}
109
110/// Similar to `date -v-1S +%s`
111impl Sub<Duration> for Seconds {
112    type Output = Seconds;
113    fn sub(
114        self,
115        rhs: Duration,
116    ) -> Self::Output {
117        let lhs: Duration = self.into();
118        Seconds::from_duration(lhs - rhs)
119    }
120}
121
122impl Into<Duration> for Seconds {
123    fn into(self) -> Duration {
124        let Seconds(secs) = self;
125        Duration::new(secs.trunc() as u64, (secs.fract() * 1.0e9) as u32)
126    }
127}
128
129#[cfg(feature = "serde")]
130struct SecondsVisitor;
131
132#[cfg(feature = "serde")]
133impl<'de> de::Visitor<'de> for SecondsVisitor {
134    type Value = Seconds;
135
136    fn expecting(
137        &self,
138        formatter: &mut fmt::Formatter,
139    ) -> fmt::Result {
140        formatter.write_str("floating point seconds")
141    }
142    fn visit_f64<E>(
143        self,
144        value: f64,
145    ) -> Result<Seconds, E>
146    where
147        E: de::Error,
148    {
149        Ok(Seconds(value))
150    }
151}
152
153#[cfg(feature = "serde")]
154impl ser::Serialize for Seconds {
155    fn serialize<S>(
156        &self,
157        serializer: S,
158    ) -> Result<S::Ok, S::Error>
159    where
160        S: Serializer,
161    {
162        let Seconds(seconds) = self;
163        serializer.serialize_f64(*seconds)
164    }
165}
166
167#[cfg(feature = "serde")]
168impl<'de> de::Deserialize<'de> for Seconds {
169    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170    where
171        D: de::Deserializer<'de>,
172    {
173        deserializer.deserialize_f64(SecondsVisitor)
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::Seconds;
180    use std::time::Duration;
181
182    #[test]
183    fn seconds_default() {
184        let (now, default) = (Seconds::default(), Seconds::now());
185        assert_eq!(now.trunc(), default.trunc());
186    }
187
188    #[test]
189    fn seconds_deref() {
190        let secs = Seconds(1_545_136_342.711_932);
191        let _f: f64 = secs.into();
192    }
193
194    #[test]
195    fn seconds_display() {
196        let secs = Seconds(1_545_136_342.711_932);
197        assert_eq!(format!("{}", secs), "1545136342.711932");
198    }
199
200    #[test]
201    fn seconds_duration_interop() {
202        let secs = Seconds(1_545_136_342.711_932);
203        let duration: Duration = secs.into();
204        assert_eq!(duration.as_secs(), 1_545_136_342);
205    }
206
207    #[test]
208    fn seconds_add_duration() {
209        let secs = Seconds(1_545_136_342.711_932);
210        assert_eq!(
211            secs + Duration::from_secs(1),
212            Seconds(1_545_136_343.711_932)
213        );
214    }
215
216    #[test]
217    fn seconds_sub_duration() {
218        let secs = Seconds(1_545_136_342.711_932);
219        assert_eq!(
220            secs - Duration::from_secs(1),
221            Seconds(1_545_136_341.711_932)
222        );
223    }
224
225    #[cfg(feature = "serde")]
226    #[test]
227    fn seconds_serialize() {
228        assert_eq!(
229            serde_json::to_string(&Seconds(1_545_136_342.711_932)).expect("failed to serialize"),
230            "1545136342.711932"
231        );
232    }
233
234    #[cfg(feature = "serde")]
235    #[test]
236    fn seconds_deserialize_floats() {
237        assert_eq!(
238            serde_json::from_slice::<Seconds>(b"1545136342.711932").expect("failed to serialize"),
239            Seconds(1_545_136_342.711_932)
240        );
241    }
242
243    #[cfg(feature = "serde")]
244    #[test]
245    fn seconds_fails_to_deserialize() {
246        match serde_json::from_slice::<Seconds>(b"{\"foo\":\"bar\"}") {
247            Err(err) => assert_eq!(
248                format!("{}", err),
249                "invalid type: map, expected floating point seconds at line 1 column 0"
250            ),
251            Ok(other) => panic!("unexpected result {}", other),
252        }
253    }
254}