1use std::{
4 fmt::{self, Display, Formatter},
5 ops::{Add, AddAssign, Sub, SubAssign},
6};
7
8use crate::{Date, TypeParseError};
9
10use serde::{Deserialize, Deserializer, Serialize};
11
12#[cfg(test)]
13mod tests;
14
15#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
17#[repr(transparent)]
18pub struct Duration(u64);
19
20impl Duration {
21 pub const ZERO: Self = Self(0);
23 pub const MIN: Self = Self(u64::MIN);
25 pub const MAX: Self = Self(u64::MAX);
27
28 const MS_PER_SEC: u64 = 1_000;
29 const SECS_PER_MIN: u64 = 60;
30 const MINS_PER_HOUR: u64 = 60;
31 const HOURS_PER_DAY: u64 = 24;
32 const DAYS_PER_WEEK: u64 = 7;
33
34 #[must_use]
36 pub const fn from_millis(millis: u64) -> Self {
37 Self(millis)
38 }
39
40 #[must_use]
42 pub const fn try_from_i64(millis: i64) -> Option<Self> {
43 if millis < 0 {
44 None
45 } else {
46 Some(Self(millis.cast_unsigned()))
47 }
48 }
49
50 #[must_use]
52 pub const fn from_micros_truncating(micros: u64) -> Self {
53 Self(micros / Self::MS_PER_SEC)
54 }
55
56 #[must_use]
58 pub const fn from_nanos_truncating(nanos: u64) -> Self {
59 Self(nanos / 1_000_000)
60 }
61
62 #[must_use]
64 pub const fn from_secs(seconds: u64) -> Self {
65 Self(seconds.saturating_mul(Self::MS_PER_SEC))
66 }
67
68 #[must_use]
70 pub const fn from_minutes(minutes: u64) -> Self {
71 Self(
72 minutes
73 .saturating_mul(Self::SECS_PER_MIN)
74 .saturating_mul(Self::MS_PER_SEC),
75 )
76 }
77
78 #[must_use]
80 pub const fn from_hours(hours: u64) -> Self {
81 Self(
82 hours
83 .saturating_mul(Self::MINS_PER_HOUR)
84 .saturating_mul(Self::SECS_PER_MIN)
85 .saturating_mul(Self::MS_PER_SEC),
86 )
87 }
88
89 #[must_use]
91 pub const fn from_days(days: u64) -> Self {
92 Self(
93 days.saturating_mul(Self::HOURS_PER_DAY)
94 .saturating_mul(Self::MINS_PER_HOUR)
95 .saturating_mul(Self::SECS_PER_MIN)
96 .saturating_mul(Self::MS_PER_SEC),
97 )
98 }
99
100 #[must_use]
102 pub const fn from_weeks(weeks: u64) -> Self {
103 Self(
104 weeks
105 .saturating_mul(Self::DAYS_PER_WEEK)
106 .saturating_mul(Self::HOURS_PER_DAY)
107 .saturating_mul(Self::MINS_PER_HOUR)
108 .saturating_mul(Self::SECS_PER_MIN)
109 .saturating_mul(Self::MS_PER_SEC),
110 )
111 }
112
113 #[must_use]
115 pub const fn as_millis(self) -> u64 {
116 self.0
117 }
118
119 #[must_use]
121 pub const fn as_secs(self) -> u64 {
122 self.0 / Self::MS_PER_SEC
123 }
124
125 #[must_use]
127 pub const fn as_minutes(self) -> u64 {
128 self.0 / (Self::SECS_PER_MIN * Self::MS_PER_SEC)
129 }
130
131 #[must_use]
133 pub const fn as_hours(self) -> u64 {
134 self.0 / (Self::MINS_PER_HOUR * Self::SECS_PER_MIN * Self::MS_PER_SEC)
135 }
136
137 #[must_use]
139 pub const fn as_days(self) -> u64 {
140 self.0 / (Self::HOURS_PER_DAY * Self::MINS_PER_HOUR * Self::SECS_PER_MIN * Self::MS_PER_SEC)
141 }
142
143 #[must_use]
145 pub const fn as_weeks(self) -> u64 {
146 self.0
147 / (Self::DAYS_PER_WEEK
148 * Self::HOURS_PER_DAY
149 * Self::MINS_PER_HOUR
150 * Self::SECS_PER_MIN
151 * Self::MS_PER_SEC)
152 }
153
154 pub fn parse_flexible(input: &str) -> Result<Self, TypeParseError> {
161 let (digits, multiplier) = if let Some(value) = input.strip_suffix("ms") {
162 (value, 1)
163 } else if let Some(value) = input.strip_suffix('s') {
164 (value, Self::MS_PER_SEC)
165 } else if let Some(value) = input.strip_suffix('m') {
166 (value, Self::SECS_PER_MIN * Self::MS_PER_SEC)
167 } else if let Some(value) = input.strip_suffix('h') {
168 (
169 value,
170 Self::MINS_PER_HOUR * Self::SECS_PER_MIN * Self::MS_PER_SEC,
171 )
172 } else if let Some(value) = input.strip_suffix('d') {
173 (
174 value,
175 Self::HOURS_PER_DAY * Self::MINS_PER_HOUR * Self::SECS_PER_MIN * Self::MS_PER_SEC,
176 )
177 } else {
178 (input, 1)
179 };
180 if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
181 return Err(TypeParseError::InvalidDuration);
182 }
183 let value = digits
184 .parse::<u64>()
185 .map_err(|_| TypeParseError::InvalidDuration)?;
186 value
187 .checked_mul(multiplier)
188 .map(Self)
189 .ok_or(TypeParseError::InvalidDuration)
190 }
191}
192
193impl Add for Duration {
194 type Output = Self;
195
196 fn add(self, other: Self) -> Self::Output {
197 Self(self.0.saturating_add(other.0))
198 }
199}
200
201impl AddAssign for Duration {
202 fn add_assign(&mut self, other: Self) {
203 self.0 = self.0.saturating_add(other.0);
204 }
205}
206
207impl<'de> Deserialize<'de> for Duration {
208 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
209 where
210 D: Deserializer<'de>,
211 {
212 struct DurationVisitor;
213
214 impl serde::de::Visitor<'_> for DurationVisitor {
215 type Value = Duration;
216
217 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
218 formatter.write_str("milliseconds or duration string")
219 }
220
221 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
222 where
223 E: serde::de::Error,
224 {
225 Duration::try_from_i64(value)
226 .ok_or_else(|| E::custom("duration must be non-negative"))
227 }
228
229 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
230 Ok(Duration::from_millis(value))
231 }
232
233 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
234 where
235 E: serde::de::Error,
236 {
237 Duration::parse_flexible(value).map_err(E::custom)
238 }
239 }
240
241 if !deserializer.is_human_readable() {
242 return u64::deserialize(deserializer).map(Self);
243 }
244
245 deserializer.deserialize_any(DurationVisitor)
246 }
247}
248
249impl From<u64> for Duration {
250 fn from(value: u64) -> Self {
251 Self(value)
252 }
253}
254
255impl Serialize for Duration {
256 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
257 where
258 S: serde::Serializer,
259 {
260 serializer.serialize_u64(self.0)
261 }
262}
263
264impl Sub for Duration {
265 type Output = Self;
266
267 fn sub(self, other: Self) -> Self::Output {
268 Self(self.0.saturating_sub(other.0))
269 }
270}
271
272impl SubAssign for Duration {
273 fn sub_assign(&mut self, other: Self) {
274 self.0 = self.0.saturating_sub(other.0);
275 }
276}
277
278#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
280#[repr(transparent)]
281pub struct Timestamp(i64);
282
283impl Timestamp {
284 pub const EPOCH: Self = Self(0);
286 pub const MIN: Self = Self(i64::MIN);
288 pub const MAX: Self = Self(i64::MAX);
290
291 const MILLIS_PER_SEC: i64 = 1_000;
292
293 #[must_use]
295 pub const fn from_secs(seconds: i64) -> Self {
296 Self(seconds.saturating_mul(Self::MILLIS_PER_SEC))
297 }
298
299 #[must_use]
301 pub const fn from_millis(millis: i64) -> Self {
302 Self(millis)
303 }
304
305 #[must_use]
307 pub const fn try_from_i64(millis: i64) -> Option<Self> {
308 Some(Self(millis))
309 }
310
311 #[must_use]
313 pub fn try_from_u64(millis: u64) -> Option<Self> {
314 i64::try_from(millis).ok().map(Self)
315 }
316
317 #[must_use]
319 pub const fn from_micros(micros: i64) -> Self {
320 Self(micros.div_euclid(Self::MILLIS_PER_SEC))
321 }
322
323 #[must_use]
325 pub const fn from_nanos(nanos: i64) -> Self {
326 Self(nanos.div_euclid(1_000_000))
327 }
328
329 pub fn parse_rfc3339(input: &str) -> Result<Self, TypeParseError> {
336 let bytes = input.as_bytes();
337 if bytes.len() < 20
338 || bytes.get(4) != Some(&b'-')
339 || bytes.get(7) != Some(&b'-')
340 || bytes.get(10) != Some(&b'T')
341 || bytes.get(13) != Some(&b':')
342 || bytes.get(16) != Some(&b':')
343 {
344 return Err(TypeParseError::InvalidTimestamp);
345 }
346 let year = parse_i32(&bytes[0..4])?;
347 let month = parse_u8(&bytes[5..7])?;
348 let day = parse_u8(&bytes[8..10])?;
349 let hour = parse_u8(&bytes[11..13])?;
350 let minute = parse_u8(&bytes[14..16])?;
351 let second = parse_u8(&bytes[17..19])?;
352
353 let mut cursor = 19;
354 let nanoseconds = if bytes.get(cursor) == Some(&b'.') {
355 cursor += 1;
356 let start = cursor;
357 while bytes.get(cursor).is_some_and(u8::is_ascii_digit) {
358 cursor += 1;
359 }
360 if cursor == start {
361 return Err(TypeParseError::InvalidTimestamp);
362 }
363 parse_fractional_nanoseconds(&bytes[start..cursor])?
364 } else {
365 0
366 };
367 let (sign, offset_hour, offset_minute) = parse_offset(&bytes[cursor..])?;
368 let date = Date::try_new(year, month, day).ok_or(TypeParseError::InvalidTimestamp)?;
369 if hour > 23 || minute > 59 || second > 59 || offset_hour > 23 || offset_minute > 59 {
370 return Err(TypeParseError::InvalidTimestamp);
371 }
372 let local_seconds = i64::from(date.as_days_since_epoch()) * 86_400
376 + i64::from(hour) * 3_600
377 + i64::from(minute) * 60
378 + i64::from(second);
379 let offset_seconds =
380 i64::from(sign) * (i64::from(offset_hour) * 3_600 + i64::from(offset_minute) * 60);
381 Ok(Self(
382 (local_seconds - offset_seconds) * Self::MILLIS_PER_SEC
383 + i64::from(nanoseconds / 1_000_000),
384 ))
385 }
386
387 pub fn parse_flexible(input: &str) -> Result<Self, TypeParseError> {
393 input
394 .parse::<i64>()
395 .map(Self)
396 .or_else(|_| Self::parse_rfc3339(input))
397 }
398
399 #[must_use]
401 pub const fn as_millis(self) -> i64 {
402 self.0
403 }
404
405 #[must_use]
407 pub const fn as_secs(self) -> i64 {
408 self.0.div_euclid(Self::MILLIS_PER_SEC)
409 }
410
411 fn from_wide_millis_saturating(millis: i128) -> Self {
412 match i64::try_from(millis) {
413 Ok(millis) => Self(millis),
414 Err(_) if millis.is_negative() => Self::MIN,
415 Err(_) => Self::MAX,
416 }
417 }
418}
419
420impl Add<Duration> for Timestamp {
421 type Output = Self;
422
423 fn add(self, duration: Duration) -> Self::Output {
424 Self::from_wide_millis_saturating(i128::from(self.0) + i128::from(duration.as_millis()))
425 }
426}
427
428impl AddAssign<Duration> for Timestamp {
429 fn add_assign(&mut self, duration: Duration) {
430 *self = *self + duration;
431 }
432}
433
434impl<'de> Deserialize<'de> for Timestamp {
435 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
436 where
437 D: Deserializer<'de>,
438 {
439 struct TimestampVisitor;
440
441 impl serde::de::Visitor<'_> for TimestampVisitor {
442 type Value = Timestamp;
443
444 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
445 formatter.write_str("unix millis or RFC3339 timestamp")
446 }
447
448 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
449 Ok(Timestamp::from_millis(value))
450 }
451
452 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
453 where
454 E: serde::de::Error,
455 {
456 Timestamp::try_from_u64(value)
457 .ok_or_else(|| E::custom("unix millis exceeds i64 timestamp range"))
458 }
459
460 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
461 where
462 E: serde::de::Error,
463 {
464 Timestamp::parse_flexible(value).map_err(E::custom)
465 }
466 }
467
468 if !deserializer.is_human_readable() {
469 return i64::deserialize(deserializer).map(Self);
470 }
471
472 deserializer.deserialize_any(TimestampVisitor)
473 }
474}
475
476impl Display for Timestamp {
477 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
478 Display::fmt(&self.0, formatter)
479 }
480}
481
482impl From<i64> for Timestamp {
483 fn from(value: i64) -> Self {
484 Self(value)
485 }
486}
487
488impl Serialize for Timestamp {
489 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
490 where
491 S: serde::Serializer,
492 {
493 serializer.serialize_i64(self.0)
494 }
495}
496
497impl Sub<Duration> for Timestamp {
498 type Output = Self;
499
500 fn sub(self, duration: Duration) -> Self::Output {
501 Self::from_wide_millis_saturating(i128::from(self.0) - i128::from(duration.as_millis()))
502 }
503}
504
505impl Sub for Timestamp {
506 type Output = Duration;
507
508 fn sub(self, other: Self) -> Self::Output {
509 if self.0 <= other.0 {
510 return Duration::ZERO;
511 }
512 Duration::from_millis(
513 u64::try_from(i128::from(self.0) - i128::from(other.0)).unwrap_or(u64::MAX),
514 )
515 }
516}
517
518impl SubAssign<Duration> for Timestamp {
519 fn sub_assign(&mut self, duration: Duration) {
520 *self = *self - duration;
521 }
522}
523
524fn parse_i32(bytes: &[u8]) -> Result<i32, TypeParseError> {
525 bytes
526 .iter()
527 .try_fold(0_i32, |value, byte| {
528 byte.checked_sub(b'0')
529 .filter(|digit| *digit <= 9)
530 .and_then(|digit| value.checked_mul(10)?.checked_add(i32::from(digit)))
531 })
532 .ok_or(TypeParseError::InvalidTimestamp)
533}
534
535fn parse_u8(bytes: &[u8]) -> Result<u8, TypeParseError> {
536 bytes
537 .iter()
538 .try_fold(0_u8, |value, byte| {
539 byte.checked_sub(b'0')
540 .filter(|digit| *digit <= 9)
541 .and_then(|digit| value.checked_mul(10)?.checked_add(digit))
542 })
543 .ok_or(TypeParseError::InvalidTimestamp)
544}
545
546fn parse_fractional_nanoseconds(bytes: &[u8]) -> Result<u32, TypeParseError> {
547 let mut value = 0_u32;
548 for byte in bytes.iter().take(9) {
549 let digit = byte
550 .checked_sub(b'0')
551 .filter(|digit| *digit <= 9)
552 .ok_or(TypeParseError::InvalidTimestamp)?;
553 value = value
554 .checked_mul(10)
555 .and_then(|current| current.checked_add(u32::from(digit)))
556 .ok_or(TypeParseError::InvalidTimestamp)?;
557 }
558 for _ in bytes.len().min(9)..9 {
559 value = value
560 .checked_mul(10)
561 .ok_or(TypeParseError::InvalidTimestamp)?;
562 }
563 Ok(value)
564}
565
566fn parse_offset(bytes: &[u8]) -> Result<(i8, u8, u8), TypeParseError> {
567 match bytes {
568 [b'Z'] => Ok((1, 0, 0)),
569 [sign @ (b'+' | b'-'), hour0, hour1, b':', minute0, minute1] => Ok((
570 if *sign == b'+' { 1 } else { -1 },
571 parse_u8(&[*hour0, *hour1])?,
572 parse_u8(&[*minute0, *minute1])?,
573 )),
574 _ => Err(TypeParseError::InvalidTimestamp),
575 }
576}