diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e70405..cc27617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ Chrono obeys the principle of [Semantic Versioning](http://semver.org/). There were/are numerous minor versions before 1.0 due to the language changes. Versions with only mechnical changes will be omitted from the following list. +## ?0.4.3? + +### Features +* Added methods to DateTime/NaiveDateTime to present the stored value as a number + of nanoseconds since the UNIX epoch (@harkonenbade #247) +* Added a serde serialise/deserialise module for nanosecond timestamps. (@harkonenbade #247) + ## 0.4.2 ### Deprecations diff --git a/src/datetime.rs b/src/datetime.rs index 03d4c3f..410fb84 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -123,6 +123,30 @@ impl DateTime { self.datetime.timestamp_millis() } + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC + /// + /// Note that this does reduce the number of years that can be represented + /// from ~584 Billion to ~584. (If this is a problem, please file + /// an issue to let me know what domain needs nanosecond precision over + /// millenia, I'm curious.) + /// + /// # Example + /// + /// ~~~~ + /// use chrono::Utc; + /// use chrono::TimeZone; + /// + /// let dt = Utc.ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); + /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); + /// + /// let dt = Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); + /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); + /// ~~~~ + #[inline] + pub fn timestamp_nanos(&self) -> i64 { + self.datetime.timestamp_nanos() + } + /// Returns the number of milliseconds since the last second boundary /// /// warning: in event of a leap second, this may exceed 999 @@ -968,6 +992,167 @@ pub mod serde { } } + /// Ser/de to/from timestamps in nanoseconds + /// + /// Intended for use with `serde`'s `with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_nanoseconds; + /// #[derive(Deserialize, Serialize)] + /// struct S { + /// #[serde(with = "ts_nanoseconds")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let time = Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); + /// let my_s = S { + /// time: time.clone(), + /// }; + /// + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// let my_s: S = serde_json::from_str(&as_string)?; + /// assert_eq!(my_s.time, time); + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub mod ts_nanoseconds { + use std::fmt; + use serdelib::{ser, de}; + + use {DateTime, Utc}; + use offset::{LocalResult, TimeZone}; + + /// Deserialize a `DateTime` from a nanosecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{DateTime, Utc}; + /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; + /// #[derive(Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// # Ok(my_s) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where D: de::Deserializer<'de> + { + Ok(try!(d.deserialize_i64(NanoSecondsTimestampVisitor))) + } + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # // We mark this ignored so that we can test on 1.13 (which does not + /// # // support custom derive), and run tests with --ignored on beta and + /// # // nightly to actually trigger these. + /// # + /// # #[macro_use] extern crate serde_derive; + /// # #[macro_use] extern crate serde_json; + /// # extern crate chrono; + /// # use chrono::{TimeZone, DateTime, Utc}; + /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_ts")] + /// time: DateTime + /// } + /// + /// # fn example() -> Result { + /// let my_s = S { + /// time: Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok(as_string) + /// # } + /// # fn main() { example().unwrap(); } + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where S: ser::Serializer + { + serializer.serialize_i64(dt.timestamp_nanos()) + } + + struct NanoSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result + { + write!(formatter, "a unix timestamp in seconds") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_i64(self, value: i64) -> Result, E> + where E: de::Error + { + from(Utc.timestamp_opt(value / 1_000_000_000, + (value % 1_000_000_000) as u32), + &value) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_u64(self, value: u64) -> Result, E> + where E: de::Error + { + from(Utc.timestamp_opt(value as i64 / 1_000_000_000, + (value as i64 % 1_000_000_000) as u32), + &value) + } + } + + // try!-like function to convert a LocalResult into a serde-ish Result + fn from(me: LocalResult, ts: &V) -> Result + where E: de::Error, + V: fmt::Display, + T: fmt::Display, + { + match me { + LocalResult::None => Err(E::custom( + format!("value is not a legal timestamp: {}", ts))), + LocalResult::Ambiguous(min, max) => Err(E::custom( + format!("value is an ambiguous timestamp: {}, could be either of {}, {}", + ts, min, max))), + LocalResult::Single(val) => Ok(val) + } + } + } + impl ser::Serialize for DateTime { /// Serialize into a rfc3339 time string /// diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 84d6360..bb3f33f 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -290,6 +290,33 @@ impl NaiveDateTime { as_ms + i64::from(self.timestamp_subsec_millis()) } + /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + /// + /// Note also that this does reduce the number of years that can be + /// represented from ~584 Billion to ~584. (If this is a problem, + /// please file an issue to let me know what domain needs nanosecond + /// precision over millenia, I'm curious.) + /// + /// # Example + /// + /// ~~~~ + /// use chrono::NaiveDate; + /// + /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); + /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); + /// + /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); + /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); + /// ~~~~ + #[inline] + pub fn timestamp_nanos(&self) -> i64 { + let as_ns = self.timestamp() * 1_000_000_000; + as_ns + i64::from(self.timestamp_subsec_nanos()) + } + /// Returns the number of milliseconds since the last whole non-leap second. /// /// The return value ranges from 0 to 999,