use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
const DISCORD_EPOCH: u64 = 1_420_070_400_000;
cfg_if::cfg_if! {
if #[cfg(all(feature = "chrono", not(feature = "time")))] {
use chrono::{DateTime, NaiveDateTime, ParseError as InnerError, SecondsFormat, TimeZone, Utc};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize, Ord, PartialOrd)]
#[serde(transparent)]
pub struct Timestamp(DateTime<Utc>);
impl Timestamp {
pub(crate) fn from_discord_id(id: u64) -> Timestamp {
Self(Utc.timestamp_millis(((id >> 22) + DISCORD_EPOCH) as i64))
}
#[must_use]
pub fn now() -> Self {
Self(Utc::now())
}
pub fn from_unix_timestamp(secs: i64) -> Result<Self, InvalidTimestamp> {
let dt = NaiveDateTime::from_timestamp_opt(secs, 0).ok_or(InvalidTimestamp)?;
Ok(Self(DateTime::from_utc(dt, Utc)))
}
#[must_use]
pub fn unix_timestamp(&self) -> i64 {
self.0.timestamp()
}
pub fn parse(input: &str) -> Result<Timestamp, ParseError> {
DateTime::parse_from_rfc3339(input).map(|d| Self(d.with_timezone(&Utc))).map_err(ParseError)
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = self.0.to_rfc3339_opts(SecondsFormat::Millis, true);
f.write_str(&s)
}
}
} else {
use dep_time::format_description::well_known::Rfc3339;
use dep_time::serde::rfc3339;
use dep_time::{Duration, OffsetDateTime};
use dep_time::error::Parse as InnerError;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize, Ord, PartialOrd)]
#[serde(transparent)]
pub struct Timestamp(#[serde(with = "rfc3339")] OffsetDateTime);
impl Timestamp {
pub(crate) fn from_discord_id(id: u64) -> Timestamp {
let ns = Duration::milliseconds(((id >> 22) + DISCORD_EPOCH) as i64).whole_nanoseconds();
Self(OffsetDateTime::from_unix_timestamp_nanos(ns).expect("can't fail"))
}
#[must_use]
pub fn now() -> Self {
Self(OffsetDateTime::now_utc())
}
pub fn from_unix_timestamp(secs: i64) -> Result<Self, InvalidTimestamp> {
let dt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| InvalidTimestamp)?;
Ok(Self(dt))
}
#[must_use]
pub fn unix_timestamp(&self) -> i64 {
self.0.unix_timestamp()
}
pub fn parse(input: &str) -> Result<Timestamp, ParseError> {
OffsetDateTime::parse(input, &Rfc3339).map(Self).map_err(ParseError)
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = self.0.format(&Rfc3339).map_err(|_| fmt::Error)?;
f.write_str(&s)
}
}
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "time")] {
impl std::ops::Deref for Timestamp {
type Target = OffsetDateTime;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<OffsetDateTime> for Timestamp {
fn from(dt: OffsetDateTime) -> Self {
Self(dt)
}
}
} else if #[cfg(feature = "chrono")] {
impl std::ops::Deref for Timestamp {
type Target = DateTime<Utc>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<Tz: TimeZone> From<DateTime<Tz>> for Timestamp {
fn from(dt: DateTime<Tz>) -> Self {
Self(dt.with_timezone(&Utc))
}
}
}
}
#[derive(Debug)]
pub struct InvalidTimestamp;
impl std::error::Error for InvalidTimestamp {}
impl fmt::Display for InvalidTimestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid UNIX timestamp value")
}
}
#[derive(Debug)]
pub struct ParseError(InnerError);
impl std::error::Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for Timestamp {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Timestamp::parse(s)
}
}
impl From<String> for Timestamp {
fn from(s: String) -> Self {
#[allow(clippy::unwrap_used)]
Timestamp::parse(&s).unwrap()
}
}
impl<'a> From<&'a str> for Timestamp {
fn from(s: &'a str) -> Self {
#[allow(clippy::unwrap_used)]
Timestamp::parse(s).unwrap()
}
}
impl From<&Timestamp> for Timestamp {
fn from(ts: &Timestamp) -> Self {
*ts
}
}
#[cfg(test)]
mod tests {
use super::Timestamp;
#[test]
fn from_unix_timestamp() {
let timestamp = Timestamp::from_unix_timestamp(1462015105).unwrap();
assert_eq!(timestamp.unix_timestamp(), 1462015105);
if cfg!(all(feature = "chrono", not(feature = "time"))) {
assert_eq!(timestamp.to_string(), "2016-04-30T11:18:25.000Z");
} else {
assert_eq!(timestamp.to_string(), "2016-04-30T11:18:25Z");
}
}
}