1use std::fmt::{self, Debug, Display, Formatter};
5use std::str::FromStr;
6use std::time::{Duration, SystemTime};
7
8use crate::Error;
9use crate::fmt::{Iso8601, Rfc2822};
10
11#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub struct UnixSeconds(pub(super) Duration);
57
58crate::thread_aware_move!(UnixSeconds);
59
60impl UnixSeconds {
61 pub const MAX: Self = Self(Duration::new(253_402_207_200, 999_999_999));
66
67 pub const UNIX_EPOCH: Self = Self(Duration::ZERO);
71
72 pub fn from_secs(seconds: u64) -> Result<Self, Error> {
100 Self::try_from(Duration::from_secs(seconds)).map_err(|_error| {
101 Error::out_of_range("the `seconds` is greater than the maximum value that can be represented by `UnixSeconds`")
102 })
103 }
104
105 #[must_use]
107 pub fn to_secs(self) -> u64 {
108 self.0.as_secs()
109 }
110}
111
112impl FromStr for UnixSeconds {
113 type Err = Error;
114
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 let secs: u64 = s.parse().map_err(Error::other)?;
117 Self::from_secs(secs)
118 }
119}
120
121impl Display for UnixSeconds {
122 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
123 write!(f, "{}", self.to_secs())
124 }
125}
126
127impl From<UnixSeconds> for SystemTime {
128 fn from(value: UnixSeconds) -> Self {
129 Self::UNIX_EPOCH + value.0
130 }
131}
132
133impl TryFrom<Duration> for UnixSeconds {
134 type Error = Error;
135
136 fn try_from(value: Duration) -> Result<Self, Self::Error> {
137 if value > Self::MAX.0 {
138 return Err(Error::out_of_range(
139 "the `duration` is greater than the maximum value that can be represented by `UnixSeconds`",
140 ));
141 }
142
143 Ok(Self(value))
144 }
145}
146
147impl TryFrom<SystemTime> for UnixSeconds {
148 type Error = crate::Error;
149
150 fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
151 Self::try_from(value.duration_since(SystemTime::UNIX_EPOCH).unwrap_or(Duration::ZERO))
152 }
153}
154
155impl From<Rfc2822> for UnixSeconds {
156 fn from(value: Rfc2822) -> Self {
157 Self(value.to_unix_epoch_duration())
158 }
159}
160
161impl From<Iso8601> for UnixSeconds {
162 fn from(value: Iso8601) -> Self {
163 Self(value.to_unix_epoch_duration())
164 }
165}
166
167#[cfg(any(feature = "serde", test))]
168impl serde_core::Serialize for UnixSeconds {
169 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
170 where
171 S: serde_core::Serializer,
172 {
173 serializer.serialize_u64(self.to_secs())
174 }
175}
176
177#[cfg(any(feature = "serde", test))]
178impl<'de> serde_core::Deserialize<'de> for UnixSeconds {
179 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
180 where
181 D: serde_core::Deserializer<'de>,
182 {
183 let secs = u64::deserialize(deserializer).map_err(serde_core::de::Error::custom)?;
184 Self::from_secs(secs).map_err(serde_core::de::Error::custom)
185 }
186}
187
188#[cfg_attr(coverage_nightly, coverage(off))]
189#[cfg(test)]
190mod tests {
191 use std::hash::Hash;
192
193 use jiff::Timestamp;
194
195 use super::*;
196
197 static_assertions::assert_impl_all!(UnixSeconds: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, TryFrom<SystemTime>, From<Iso8601>, FromStr);
198
199 #[test]
200 fn max_duration_is_jiff_duration() {
201 let jiff_max = Timestamp::MAX.duration_since(Timestamp::UNIX_EPOCH).unsigned_abs();
202
203 assert_eq!(UnixSeconds::MAX.0, jiff_max);
205 }
206
207 #[test]
208 fn from_secs() {
209 let ts = UnixSeconds::from_secs(10).unwrap();
210
211 assert_eq!(ts.to_secs(), 10);
212 }
213
214 #[test]
215 fn try_from_duration() {
216 let ts = UnixSeconds::try_from(Duration::MAX).unwrap_err();
217 assert_eq!(
218 ts.to_string(),
219 "the `duration` is greater than the maximum value that can be represented by `UnixSeconds`"
220 );
221 }
222
223 #[test]
224 fn from_secs_error() {
225 let error = UnixSeconds::from_secs(u64::MAX).unwrap_err();
226
227 assert_eq!(
228 error.to_string(),
229 "the `seconds` is greater than the maximum value that can be represented by `UnixSeconds`"
230 );
231 }
232
233 #[test]
234 fn to_system_time() {
235 let ts = UnixSeconds::from_secs(10).unwrap();
236 let system_time: SystemTime = ts.into();
237
238 assert_eq!(system_time, SystemTime::UNIX_EPOCH + Duration::from_secs(10));
239 }
240
241 #[test]
242 fn parse_err() {
243 "date".parse::<UnixSeconds>().unwrap_err();
244 }
245
246 #[test]
247 fn parse_min() {
248 let stamp: UnixSeconds = "0".parse().unwrap();
249 assert_eq!(SystemTime::from(stamp), SystemTime::UNIX_EPOCH);
250 }
251
252 #[test]
253 fn parse_then_display() {
254 let stamp: UnixSeconds = "3600".parse().unwrap();
255
256 assert_eq!(stamp.to_string(), "3600");
258 }
259
260 #[test]
261 fn parse_max() {
262 let max = UnixSeconds::MAX;
263
264 let stamp: UnixSeconds = max.to_string().parse().unwrap();
265
266 assert_eq!(stamp.to_string(), max.to_string());
267 }
268
269 #[test]
270 fn parse_max_overflow() {
271 "99999999999999999999999".parse::<UnixSeconds>().unwrap_err();
272 }
273
274 #[test]
275 #[cfg(feature = "serde")]
276 fn serialize_deserialize() {
277 let iso: UnixSeconds = "9999".parse().unwrap();
278 let serialized = serde_json::to_string(&iso).unwrap();
279 let deserialized: UnixSeconds = serde_json::from_str(&serialized).unwrap();
280
281 assert_eq!(iso, deserialized);
282 }
283
284 #[test]
285 fn iso_8601_roundtrip() {
286 let iso: UnixSeconds = "9999".parse().unwrap();
287 let iso_str = iso.to_string();
288 let parsed_iso: UnixSeconds = iso_str.parse().unwrap();
289
290 assert_eq!(iso, parsed_iso);
291 }
292
293 #[test]
294 fn iso_8601_roundtrip_with_timezone() {
295 let unix_seconds: UnixSeconds = "9999".parse().unwrap();
296 let iso: Iso8601 = unix_seconds.into();
297
298 assert_eq!(iso.to_string(), "1970-01-01T02:46:39Z");
299
300 let iso: Iso8601 = UnixSeconds::MAX.into();
301 assert_eq!(iso, Iso8601::MAX);
302
303 let iso: Iso8601 = UnixSeconds::UNIX_EPOCH.into();
304 assert_eq!(iso, Iso8601::UNIX_EPOCH);
305 }
306
307 #[test]
308 fn try_from_max_ensure_accepted() {
309 let unix_seconds = UnixSeconds::try_from(UnixSeconds::MAX.0).unwrap();
310 assert_eq!(unix_seconds, UnixSeconds::MAX);
311 }
312}