use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use jiff::{SignedDuration, Timestamp};
use crate::Error;
use crate::fmt::{Rfc2822, UnixSeconds};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Iso8601(pub(super) Timestamp);
crate::thread_aware_move!(Iso8601);
impl Iso8601 {
pub const MAX: Self = Self(Timestamp::MAX);
pub const UNIX_EPOCH: Self = Self(Timestamp::UNIX_EPOCH);
pub(super) fn to_unix_epoch_duration(self) -> Duration {
self.0.duration_since(Timestamp::UNIX_EPOCH).unsigned_abs()
}
}
impl FromStr for Iso8601 {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let timestamp = s.parse::<jiff::Timestamp>().map_err(Error::jiff)?;
Ok(Self(timestamp))
}
}
impl Display for Iso8601 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let rounded = with_rounded_nanos(self.0);
Display::fmt(&rounded, f)
}
}
impl From<Iso8601> for SystemTime {
fn from(value: Iso8601) -> Self {
value.0.into()
}
}
impl TryFrom<SystemTime> for Iso8601 {
type Error = Error;
fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
let timestamp = Timestamp::try_from(value).map_err(Error::jiff)?;
Ok(Self(timestamp))
}
}
impl From<Rfc2822> for Iso8601 {
fn from(value: Rfc2822) -> Self {
Self(value.0)
}
}
impl From<UnixSeconds> for Iso8601 {
fn from(value: UnixSeconds) -> Self {
Self(Timestamp::UNIX_EPOCH + value.0)
}
}
#[cfg(any(feature = "serde", test))]
impl serde_core::Serialize for Iso8601 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde_core::Serializer,
{
serializer.collect_str(self)
}
}
#[cfg(any(feature = "serde", test))]
impl<'de> serde_core::Deserialize<'de> for Iso8601 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde_core::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<Self>()
.map_err(serde_core::de::Error::custom)
}
}
fn with_rounded_nanos(timestamp: Timestamp) -> Timestamp {
let duration = timestamp.duration_since(Timestamp::UNIX_EPOCH);
let secs = duration.as_secs();
let nanos = duration.subsec_nanos();
let nanos = (nanos / 100) * 100;
let duration = SignedDuration::new(secs, nanos);
Timestamp::UNIX_EPOCH
.saturating_add(duration)
.expect("this can never fail as we know we are within a valid range")
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use std::hash::Hash;
use super::*;
static_assertions::assert_impl_all!(Iso8601: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, TryFrom<SystemTime>, From<Iso8601>, FromStr);
#[test]
fn parse_err() {
let err = "date".parse::<Iso8601>().unwrap_err();
assert!(err.to_string().starts_with("failed to parse year in date"));
}
#[test]
fn parse_min() {
let iso: Iso8601 = "1970-01-01T00:00:00Z".parse().unwrap();
assert_eq!(iso, Iso8601::UNIX_EPOCH);
let system_time: SystemTime = iso.into();
assert_eq!(system_time, SystemTime::UNIX_EPOCH);
}
#[test]
fn parse_then_display() {
let stamp: Iso8601 = "1970-01-01T01:00:00Z".parse().unwrap();
assert_eq!(stamp.to_string(), "1970-01-01T01:00:00Z");
assert_eq!(SystemTime::from(stamp), SystemTime::UNIX_EPOCH + Duration::from_hours(1));
}
#[test]
fn parse_max() {
let stamp: Iso8601 = "9999-12-30T22:00:00.9999999Z".parse().unwrap();
assert_eq!(stamp.to_string(), "9999-12-30T22:00:00.9999999Z");
}
#[test]
fn parse_max_overflow() {
"10000-12-30T22:00:00.999999999Z".parse::<Iso8601>().unwrap_err();
}
#[test]
fn from_to() {
let at = SystemTime::UNIX_EPOCH + Duration::from_hours(1);
let now = crate::Clock::new_frozen_at(at).system_time();
let iso = Iso8601::try_from(now).unwrap();
assert_eq!(SystemTime::from(iso), at);
}
#[test]
fn parse_leap_seconds() {
let stamp: Iso8601 = "1990-12-31T23:59:60Z".parse().unwrap();
assert_eq!(stamp.to_string(), "1990-12-31T23:59:59Z");
}
#[test]
#[cfg(feature = "serde")]
fn serialize_deserialize() {
let iso: Iso8601 = "1970-01-01T01:00:00Z".parse().unwrap();
let serialized = serde_json::to_string(&iso).unwrap();
let deserialized: Iso8601 = serde_json::from_str(&serialized).unwrap();
assert_eq!(iso, deserialized);
}
#[test]
fn ensure_precise_nanos_parsed() {
let iso: Iso8601 = "1970-01-01T00:00:08.999999999Z".parse().unwrap();
assert_eq!(iso.to_string(), "1970-01-01T00:00:08.9999999Z");
}
#[test]
fn ensure_nanos_rounded() {
let system_time = SystemTime::UNIX_EPOCH + Duration::new(8, 999_999_999);
let iso: Iso8601 = system_time.try_into().unwrap();
assert_eq!(iso.to_string(), "1970-01-01T00:00:08.9999999Z");
let iso: Iso8601 = SystemTime::UNIX_EPOCH.try_into().unwrap();
assert_eq!(iso.to_string(), "1970-01-01T00:00:00Z");
}
#[test]
fn ensure_nanos_rounded_when_before_epoch() {
let system_time = SystemTime::UNIX_EPOCH - Duration::new(8, 999_999_999);
let iso: Iso8601 = system_time.try_into().unwrap();
assert_eq!(iso.to_string(), "1969-12-31T23:59:51.0000001Z");
}
#[test]
fn far_in_the_past() {
let iso: Iso8601 = "1601-01-01T00:00:00Z".parse().unwrap();
assert_eq!(iso.to_string(), "1601-01-01T00:00:00Z");
assert_eq!(SystemTime::from(iso), SystemTime::UNIX_EPOCH - Duration::new(11_644_473_600, 0));
}
}