serenity/model/
timestamp.rs1use std::fmt;
33use std::str::FromStr;
34
35#[cfg(feature = "chrono")]
36pub use chrono::ParseError as InnerError;
37#[cfg(feature = "chrono")]
38use chrono::{DateTime, SecondsFormat, TimeZone, Utc};
39#[cfg(not(feature = "chrono"))]
40pub use dep_time::error::Parse as InnerError;
41#[cfg(not(feature = "chrono"))]
42use dep_time::{format_description::well_known::Rfc3339, serde::rfc3339, Duration, OffsetDateTime};
43use serde::{Deserialize, Serialize};
44
45const DISCORD_EPOCH: u64 = 1_420_070_400_000;
47
48#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
49#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
50#[serde(transparent)]
51pub struct Timestamp(
52 #[cfg(feature = "chrono")] DateTime<Utc>,
53 #[cfg(not(feature = "chrono"))]
54 #[serde(with = "rfc3339")]
55 OffsetDateTime,
56);
57
58impl Timestamp {
59 pub fn from_millis(millis: i64) -> Result<Self, InvalidTimestamp> {
65 #[cfg(feature = "chrono")]
66 let x = Utc.timestamp_millis_opt(millis).single();
67 #[cfg(not(feature = "chrono"))]
68 let x = OffsetDateTime::from_unix_timestamp_nanos(
69 Duration::milliseconds(millis).whole_nanoseconds(),
70 )
71 .ok();
72 x.map(Self).ok_or(InvalidTimestamp)
73 }
74
75 pub(crate) fn from_discord_id(id: u64) -> Self {
76 Self::from_millis(((id >> 22) + DISCORD_EPOCH) as i64).expect("can't fail")
79 }
80
81 #[must_use]
83 pub fn now() -> Self {
84 #[cfg(feature = "chrono")]
85 let x = Utc::now();
86 #[cfg(not(feature = "chrono"))]
87 let x = OffsetDateTime::now_utc();
88 Self(x)
89 }
90
91 pub fn from_unix_timestamp(secs: i64) -> Result<Self, InvalidTimestamp> {
97 Self::from_millis(secs * 1000)
98 }
99
100 #[must_use]
102 pub fn unix_timestamp(&self) -> i64 {
103 #[cfg(feature = "chrono")]
104 let x = self.0.timestamp();
105 #[cfg(not(feature = "chrono"))]
106 let x = self.0.unix_timestamp();
107 x
108 }
109
110 pub fn parse(input: &str) -> Result<Timestamp, ParseError> {
128 #[cfg(feature = "chrono")]
129 let x = DateTime::parse_from_rfc3339(input).map_err(ParseError)?.with_timezone(&Utc);
130 #[cfg(not(feature = "chrono"))]
131 let x = OffsetDateTime::parse(input, &Rfc3339).map_err(ParseError)?;
132 Ok(Self(x))
133 }
134
135 #[must_use]
136 pub fn to_rfc3339(&self) -> Option<String> {
137 #[cfg(feature = "chrono")]
138 let x = self.0.to_rfc3339_opts(SecondsFormat::Millis, true);
139 #[cfg(not(feature = "chrono"))]
140 let x = self.0.format(&Rfc3339).ok()?;
141 Some(x)
142 }
143}
144
145impl std::fmt::Display for Timestamp {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.write_str(&self.to_rfc3339().ok_or(std::fmt::Error)?)
148 }
149}
150
151impl std::ops::Deref for Timestamp {
152 #[cfg(feature = "chrono")]
153 type Target = DateTime<Utc>;
154 #[cfg(not(feature = "chrono"))]
155 type Target = OffsetDateTime;
156
157 fn deref(&self) -> &Self::Target {
158 &self.0
159 }
160}
161
162#[cfg(feature = "chrono")]
163impl<Tz: TimeZone> From<DateTime<Tz>> for Timestamp {
164 fn from(dt: DateTime<Tz>) -> Self {
165 Self(dt.with_timezone(&Utc))
166 }
167}
168#[cfg(not(feature = "chrono"))]
169impl From<OffsetDateTime> for Timestamp {
170 fn from(dt: OffsetDateTime) -> Self {
171 Self(dt)
172 }
173}
174
175impl Default for Timestamp {
176 fn default() -> Self {
177 #[cfg(feature = "chrono")]
178 let x = DateTime::default();
179 #[cfg(not(feature = "chrono"))]
180 let x = OffsetDateTime::UNIX_EPOCH;
181 Self(x)
182 }
183}
184
185#[derive(Debug)]
186pub struct InvalidTimestamp;
187
188impl std::error::Error for InvalidTimestamp {}
189
190impl fmt::Display for InvalidTimestamp {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.write_str("invalid UNIX timestamp value")
193 }
194}
195
196#[derive(Debug)]
198pub struct ParseError(InnerError);
199
200impl std::error::Error for ParseError {}
201
202impl fmt::Display for ParseError {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 fmt::Display::fmt(&self.0, f)
205 }
206}
207
208impl FromStr for Timestamp {
209 type Err = ParseError;
210
211 fn from_str(s: &str) -> Result<Self, Self::Err> {
213 Timestamp::parse(s)
214 }
215}
216
217impl<'a> std::convert::TryFrom<&'a str> for Timestamp {
218 type Error = ParseError;
219
220 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
222 Timestamp::parse(s)
223 }
224}
225
226impl From<&Timestamp> for Timestamp {
227 fn from(ts: &Timestamp) -> Self {
228 *ts
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::Timestamp;
235
236 #[test]
237 fn from_unix_timestamp() {
238 let timestamp = Timestamp::from_unix_timestamp(1462015105).unwrap();
239 assert_eq!(timestamp.unix_timestamp(), 1462015105);
240 if cfg!(feature = "chrono") {
241 assert_eq!(timestamp.to_string(), "2016-04-30T11:18:25.000Z");
242 } else {
243 assert_eq!(timestamp.to_string(), "2016-04-30T11:18:25Z");
244 }
245 }
246}