1use core::{
24 cmp, fmt,
25 ops::{Add, AddAssign, Sub, SubAssign},
26 str::{self, FromStr},
27 time::Duration,
28};
29
30use crate::{
31 buf::Buffer,
32 value::{FromValue, ToValue, Value},
33};
34
35#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Timestamp(Duration);
40
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub struct Parts {
48 pub years: u16,
52 pub months: u8,
56 pub days: u8,
60 pub hours: u8,
64 pub minutes: u8,
68 pub seconds: u8,
72 pub nanos: u32,
76}
77
78const LEAPOCH_SECS: u64 = 946_684_800 + 86400 * (31 + 29);
80const DAYS_PER_400Y: i32 = 365 * 400 + 97;
81const DAYS_PER_100Y: i32 = 365 * 100 + 24;
82const DAYS_PER_4Y: i32 = 365 * 4 + 1;
83const DAYS_IN_MONTH: [u8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
84
85const MIN: Duration = Duration::new(0, 0);
87
88const MAX: Duration = Duration::new(253402300799, 999999999);
90
91impl Timestamp {
92 pub const MIN: Self = Timestamp(MIN);
96
97 pub const MAX: Self = Timestamp(MAX);
101
102 pub fn from_unix(unix_time: Duration) -> Option<Self> {
108 if unix_time >= MIN && unix_time <= MAX {
109 Some(Timestamp(unix_time))
110 } else {
111 None
112 }
113 }
114
115 pub fn to_unix(&self) -> Duration {
119 self.0
120 }
121
122 pub fn try_from_str(ts: &str) -> Result<Self, ParseTimestampError> {
126 ts.parse()
127 }
128
129 pub fn parse(ts: impl fmt::Display) -> Result<Self, ParseTimestampError> {
133 let mut buf = Buffer::<30>::new();
134
135 Self::try_from_str(
136 str::from_utf8(buf.buffer(ts).ok_or_else(|| ParseTimestampError {})?)
137 .map_err(|_| ParseTimestampError {})?,
138 )
139 }
140
141 pub fn duration_since(self, earlier: Self) -> Option<Duration> {
147 self.0.checked_sub(earlier.0)
148 }
149
150 #[cfg(feature = "std")]
156 pub fn to_system_time(&self) -> std::time::SystemTime {
157 std::time::SystemTime::UNIX_EPOCH + self.0
158 }
159
160 pub fn from_parts(parts: Parts) -> Option<Self> {
168 let is_leap;
169 let start_of_year;
170 let year = (parts.years as i64) - 1900;
171
172 if year as u64 <= 138 {
176 let mut leaps: i64 = (year - 68) >> 2;
177 if (year - 68).trailing_zeros() >= 2 {
178 leaps -= 1;
179 is_leap = true;
180 } else {
181 is_leap = false;
182 }
183
184 start_of_year = i128::from(31_536_000 * (year - 70) + 86400 * leaps);
185 } else {
186 let centuries: i64;
187 let mut leaps: i64;
188
189 let mut cycles: i64 = (year - 100) / 400;
190 let mut rem: i64 = (year - 100) % 400;
191
192 if rem < 0 {
193 cycles -= 1;
194 rem += 400
195 }
196 if rem == 0 {
197 is_leap = true;
198 centuries = 0;
199 leaps = 0;
200 } else {
201 if rem >= 200 {
202 if rem >= 300 {
203 centuries = 3;
204 rem -= 300;
205 } else {
206 centuries = 2;
207 rem -= 200;
208 }
209 } else if rem >= 100 {
210 centuries = 1;
211 rem -= 100;
212 } else {
213 centuries = 0;
214 }
215 if rem == 0 {
216 is_leap = false;
217 leaps = 0;
218 } else {
219 leaps = rem / 4;
220 rem %= 4;
221 is_leap = rem == 0;
222 }
223 }
224 leaps += 97 * cycles + 24 * centuries - i64::from(is_leap);
225
226 start_of_year = i128::from((year - 100) * 31_536_000)
227 + i128::from(leaps * 86400 + 946_684_800 + 86400);
228 }
229
230 let seconds_within_month = 86400 * u32::from(parts.days - 1)
231 + 3600 * u32::from(parts.hours)
232 + 60 * u32::from(parts.minutes)
233 + u32::from(parts.seconds);
234
235 let mut seconds_within_year = [
236 0, 31 * 86400, 59 * 86400, 90 * 86400, 120 * 86400, 151 * 86400, 181 * 86400, 212 * 86400, 243 * 86400, 273 * 86400, 304 * 86400, 334 * 86400, ][usize::from(parts.months - 1) % 12]
249 + seconds_within_month;
250
251 if is_leap && parts.months > 2 {
252 seconds_within_year += 86400
253 }
254
255 Timestamp::from_unix(Duration::new(
256 (start_of_year + i128::from(seconds_within_year))
257 .try_into()
258 .ok()?,
259 parts.nanos,
260 ))
261 }
262
263 pub fn to_parts(&self) -> Parts {
269 let dur = self.0;
270 let secs = dur.as_secs();
271 let nanos = dur.subsec_nanos();
272
273 let mut days = ((secs as i64 / 86_400) - (LEAPOCH_SECS as i64 / 86_400)) as i64;
274 let mut remsecs = (secs % 86_400) as i32;
275 if remsecs < 0i32 {
276 remsecs += 86_400;
277 days -= 1
278 }
279
280 let mut qc_cycles: i32 = (days / (DAYS_PER_400Y as i64)) as i32;
281 let mut remdays: i32 = (days % (DAYS_PER_400Y as i64)) as i32;
282 if remdays < 0 {
283 remdays += DAYS_PER_400Y;
284 qc_cycles -= 1;
285 }
286
287 let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
288 if c_cycles == 4 {
289 c_cycles -= 1;
290 }
291 remdays -= c_cycles * DAYS_PER_100Y;
292
293 let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
294 if q_cycles == 25 {
295 q_cycles -= 1;
296 }
297 remdays -= q_cycles * DAYS_PER_4Y;
298
299 let mut remyears: i32 = remdays / 365;
300 if remyears == 4 {
301 remyears -= 1;
302 }
303 remdays -= remyears * 365;
304
305 let mut years: i64 = i64::from(remyears)
306 + 4 * i64::from(q_cycles)
307 + 100 * i64::from(c_cycles)
308 + 400 * i64::from(qc_cycles);
309
310 let mut months: i32 = 0;
311 while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
312 remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
313 months += 1
314 }
315
316 if months >= 10 {
317 months -= 12;
318 years += 1;
319 }
320
321 let years = (years + 2000) as u16;
322 let months = (months + 3) as u8;
323 let days = (remdays + 1) as u8;
324 let hours = (remsecs / 3600) as u8;
325 let minutes = (remsecs / 60 % 60) as u8;
326 let seconds = (remsecs % 60) as u8;
327
328 Parts {
329 years,
330 months,
331 days,
332 hours,
333 minutes,
334 seconds,
335 nanos,
336 }
337 }
338
339 #[must_use = "the result of addition is returned without modifying the original"]
345 pub fn checked_add(&self, rhs: Duration) -> Option<Self> {
346 Timestamp::from_unix(self.to_unix().checked_add(rhs)?)
347 }
348
349 #[must_use = "the result of subtraction is returned without modifying the original"]
355 pub fn checked_sub(&self, rhs: Duration) -> Option<Self> {
356 Timestamp::from_unix(self.to_unix().checked_sub(rhs)?)
357 }
358
359 pub fn checked_duration_since(&self, earlier: Timestamp) -> Option<Duration> {
363 self.to_unix().checked_sub(earlier.to_unix())
364 }
365}
366
367impl Add<Duration> for Timestamp {
368 type Output = Timestamp;
369
370 fn add(self, rhs: Duration) -> Self::Output {
371 self.checked_add(rhs).expect("overflow adding to timestamp")
372 }
373}
374
375impl AddAssign<Duration> for Timestamp {
376 fn add_assign(&mut self, rhs: Duration) {
377 *self = *self + rhs;
378 }
379}
380
381impl Sub<Duration> for Timestamp {
382 type Output = Timestamp;
383
384 fn sub(self, rhs: Duration) -> Self::Output {
385 self.checked_sub(rhs)
386 .expect("overflow subtracting from timestamp")
387 }
388}
389
390impl SubAssign<Duration> for Timestamp {
391 fn sub_assign(&mut self, rhs: Duration) {
392 *self = *self - rhs;
393 }
394}
395
396impl Sub<Timestamp> for Timestamp {
397 type Output = Duration;
398
399 fn sub(self, earlier: Timestamp) -> Self::Output {
400 self.checked_duration_since(earlier)
401 .expect("overflow subtracting from timestamp")
402 }
403}
404
405impl fmt::Debug for Timestamp {
406 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
407 use fmt::Write as _;
408
409 f.write_char('"')?;
410 fmt_rfc3339(*self, f)?;
411 f.write_char('"')
412 }
413}
414
415impl fmt::Display for Timestamp {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 fmt_rfc3339(*self, f)
418 }
419}
420
421impl FromStr for Timestamp {
422 type Err = ParseTimestampError;
423
424 fn from_str(s: &str) -> Result<Self, Self::Err> {
425 parse_rfc3339(s)
426 }
427}
428
429impl ToValue for Timestamp {
430 fn to_value(&self) -> Value<'_> {
431 Value::capture_display(self)
432 }
433}
434
435impl<'v> FromValue<'v> for Timestamp {
436 fn from_value(value: Value<'v>) -> Option<Self> {
437 value
438 .downcast_ref::<Timestamp>()
439 .copied()
440 .or_else(|| value.parse())
441 }
442}
443
444impl<'a> PartialEq<&'a Timestamp> for Timestamp {
445 fn eq(&self, other: &&'a Timestamp) -> bool {
446 self == *other
447 }
448}
449
450impl<'a> PartialEq<Timestamp> for &'a Timestamp {
451 fn eq(&self, other: &Timestamp) -> bool {
452 *self == other
453 }
454}
455
456#[derive(Debug)]
460pub struct ParseTimestampError {}
461
462impl fmt::Display for ParseTimestampError {
463 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464 write!(f, "the input was not a valid timestamp")
465 }
466}
467
468#[cfg(feature = "std")]
469impl std::error::Error for ParseTimestampError {}
470
471fn parse_rfc3339(fmt: &str) -> Result<Timestamp, ParseTimestampError> {
472 if fmt.len() > 30 || fmt.len() < 19 {
473 return Err(ParseTimestampError {});
475 }
476
477 if *fmt.as_bytes().last().unwrap() != b'Z' {
478 return Err(ParseTimestampError {});
480 }
481
482 let years = u16::from_str_radix(&fmt[0..4], 10).map_err(|_| ParseTimestampError {})?;
483 let months = u8::from_str_radix(&fmt[5..7], 10).map_err(|_| ParseTimestampError {})?;
484 let days = u8::from_str_radix(&fmt[8..10], 10).map_err(|_| ParseTimestampError {})?;
485 let hours = u8::from_str_radix(&fmt[11..13], 10).map_err(|_| ParseTimestampError {})?;
486 let minutes = u8::from_str_radix(&fmt[14..16], 10).map_err(|_| ParseTimestampError {})?;
487 let seconds = u8::from_str_radix(&fmt[17..19], 10).map_err(|_| ParseTimestampError {})?;
488 let nanos = if fmt.len() > 19 {
489 let subsecond = &fmt[20..fmt.len() - 1];
490 u32::from_str_radix(subsecond, 10).unwrap() * 10u32.pow(9 - subsecond.len() as u32)
491 } else {
492 0
493 };
494
495 Timestamp::from_parts(Parts {
496 years,
497 months,
498 days,
499 hours,
500 minutes,
501 seconds,
502 nanos,
503 })
504 .ok_or_else(|| ParseTimestampError {})
505}
506
507fn fmt_rfc3339(ts: Timestamp, f: &mut fmt::Formatter) -> fmt::Result {
508 let Parts {
509 years,
510 months,
511 days,
512 hours,
513 minutes,
514 seconds,
515 nanos: subsecond_nanos,
516 } = ts.to_parts();
517
518 const BUF_INIT: [u8; 30] = *b"0000-00-00T00:00:00.000000000Z";
519
520 let mut buf: [u8; 30] = BUF_INIT;
521 buf[0] = b'0' + (years / 1000) as u8;
522 buf[1] = b'0' + (years / 100 % 10) as u8;
523 buf[2] = b'0' + (years / 10 % 10) as u8;
524 buf[3] = b'0' + (years % 10) as u8;
525 buf[5] = b'0' + (months / 10) as u8;
526 buf[6] = b'0' + (months % 10) as u8;
527 buf[8] = b'0' + (days / 10) as u8;
528 buf[9] = b'0' + (days % 10) as u8;
529 buf[11] = b'0' + (hours / 10) as u8;
530 buf[12] = b'0' + (hours % 10) as u8;
531 buf[14] = b'0' + (minutes / 10) as u8;
532 buf[15] = b'0' + (minutes % 10) as u8;
533 buf[17] = b'0' + (seconds / 10) as u8;
534 buf[18] = b'0' + (seconds % 10) as u8;
535
536 let i = match f.precision() {
537 Some(0) => 19,
538 precision => {
539 let mut i = 20;
540 let mut divisor = 100_000_000;
541 let end = i + cmp::min(9, precision.unwrap_or(9));
542
543 while i < end {
544 buf[i] = b'0' + (subsecond_nanos / divisor % 10) as u8;
545
546 i += 1;
547 divisor /= 10;
548 }
549
550 i
551 }
552 };
553
554 buf[i] = b'Z';
555
556 f.write_str(str::from_utf8(&buf[..=i]).expect("Conversion to utf8 failed"))
558}
559
560#[cfg(feature = "sval")]
561impl sval::Value for Timestamp {
562 fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
563 sval::stream_display(stream, self)
564 }
565}
566
567#[cfg(feature = "serde")]
568impl serde::Serialize for Timestamp {
569 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
570 serializer.collect_str(self)
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577
578 #[test]
579 fn roundtrip() {
580 let ts = Timestamp::from_unix(Duration::new(1691961703, 17532)).unwrap();
581
582 let fmt = ts.to_string();
583
584 for parsed in [
585 Timestamp::try_from_str(&fmt),
586 Timestamp::parse(&fmt),
587 fmt.parse(),
588 ] {
589 let parsed = parsed.unwrap();
590
591 assert_eq!(ts, parsed, "{}", fmt);
592 }
593 }
594
595 #[test]
596 fn parse_invalid() {
597 for case in [
598 "",
599 "0",
600 "2024-01-01T00:00:00.00000000000000000000000000Z",
601 "2024-01-01T00:00:00.000+10",
602 "Thursday, September 12, 2024",
603 ] {
604 assert!(Timestamp::try_from_str(case).is_err());
605 assert!(Timestamp::parse(case).is_err());
606 }
607 }
608
609 #[test]
610 fn parts_max() {
611 let ts = Timestamp::from_parts(Parts {
612 years: 9999,
613 months: 12,
614 days: 31,
615 hours: 23,
616 minutes: 59,
617 seconds: 59,
618 nanos: 999999999,
619 })
620 .unwrap();
621
622 assert_eq!(ts.to_unix(), MAX);
623 }
624
625 #[test]
626 fn parts_min() {
627 let ts = Timestamp::from_parts(Parts {
628 years: 1970,
629 months: 1,
630 days: 1,
631 hours: 0,
632 minutes: 0,
633 seconds: 0,
634 nanos: 0,
635 })
636 .unwrap();
637
638 assert_eq!(ts.to_unix(), MIN);
639 }
640
641 #[test]
642 fn parts_overflow() {
643 let ts = Timestamp::from_parts(Parts {
644 years: 2000,
645 months: 13,
646 days: 32,
647 hours: 25,
648 minutes: 61,
649 seconds: 61,
650 nanos: 1000000000,
651 })
652 .unwrap();
653
654 let expected = Timestamp::from_parts(Parts {
655 years: 2000,
656 months: 13,
657 days: 32,
658 hours: 25,
659 minutes: 61,
660 seconds: 62,
661 nanos: 0,
662 })
663 .unwrap();
664
665 assert_eq!(expected, ts);
666 }
667
668 #[test]
669 fn add() {
670 for (case, add, expected) in [
671 (
672 Timestamp::MIN,
673 Duration::from_nanos(1),
674 Some(Timestamp::from_unix(Duration::from_nanos(1)).unwrap()),
675 ),
676 (Timestamp::MAX, Duration::from_nanos(1), None),
677 (Timestamp::MAX, Duration::MAX, None),
678 ] {
679 assert_eq!(expected, case.checked_add(add));
680 }
681 }
682
683 #[test]
684 fn sub() {
685 for (case, sub, expected) in [
686 (
687 Timestamp::MAX,
688 Duration::from_nanos(1),
689 Some(Timestamp::from_unix(MAX - Duration::from_nanos(1)).unwrap()),
690 ),
691 (Timestamp::MIN, Duration::from_nanos(1), None),
692 (Timestamp::MIN, Duration::MAX, None),
693 ] {
694 assert_eq!(expected, case.checked_sub(sub));
695 }
696 }
697
698 #[test]
699 fn sub_timestamp() {
700 for (case, earlier, expected) in [
701 (Timestamp::MIN, Timestamp::MIN, Some(Duration::from_secs(0))),
702 (
703 Timestamp::from_unix(Duration::from_secs(10)).unwrap(),
704 Timestamp::from_unix(Duration::from_secs(0)).unwrap(),
705 Some(Duration::from_secs(10)),
706 ),
707 (
708 Timestamp::from_unix(Duration::from_secs(0)).unwrap(),
709 Timestamp::from_unix(Duration::from_secs(10)).unwrap(),
710 None,
711 ),
712 (Timestamp::MAX, Timestamp::MIN, Some(MAX)),
713 ] {
714 assert_eq!(expected, case.checked_duration_since(earlier));
715 }
716 }
717
718 #[test]
719 fn to_from_value() {
720 for case in [
721 Timestamp::MIN,
722 Timestamp::MAX,
723 Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
724 ] {
725 let value = case.to_value();
726
727 assert_eq!(case, value.cast::<Timestamp>().unwrap());
728 }
729
730 for (case, expected) in [
731 (
732 Value::from("2024-01-01T00:13:00.000Z"),
733 Some(
734 Timestamp::from_parts(Parts {
735 years: 2024,
736 months: 01,
737 days: 01,
738 hours: 00,
739 minutes: 13,
740 seconds: 00,
741 nanos: 000,
742 })
743 .unwrap(),
744 ),
745 ),
746 (Value::from(""), None),
747 (Value::from("12024-01-01T00:13:00.000Z"), None),
748 (Value::from("2024-01-01T00:13:00.000+10"), None),
749 ] {
750 assert_eq!(expected, case.cast::<Timestamp>());
751 }
752 }
753
754 #[cfg(feature = "sval")]
755 #[test]
756 fn stream() {
757 sval_test::assert_tokens(
758 &Timestamp::try_from_str("2024-01-01T00:13:00.000Z").unwrap(),
759 &[
760 sval_test::Token::TextBegin(None),
761 sval_test::Token::TextFragmentComputed("2024-01-01T00:13:00.000000000Z".to_owned()),
762 sval_test::Token::TextEnd,
763 ],
764 );
765 }
766
767 #[cfg(feature = "serde")]
768 #[test]
769 fn serialize() {
770 serde_test::assert_ser_tokens(
771 &Timestamp::try_from_str("2024-01-01T00:13:00.000Z").unwrap(),
772 &[serde_test::Token::Str("2024-01-01T00:13:00.000000000Z")],
773 );
774 }
775}