1use crate::{
20 error::{ErrorKind, Result, WriteError, err},
21 limits::Limits,
22 text::trim_ows,
23 write::Sink,
24};
25
26mod mail_date;
27
28pub use mail_date::MailDate;
29
30#[must_use]
40pub const fn is_leap_year(year: i32) -> bool {
41 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
42}
43
44#[must_use]
56pub const fn days_in_month(year: i32, month: u8) -> u8 {
57 match month {
58 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
59 4 | 6 | 9 | 11 => 30,
60 2 if is_leap_year(year) => 29,
61 2 => 28,
62 _ => 0,
63 }
64}
65
66#[must_use]
82pub const fn days_from_civil(year: i32, month: u8, day: u8) -> i64 {
83 let m = month as i64;
84 let y = if m <= 2 { year as i64 - 1 } else { year as i64 };
85 let era = if y >= 0 { y } else { y - 399 } / 400;
86 let yoe = y - era * 400;
87 let mp = (m + 9) % 12;
88 let doy = (153 * mp + 2) / 5 + day as i64 - 1;
89 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
90 era * 146_097 + doe - 719_468
91}
92
93const DAYS_MIN: i64 = days_from_civil(i32::MIN, 1, 1);
96const DAYS_MAX: i64 = days_from_civil(i32::MAX, 12, 31);
97
98#[must_use]
114pub const fn civil_from_days(days: i64) -> (i32, u8, u8) {
115 let days = if days > DAYS_MAX {
116 DAYS_MAX
117 } else if days < DAYS_MIN {
118 DAYS_MIN
119 } else {
120 days
121 };
122 let z = days + 719_468;
123 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
124 let doe = z - era * 146_097;
125 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
126 let y = yoe + era * 400;
127 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
128 let mp = (5 * doy + 2) / 153;
129 let d = doy - (153 * mp + 2) / 5 + 1;
130 let m = if mp < 10 { mp + 3 } else { mp - 9 };
131 let y = if m <= 2 { y + 1 } else { y };
132 (y as i32, m as u8, d as u8)
135}
136
137#[must_use]
146pub const fn weekday_from_days(days: i64) -> u8 {
147 ((days.rem_euclid(7) + 3) % 7 + 1) as u8
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
161pub struct Civil {
162 pub year: u16,
164 pub month: u8,
166 pub day: u8,
168 pub hour: u8,
170 pub minute: u8,
172 pub second: u8,
174 pub weekday: u8,
176}
177
178#[non_exhaustive]
190#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
191pub enum DateFormat {
192 ImfFixdate,
194 Rfc850,
196 Asctime,
198}
199
200const SECS_PER_DAY: i64 = 86_400;
201const MIN_SECS: i64 = days_from_civil(1, 1, 1) * SECS_PER_DAY;
202const MAX_SECS: i64 = days_from_civil(9999, 12, 31) * SECS_PER_DAY + SECS_PER_DAY - 1;
203
204const DAY_NAMES: [&[u8; 3]; 7] = [b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat", b"Sun"];
205const LONG_DAY_NAMES: [&[u8]; 7] = [
206 b"Monday",
207 b"Tuesday",
208 b"Wednesday",
209 b"Thursday",
210 b"Friday",
211 b"Saturday",
212 b"Sunday",
213];
214const MONTH_NAMES: [&[u8; 3]; 12] = [
215 b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec",
216];
217
218#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
232pub struct HttpDate {
233 secs: i64,
234}
235
236impl HttpDate {
237 pub const MIN: Self = Self { secs: MIN_SECS };
248 pub const MAX: Self = Self { secs: MAX_SECS };
257
258 pub const fn from_unix(secs: i64) -> Result<Self> {
274 if secs < MIN_SECS || secs > MAX_SECS {
275 return err(ErrorKind::OutOfRange, 0);
276 }
277 Ok(Self { secs })
278 }
279
280 #[must_use]
290 pub const fn unix(self) -> i64 {
291 self.secs
292 }
293
294 #[must_use]
304 pub fn civil(self) -> Civil {
305 let days = self.secs.div_euclid(SECS_PER_DAY);
306 let rem = self.secs.rem_euclid(SECS_PER_DAY);
307 let (year, month, day) = civil_from_days(days);
308 Civil {
309 year: u16::try_from(year).unwrap_or(0),
311 month,
312 day,
313 hour: (rem / 3600) as u8,
314 minute: (rem % 3600 / 60) as u8,
315 second: (rem % 60) as u8,
316 weekday: weekday_from_days(days),
317 }
318 }
319
320 #[must_use]
330 pub fn format(self) -> [u8; 29] {
331 let c = self.civil();
332 let mut out = *b"Thu, 01 Jan 1970 00:00:00 GMT";
333 let name = DAY_NAMES
334 .get(usize::from(c.weekday).saturating_sub(1))
335 .copied()
336 .unwrap_or(b"Mon");
337 let month = MONTH_NAMES
338 .get(usize::from(c.month).saturating_sub(1))
339 .copied()
340 .unwrap_or(b"Jan");
341 put(&mut out, 0, name);
342 two(&mut out, 5, c.day);
343 put(&mut out, 8, month);
344 two(&mut out, 12, (c.year / 100) as u8);
345 two(&mut out, 14, (c.year % 100) as u8);
346 two(&mut out, 17, c.hour);
347 two(&mut out, 20, c.minute);
348 two(&mut out, 23, c.second);
349 out
350 }
351
352 pub fn write_to(self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
371 out.put(&self.format())?;
372 Ok(())
373 }
374
375 pub fn parse_imf_fixdate(value: &[u8]) -> Result<Self> {
394 let (lead, v) = trimmed(value);
395 imf_fixdate(v, &Limits::DEFAULT).map_err(|e| e.shifted(lead))
396 }
397
398 pub fn parse_at(value: &[u8], now: i64) -> Result<Self> {
424 Self::parse_detailed_with(value, now, &Limits::DEFAULT).map(|(d, _)| d)
425 }
426
427 pub fn parse_at_with(value: &[u8], now: i64, limits: &Limits) -> Result<Self> {
444 Self::parse_detailed_with(value, now, limits).map(|(d, _)| d)
445 }
446
447 pub fn parse_detailed(value: &[u8], now: i64) -> Result<(Self, DateFormat)> {
463 Self::parse_detailed_with(value, now, &Limits::DEFAULT)
464 }
465
466 fn parse_detailed_with(value: &[u8], now: i64, limits: &Limits) -> Result<(Self, DateFormat)> {
467 let (lead, v) = trimmed(value);
468 let result = match (v.get(3), v.iter().position(|&b| b == b',')) {
469 (Some(b','), _) => imf_fixdate(v, limits).map(|d| (d, DateFormat::ImfFixdate)),
470 (Some(b' '), _) => asctime(v, limits).map(|d| (d, DateFormat::Asctime)),
471 (_, Some(_)) => rfc850(v, now, limits).map(|d| (d, DateFormat::Rfc850)),
472 _ => err(ErrorKind::InvalidDate, 0),
473 };
474 result.map_err(|e| e.shifted(lead))
475 }
476
477 #[cfg(feature = "std")]
489 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
490 #[must_use]
491 pub fn now() -> Self {
492 match Self::from_system_time(std::time::SystemTime::now()) {
493 Ok(d) => d,
494 Err(_) => Self::MAX,
495 }
496 }
497
498 #[cfg(feature = "std")]
515 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
516 pub fn from_system_time(t: std::time::SystemTime) -> Result<Self> {
517 let secs = match t.duration_since(std::time::UNIX_EPOCH) {
518 Ok(d) => i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
519 Err(before) => {
520 let d = before.duration();
521 let whole = i64::try_from(d.as_secs()).unwrap_or(i64::MAX);
522 let extra = i64::from(d.subsec_nanos() > 0);
523 0i64.saturating_sub(whole).saturating_sub(extra)
524 }
525 };
526 Self::from_unix(secs)
527 }
528
529 #[cfg(feature = "std")]
542 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
543 #[must_use]
544 pub fn to_system_time(self) -> Option<std::time::SystemTime> {
545 let d = std::time::Duration::from_secs(self.secs.unsigned_abs());
546 if self.secs >= 0 {
547 std::time::UNIX_EPOCH.checked_add(d)
548 } else {
549 std::time::UNIX_EPOCH.checked_sub(d)
550 }
551 }
552}
553
554pub fn http_date_at(value: &[u8], now: i64) -> Result<HttpDate> {
570 HttpDate::parse_at(value, now)
571}
572
573#[cfg(feature = "std")]
588#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
589pub fn http_date(value: &[u8]) -> Result<HttpDate> {
590 HttpDate::parse_at(value, HttpDate::now().unix())
591}
592
593fn trimmed(value: &[u8]) -> (usize, &[u8]) {
594 let lead = crate::text::skip_ows(value);
595 (lead, trim_ows(value))
596}
597
598fn put(out: &mut [u8], at: usize, bytes: &[u8]) {
599 if let Some(dst) = out.get_mut(at..at + bytes.len()) {
600 dst.copy_from_slice(bytes);
601 }
602}
603
604fn two(out: &mut [u8], at: usize, v: u8) {
605 put(out, at, &[b'0' + v / 10 % 10, b'0' + v % 10]);
606}
607
608fn digits(v: &[u8], at: usize, n: usize) -> Result<u32> {
610 let Some(ds) = v.get(at..at + n) else {
611 return err(ErrorKind::InvalidDate, v.len().min(at));
612 };
613 let mut x = 0u32;
614 for (i, &d) in ds.iter().enumerate() {
615 if !d.is_ascii_digit() {
616 return err(ErrorKind::InvalidDate, at + i);
617 }
618 x = x * 10 + u32::from(d - b'0');
619 }
620 Ok(x)
621}
622
623fn expect(v: &[u8], at: usize, lit: &[u8]) -> Result<()> {
624 match v.get(at..at + lit.len()) {
625 Some(s) if s == lit => Ok(()),
626 _ => err(ErrorKind::InvalidDate, at),
627 }
628}
629
630fn month(v: &[u8], at: usize) -> Result<u8> {
632 let name = v.get(at..at + 3).unwrap_or(&[]);
633 match MONTH_NAMES.iter().position(|m| m.as_slice() == name) {
634 Some(i) => Ok(i as u8 + 1),
636 None => err(ErrorKind::InvalidDate, at),
637 }
638}
639
640fn short_day(v: &[u8]) -> Result<u8> {
642 let name = v.get(..3).unwrap_or(&[]);
643 match DAY_NAMES.iter().position(|d| d.as_slice() == name) {
644 Some(i) => Ok(i as u8 + 1),
645 None => err(ErrorKind::InvalidDate, 0),
646 }
647}
648
649fn time(v: &[u8], at: usize, limits: &Limits) -> Result<i64> {
651 let h = digits(v, at, 2)?;
652 expect(v, at + 2, b":")?;
653 let m = digits(v, at + 3, 2)?;
654 expect(v, at + 5, b":")?;
655 let mut s = digits(v, at + 6, 2)?;
656 if h > 23 {
657 return err(ErrorKind::InvalidDate, at);
658 }
659 if m > 59 {
660 return err(ErrorKind::InvalidDate, at + 3);
661 }
662 if s == 60 && limits.allow_leap_second {
663 s = 59;
664 }
665 if s > 59 {
666 return err(ErrorKind::InvalidDate, at + 6);
667 }
668 Ok(i64::from(h * 3600 + m * 60 + s))
669}
670
671fn assemble(
673 year: u32,
674 month: u8,
675 day: u32,
676 secs: i64,
677 weekday: u8,
678 year_at: usize,
679 day_at: usize,
680) -> Result<HttpDate> {
681 if year == 0 || year > 9999 {
682 return err(ErrorKind::OutOfRange, year_at);
683 }
684 let y = year as i32;
686 if day == 0 || day > u32::from(days_in_month(y, month)) {
687 return err(ErrorKind::InvalidDate, day_at);
688 }
689 let days = days_from_civil(y, month, day as u8);
690 if weekday_from_days(days) != weekday {
691 return err(ErrorKind::WeekdayMismatch, 0);
692 }
693 HttpDate::from_unix(days * SECS_PER_DAY + secs)
694}
695
696fn trailing(v: &[u8], len: usize) -> Result<()> {
697 if v.len() > len {
698 return err(ErrorKind::TrailingData, len);
699 }
700 Ok(())
701}
702
703fn imf_fixdate(v: &[u8], limits: &Limits) -> Result<HttpDate> {
705 let weekday = short_day(v)?;
706 expect(v, 3, b", ")?;
707 let day = digits(v, 5, 2)?;
708 expect(v, 7, b" ")?;
709 let m = month(v, 8)?;
710 expect(v, 11, b" ")?;
711 let year = digits(v, 12, 4)?;
712 expect(v, 16, b" ")?;
713 let secs = time(v, 17, limits)?;
714 expect(v, 25, b" GMT")?;
715 trailing(v, 29)?;
716 assemble(year, m, day, secs, weekday, 12, 5)
717}
718
719fn asctime(v: &[u8], limits: &Limits) -> Result<HttpDate> {
721 let weekday = short_day(v)?;
722 expect(v, 3, b" ")?;
723 let m = month(v, 4)?;
724 expect(v, 7, b" ")?;
725 let day = if v.get(8) == Some(&b' ') {
726 digits(v, 9, 1)?
727 } else {
728 digits(v, 8, 2)?
729 };
730 expect(v, 10, b" ")?;
731 let secs = time(v, 11, limits)?;
732 expect(v, 19, b" ")?;
733 let year = digits(v, 20, 4)?;
734 trailing(v, 24)?;
735 assemble(year, m, day, secs, weekday, 20, 8)
736}
737
738fn rfc850(v: &[u8], now: i64, limits: &Limits) -> Result<HttpDate> {
740 let comma = v.iter().position(|&b| b == b',').unwrap_or(0);
741 let name = v.get(..comma).unwrap_or(&[]);
742 let Some(day_index) = LONG_DAY_NAMES.iter().position(|d| *d == name) else {
743 return err(ErrorKind::InvalidDate, 0);
744 };
745 let weekday = day_index as u8 + 1;
746 let at = comma + 2;
747 expect(v, comma, b", ")?;
748 let day = digits(v, at, 2)?;
749 expect(v, at + 2, b"-")?;
750 let m = month(v, at + 3)?;
751 expect(v, at + 6, b"-")?;
752 let yy = digits(v, at + 7, 2)?;
753 expect(v, at + 9, b" ")?;
754 let secs = time(v, at + 10, limits)?;
755 expect(v, at + 18, b" GMT")?;
756 trailing(v, at + 22)?;
757 let year = resolve_two_digit_year(yy, m, day, secs, now);
758 assemble(year, m, day, secs, weekday, at + 7, at)
759}
760
761fn resolve_two_digit_year(yy: u32, month: u8, day: u32, secs: i64, now: i64) -> u32 {
766 let now = HttpDate::from_unix(now.clamp(MIN_SECS, MAX_SECS))
767 .unwrap_or(HttpDate::MIN)
768 .civil();
769 let now_key = (
770 u32::from(now.month),
771 u32::from(now.day),
772 i64::from(now.hour) * 3600 + i64::from(now.minute) * 60 + i64::from(now.second),
773 );
774 let key = (u32::from(month), day, secs);
775 let now_year = u32::from(now.year);
776 let mut year = now_year - now_year % 100 + yy;
777 if (year, key) > (now_year + 50, now_key) {
779 year = year.saturating_sub(100);
780 } else if (year, key) <= (now_year.saturating_sub(50), now_key) {
781 year += 100;
782 }
783 year
784}
785
786#[cfg(test)]
787mod tests {
788 use super::*;
789
790 const NOW_2026: i64 = 1_792_540_800; fn at(v: &[u8]) -> Result<HttpDate> {
793 HttpDate::parse_at(v, NOW_2026)
794 }
795
796 #[test]
797 fn test_calendar_round_trips_every_day_of_four_centuries() {
798 let start = days_from_civil(1600, 1, 1);
799 for days in start..start + 146_097 {
800 let (y, m, d) = civil_from_days(days);
801 assert_eq!(days_from_civil(y, m, d), days);
802 assert!(d >= 1 && d <= days_in_month(y, m));
803 }
804 assert_eq!(civil_from_days(i64::MAX), (i32::MAX, 12, 31));
805 assert_eq!(civil_from_days(DAYS_MAX + 1), (i32::MAX, 12, 31));
806 assert_eq!(civil_from_days(i64::MIN), (i32::MIN, 1, 1));
807 assert_eq!(civil_from_days(DAYS_MIN - 1), (i32::MIN, 1, 1));
808 }
809
810 #[test]
811 fn test_weekday_known_dates() {
812 assert_eq!(weekday_from_days(days_from_civil(1994, 11, 6)), 7);
813 assert_eq!(weekday_from_days(days_from_civil(2000, 1, 1)), 6);
814 assert_eq!(weekday_from_days(days_from_civil(1, 1, 1)), 1);
815 assert_eq!(weekday_from_days(-1), 3);
816 }
817
818 #[test]
819 fn test_http_date_rfc_examples_all_formats() {
820 let d = HttpDate::from_unix(784_111_777).unwrap();
821 assert_eq!(at(b"Sun, 06 Nov 1994 08:49:37 GMT").unwrap(), d);
822 assert_eq!(at(b"Sunday, 06-Nov-94 08:49:37 GMT").unwrap(), d);
823 assert_eq!(at(b"Sun Nov 6 08:49:37 1994").unwrap(), d);
824 assert_eq!(at(b" Sun, 06 Nov 1994 08:49:37 GMT \t").unwrap(), d);
825 }
826
827 #[test]
828 fn test_http_date_rejections_table() {
829 use ErrorKind::*;
830 let cases: &[(&[u8], ErrorKind)] = &[
831 (b"Mon, 06 Nov 1994 08:49:37 GMT", WeekdayMismatch),
832 (b"sun, 06 Nov 1994 08:49:37 GMT", InvalidDate),
833 (b"Sun, 06 nov 1994 08:49:37 GMT", InvalidDate),
834 (b"Sun, 06 Nov 1994 08:49:37 UTC", InvalidDate),
835 (b"Sun, 06 Nov 1994 08:49:37 +0000", InvalidDate),
836 (b"Fri, 29 Feb 2023 00:00:00 GMT", InvalidDate),
837 (b"Thu, 31 Apr 2025 00:00:00 GMT", InvalidDate),
838 (b"Sun, 06 Nov 1994 24:00:00 GMT", InvalidDate),
839 (b"Sun, 06 Nov 1994 23:60:00 GMT", InvalidDate),
840 (b"Sun, 06 Nov 1994 23:59:60 GMT", InvalidDate),
841 (b"Sat, 01 Jan 0000 00:00:00 GMT", OutOfRange),
842 (b"Sun, 06 Nov 1994 08:49:37 GMTx", TrailingData),
843 (b"Sun, 06 Nov 1994 08:49:37 GM", InvalidDate),
844 (b"Sun, 6 Nov 1994 08:49:37 GMT", InvalidDate),
845 (b"Sun Nov 6 08:49:37 1994", InvalidDate),
846 (b"Sunday, 06-Nov-1994 08:49:37 GMT", InvalidDate),
847 (b"Sun, 00 Nov 1994 08:49:37 GMT", InvalidDate),
848 (b"", InvalidDate),
849 (b"garbage", InvalidDate),
850 (b"Sun, 06 Nov 1994 08:49:37 GMT, x", TrailingData),
851 ];
852 for (v, kind) in cases {
853 assert_eq!(
854 at(v).unwrap_err().kind(),
855 *kind,
856 "{}",
857 std::string::String::from_utf8_lossy(v)
858 );
859 }
860 }
861
862 #[test]
863 fn test_http_date_leap_year_and_leap_second() {
864 assert!(at(b"Thu, 29 Feb 2024 00:00:00 GMT").is_ok());
865 let v = b"Sun, 06 Nov 1994 08:49:60 GMT";
866 assert!(at(v).is_err());
867 let l = Limits::DEFAULT.allow_leap_second(true);
868 let d = HttpDate::parse_at_with(v, NOW_2026, &l).unwrap();
869 assert_eq!(d.civil().second, 59);
870 }
871
872 #[test]
873 fn test_http_date_range_ends() {
874 assert_eq!(&HttpDate::MAX.format(), b"Fri, 31 Dec 9999 23:59:59 GMT");
875 assert_eq!(&HttpDate::MIN.format(), b"Mon, 01 Jan 0001 00:00:00 GMT");
876 assert_eq!(at(b"Fri, 31 Dec 9999 23:59:59 GMT").unwrap(), HttpDate::MAX);
877 assert_eq!(at(b"Mon, 01 Jan 0001 00:00:00 GMT").unwrap(), HttpDate::MIN);
878 assert!(HttpDate::from_unix(MIN_SECS - 1).is_err());
879 assert!(HttpDate::from_unix(MAX_SECS + 1).is_err());
880 assert_eq!(MIN_SECS, -62_135_596_800);
881 assert_eq!(MAX_SECS, 253_402_300_799);
882 }
883
884 #[test]
885 fn test_rfc850_fifty_year_rule() {
886 let y = |v: &[u8]| at(v).unwrap().civil().year;
887 assert_eq!(y(b"Wednesday, 01-Jan-76 00:00:00 GMT"), 2076);
888 assert_eq!(y(b"Saturday, 01-Jan-77 00:00:00 GMT"), 1977);
889 assert_eq!(y(b"Wednesday, 21-Oct-76 00:00:00 GMT"), 2076);
890 assert_eq!(y(b"Wednesday, 21-Oct-26 00:00:00 GMT"), 2026);
891 assert_eq!(y(b"Tuesday, 01-Jan-80 00:00:00 GMT"), 1980);
892 let now_2099 = HttpDate::parse_imf_fixdate(b"Thu, 01 Jan 2099 00:00:00 GMT")
894 .unwrap()
895 .unix();
896 let d = HttpDate::parse_at(b"Friday, 01-Jan-00 00:00:00 GMT", now_2099).unwrap();
897 assert_eq!(d.civil().year, 2100);
898 let d = HttpDate::parse_at(b"Saturday, 01-Jan-50 00:00:00 GMT", now_2099).unwrap();
899 assert_eq!(d.civil().year, 2050);
900 }
901
902 #[test]
903 fn test_asctime_day_forms() {
904 assert!(at(b"Wed Nov 16 08:49:37 1994").is_ok());
905 assert_eq!(
906 at(b"Sun Nov 6 08:49:37 1994 ").unwrap().unix(),
907 784_111_777
908 );
909 assert_eq!(
910 at(b"Sun Nov 6 08:49:37 1994x").unwrap_err().kind(),
911 ErrorKind::TrailingData
912 );
913 }
914
915 #[test]
916 fn test_format_parse_round_trip_sampled() {
917 let mut secs = MIN_SECS;
918 while secs < MAX_SECS {
919 let d = HttpDate::from_unix(secs).unwrap();
920 assert_eq!(HttpDate::parse_imf_fixdate(&d.format()).unwrap(), d);
921 secs += 7_919_993; }
923 }
924
925 #[cfg(feature = "std")]
926 #[test]
927 fn test_system_time_conversions() {
928 use std::time::{Duration, UNIX_EPOCH};
929 let t = UNIX_EPOCH + Duration::from_millis(1500);
930 assert_eq!(HttpDate::from_system_time(t).unwrap().unix(), 1);
931 let before = UNIX_EPOCH - Duration::from_millis(1500);
932 assert_eq!(HttpDate::from_system_time(before).unwrap().unix(), -2);
933 let d = HttpDate::from_unix(784_111_777).unwrap();
934 assert_eq!(
935 HttpDate::from_system_time(d.to_system_time().unwrap()).unwrap(),
936 d
937 );
938 assert!(HttpDate::now() > d);
939 assert_eq!(http_date(b"Sun, 06 Nov 1994 08:49:37 GMT").unwrap(), d);
940 }
941}