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_LIMIT: i64 = 784_352_296_671;
96
97#[must_use]
113pub const fn civil_from_days(days: i64) -> (i32, u8, u8) {
114 let days = if days > DAYS_LIMIT {
115 DAYS_LIMIT
116 } else if days < -DAYS_LIMIT {
117 -DAYS_LIMIT
118 } else {
119 days
120 };
121 let z = days + 719_468;
122 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
123 let doe = z - era * 146_097;
124 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
125 let y = yoe + era * 400;
126 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
127 let mp = (5 * doy + 2) / 153;
128 let d = doy - (153 * mp + 2) / 5 + 1;
129 let m = if mp < 10 { mp + 3 } else { mp - 9 };
130 let y = if m <= 2 { y + 1 } else { y };
131 let y = if y > i32::MAX as i64 {
132 i32::MAX
133 } else if y < i32::MIN as i64 {
134 i32::MIN
135 } else {
136 y as i32
137 };
138 (y, m as u8, d as u8)
140}
141
142#[must_use]
151pub const fn weekday_from_days(days: i64) -> u8 {
152 ((days.rem_euclid(7) + 3) % 7 + 1) as u8
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
166pub struct Civil {
167 pub year: u16,
169 pub month: u8,
171 pub day: u8,
173 pub hour: u8,
175 pub minute: u8,
177 pub second: u8,
179 pub weekday: u8,
181}
182
183#[non_exhaustive]
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
196pub enum DateFormat {
197 ImfFixdate,
199 Rfc850,
201 Asctime,
203}
204
205const SECS_PER_DAY: i64 = 86_400;
206const MIN_SECS: i64 = days_from_civil(1, 1, 1) * SECS_PER_DAY;
207const MAX_SECS: i64 = days_from_civil(9999, 12, 31) * SECS_PER_DAY + SECS_PER_DAY - 1;
208
209const DAY_NAMES: [&[u8; 3]; 7] = [b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat", b"Sun"];
210const LONG_DAY_NAMES: [&[u8]; 7] = [
211 b"Monday",
212 b"Tuesday",
213 b"Wednesday",
214 b"Thursday",
215 b"Friday",
216 b"Saturday",
217 b"Sunday",
218];
219const MONTH_NAMES: [&[u8; 3]; 12] = [
220 b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec",
221];
222
223#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
237pub struct HttpDate {
238 secs: i64,
239}
240
241impl HttpDate {
242 pub const MIN: Self = Self { secs: MIN_SECS };
253 pub const MAX: Self = Self { secs: MAX_SECS };
262
263 pub const fn from_unix(secs: i64) -> Result<Self> {
279 if secs < MIN_SECS || secs > MAX_SECS {
280 return err(ErrorKind::OutOfRange, 0);
281 }
282 Ok(Self { secs })
283 }
284
285 #[must_use]
295 pub const fn unix(self) -> i64 {
296 self.secs
297 }
298
299 #[must_use]
309 pub fn civil(self) -> Civil {
310 let days = self.secs.div_euclid(SECS_PER_DAY);
311 let rem = self.secs.rem_euclid(SECS_PER_DAY);
312 let (year, month, day) = civil_from_days(days);
313 Civil {
314 year: u16::try_from(year).unwrap_or(0),
316 month,
317 day,
318 hour: (rem / 3600) as u8,
319 minute: (rem % 3600 / 60) as u8,
320 second: (rem % 60) as u8,
321 weekday: weekday_from_days(days),
322 }
323 }
324
325 #[must_use]
335 pub fn format(self) -> [u8; 29] {
336 let c = self.civil();
337 let mut out = *b"Thu, 01 Jan 1970 00:00:00 GMT";
338 let name = DAY_NAMES
339 .get(usize::from(c.weekday).saturating_sub(1))
340 .copied()
341 .unwrap_or(b"Mon");
342 let month = MONTH_NAMES
343 .get(usize::from(c.month).saturating_sub(1))
344 .copied()
345 .unwrap_or(b"Jan");
346 put(&mut out, 0, name);
347 two(&mut out, 5, c.day);
348 put(&mut out, 8, month);
349 two(&mut out, 12, (c.year / 100) as u8);
350 two(&mut out, 14, (c.year % 100) as u8);
351 two(&mut out, 17, c.hour);
352 two(&mut out, 20, c.minute);
353 two(&mut out, 23, c.second);
354 out
355 }
356
357 pub fn write_to(self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
376 out.put(&self.format())?;
377 Ok(())
378 }
379
380 pub fn parse_imf_fixdate(value: &[u8]) -> Result<Self> {
399 let (lead, v) = trimmed(value);
400 imf_fixdate(v, &Limits::DEFAULT).map_err(|e| e.shifted(lead))
401 }
402
403 pub fn parse_at(value: &[u8], now: i64) -> Result<Self> {
429 Self::parse_detailed_with(value, now, &Limits::DEFAULT).map(|(d, _)| d)
430 }
431
432 pub fn parse_at_with(value: &[u8], now: i64, limits: &Limits) -> Result<Self> {
449 Self::parse_detailed_with(value, now, limits).map(|(d, _)| d)
450 }
451
452 pub fn parse_detailed(value: &[u8], now: i64) -> Result<(Self, DateFormat)> {
468 Self::parse_detailed_with(value, now, &Limits::DEFAULT)
469 }
470
471 fn parse_detailed_with(value: &[u8], now: i64, limits: &Limits) -> Result<(Self, DateFormat)> {
472 let (lead, v) = trimmed(value);
473 let result = match (v.get(3), v.iter().position(|&b| b == b',')) {
474 (Some(b','), _) => imf_fixdate(v, limits).map(|d| (d, DateFormat::ImfFixdate)),
475 (Some(b' '), _) => asctime(v, limits).map(|d| (d, DateFormat::Asctime)),
476 (_, Some(_)) => rfc850(v, now, limits).map(|d| (d, DateFormat::Rfc850)),
477 _ => err(ErrorKind::InvalidDate, 0),
478 };
479 result.map_err(|e| e.shifted(lead))
480 }
481
482 #[cfg(feature = "std")]
494 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
495 #[must_use]
496 pub fn now() -> Self {
497 match Self::from_system_time(std::time::SystemTime::now()) {
498 Ok(d) => d,
499 Err(_) => Self::MAX,
500 }
501 }
502
503 #[cfg(feature = "std")]
520 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
521 pub fn from_system_time(t: std::time::SystemTime) -> Result<Self> {
522 let secs = match t.duration_since(std::time::UNIX_EPOCH) {
523 Ok(d) => i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
524 Err(before) => {
525 let d = before.duration();
526 let whole = i64::try_from(d.as_secs()).unwrap_or(i64::MAX);
527 let extra = i64::from(d.subsec_nanos() > 0);
528 0i64.saturating_sub(whole).saturating_sub(extra)
529 }
530 };
531 Self::from_unix(secs)
532 }
533
534 #[cfg(feature = "std")]
547 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
548 #[must_use]
549 pub fn to_system_time(self) -> Option<std::time::SystemTime> {
550 let d = std::time::Duration::from_secs(self.secs.unsigned_abs());
551 if self.secs >= 0 {
552 std::time::UNIX_EPOCH.checked_add(d)
553 } else {
554 std::time::UNIX_EPOCH.checked_sub(d)
555 }
556 }
557}
558
559pub fn http_date_at(value: &[u8], now: i64) -> Result<HttpDate> {
575 HttpDate::parse_at(value, now)
576}
577
578#[cfg(feature = "std")]
593#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
594pub fn http_date(value: &[u8]) -> Result<HttpDate> {
595 HttpDate::parse_at(value, HttpDate::now().unix())
596}
597
598fn trimmed(value: &[u8]) -> (usize, &[u8]) {
599 let lead = crate::text::skip_ows(value);
600 (lead, trim_ows(value))
601}
602
603fn put(out: &mut [u8], at: usize, bytes: &[u8]) {
604 if let Some(dst) = out.get_mut(at..at + bytes.len()) {
605 dst.copy_from_slice(bytes);
606 }
607}
608
609fn two(out: &mut [u8], at: usize, v: u8) {
610 put(out, at, &[b'0' + v / 10 % 10, b'0' + v % 10]);
611}
612
613fn digits(v: &[u8], at: usize, n: usize) -> Result<u32> {
615 let Some(ds) = v.get(at..at + n) else {
616 return err(ErrorKind::InvalidDate, v.len().min(at));
617 };
618 let mut x = 0u32;
619 for (i, &d) in ds.iter().enumerate() {
620 if !d.is_ascii_digit() {
621 return err(ErrorKind::InvalidDate, at + i);
622 }
623 x = x * 10 + u32::from(d - b'0');
624 }
625 Ok(x)
626}
627
628fn expect(v: &[u8], at: usize, lit: &[u8]) -> Result<()> {
629 match v.get(at..at + lit.len()) {
630 Some(s) if s == lit => Ok(()),
631 _ => err(ErrorKind::InvalidDate, at),
632 }
633}
634
635fn month(v: &[u8], at: usize) -> Result<u8> {
637 let name = v.get(at..at + 3).unwrap_or(&[]);
638 match MONTH_NAMES.iter().position(|m| m.as_slice() == name) {
639 Some(i) => Ok(i as u8 + 1),
641 None => err(ErrorKind::InvalidDate, at),
642 }
643}
644
645fn short_day(v: &[u8]) -> Result<u8> {
647 let name = v.get(..3).unwrap_or(&[]);
648 match DAY_NAMES.iter().position(|d| d.as_slice() == name) {
649 Some(i) => Ok(i as u8 + 1),
650 None => err(ErrorKind::InvalidDate, 0),
651 }
652}
653
654fn time(v: &[u8], at: usize, limits: &Limits) -> Result<i64> {
656 let h = digits(v, at, 2)?;
657 expect(v, at + 2, b":")?;
658 let m = digits(v, at + 3, 2)?;
659 expect(v, at + 5, b":")?;
660 let mut s = digits(v, at + 6, 2)?;
661 if h > 23 {
662 return err(ErrorKind::InvalidDate, at);
663 }
664 if m > 59 {
665 return err(ErrorKind::InvalidDate, at + 3);
666 }
667 if s == 60 && limits.allow_leap_second {
668 s = 59;
669 }
670 if s > 59 {
671 return err(ErrorKind::InvalidDate, at + 6);
672 }
673 Ok(i64::from(h * 3600 + m * 60 + s))
674}
675
676fn assemble(
678 year: u32,
679 month: u8,
680 day: u32,
681 secs: i64,
682 weekday: u8,
683 year_at: usize,
684 day_at: usize,
685) -> Result<HttpDate> {
686 if year == 0 || year > 9999 {
687 return err(ErrorKind::OutOfRange, year_at);
688 }
689 let y = year as i32;
691 if day == 0 || day > u32::from(days_in_month(y, month)) {
692 return err(ErrorKind::InvalidDate, day_at);
693 }
694 let days = days_from_civil(y, month, day as u8);
695 if weekday_from_days(days) != weekday {
696 return err(ErrorKind::WeekdayMismatch, 0);
697 }
698 HttpDate::from_unix(days * SECS_PER_DAY + secs)
699}
700
701fn trailing(v: &[u8], len: usize) -> Result<()> {
702 if v.len() > len {
703 return err(ErrorKind::TrailingData, len);
704 }
705 Ok(())
706}
707
708fn imf_fixdate(v: &[u8], limits: &Limits) -> Result<HttpDate> {
710 let weekday = short_day(v)?;
711 expect(v, 3, b", ")?;
712 let day = digits(v, 5, 2)?;
713 expect(v, 7, b" ")?;
714 let m = month(v, 8)?;
715 expect(v, 11, b" ")?;
716 let year = digits(v, 12, 4)?;
717 expect(v, 16, b" ")?;
718 let secs = time(v, 17, limits)?;
719 expect(v, 25, b" GMT")?;
720 trailing(v, 29)?;
721 assemble(year, m, day, secs, weekday, 12, 5)
722}
723
724fn asctime(v: &[u8], limits: &Limits) -> Result<HttpDate> {
726 let weekday = short_day(v)?;
727 expect(v, 3, b" ")?;
728 let m = month(v, 4)?;
729 expect(v, 7, b" ")?;
730 let day = if v.get(8) == Some(&b' ') {
731 digits(v, 9, 1)?
732 } else {
733 digits(v, 8, 2)?
734 };
735 expect(v, 10, b" ")?;
736 let secs = time(v, 11, limits)?;
737 expect(v, 19, b" ")?;
738 let year = digits(v, 20, 4)?;
739 trailing(v, 24)?;
740 assemble(year, m, day, secs, weekday, 20, 8)
741}
742
743fn rfc850(v: &[u8], now: i64, limits: &Limits) -> Result<HttpDate> {
745 let comma = v.iter().position(|&b| b == b',').unwrap_or(0);
746 let name = v.get(..comma).unwrap_or(&[]);
747 let Some(day_index) = LONG_DAY_NAMES.iter().position(|d| *d == name) else {
748 return err(ErrorKind::InvalidDate, 0);
749 };
750 let weekday = day_index as u8 + 1;
751 let at = comma + 2;
752 expect(v, comma, b", ")?;
753 let day = digits(v, at, 2)?;
754 expect(v, at + 2, b"-")?;
755 let m = month(v, at + 3)?;
756 expect(v, at + 6, b"-")?;
757 let yy = digits(v, at + 7, 2)?;
758 expect(v, at + 9, b" ")?;
759 let secs = time(v, at + 10, limits)?;
760 expect(v, at + 18, b" GMT")?;
761 trailing(v, at + 22)?;
762 let year = resolve_two_digit_year(yy, m, day, secs, now);
763 assemble(year, m, day, secs, weekday, at + 7, at)
764}
765
766fn resolve_two_digit_year(yy: u32, month: u8, day: u32, secs: i64, now: i64) -> u32 {
771 let now = HttpDate::from_unix(now.clamp(MIN_SECS, MAX_SECS))
772 .unwrap_or(HttpDate::MIN)
773 .civil();
774 let now_key = (
775 u32::from(now.month),
776 u32::from(now.day),
777 i64::from(now.hour) * 3600 + i64::from(now.minute) * 60 + i64::from(now.second),
778 );
779 let key = (u32::from(month), day, secs);
780 let now_year = u32::from(now.year);
781 let mut year = now_year - now_year % 100 + yy;
782 if (year, key) > (now_year + 50, now_key) {
784 year = year.saturating_sub(100);
785 } else if (year, key) <= (now_year.saturating_sub(50), now_key) {
786 year += 100;
787 }
788 year
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 const NOW_2026: i64 = 1_792_540_800; fn at(v: &[u8]) -> Result<HttpDate> {
798 HttpDate::parse_at(v, NOW_2026)
799 }
800
801 #[test]
802 fn test_calendar_round_trips_every_day_of_four_centuries() {
803 let start = days_from_civil(1600, 1, 1);
804 for days in start..start + 146_097 {
805 let (y, m, d) = civil_from_days(days);
806 assert_eq!(days_from_civil(y, m, d), days);
807 assert!(d >= 1 && d <= days_in_month(y, m));
808 }
809 assert_eq!(civil_from_days(i64::MAX).0, civil_from_days(DAYS_LIMIT).0);
810 assert_eq!(civil_from_days(i64::MIN).0, civil_from_days(-DAYS_LIMIT).0);
811 }
812
813 #[test]
814 fn test_weekday_known_dates() {
815 assert_eq!(weekday_from_days(days_from_civil(1994, 11, 6)), 7);
816 assert_eq!(weekday_from_days(days_from_civil(2000, 1, 1)), 6);
817 assert_eq!(weekday_from_days(days_from_civil(1, 1, 1)), 1);
818 assert_eq!(weekday_from_days(-1), 3);
819 }
820
821 #[test]
822 fn test_http_date_rfc_examples_all_formats() {
823 let d = HttpDate::from_unix(784_111_777).unwrap();
824 assert_eq!(at(b"Sun, 06 Nov 1994 08:49:37 GMT").unwrap(), d);
825 assert_eq!(at(b"Sunday, 06-Nov-94 08:49:37 GMT").unwrap(), d);
826 assert_eq!(at(b"Sun Nov 6 08:49:37 1994").unwrap(), d);
827 assert_eq!(at(b" Sun, 06 Nov 1994 08:49:37 GMT \t").unwrap(), d);
828 }
829
830 #[test]
831 fn test_http_date_rejections_table() {
832 use ErrorKind::*;
833 let cases: &[(&[u8], ErrorKind)] = &[
834 (b"Mon, 06 Nov 1994 08:49:37 GMT", WeekdayMismatch),
835 (b"sun, 06 Nov 1994 08:49:37 GMT", InvalidDate),
836 (b"Sun, 06 nov 1994 08:49:37 GMT", InvalidDate),
837 (b"Sun, 06 Nov 1994 08:49:37 UTC", InvalidDate),
838 (b"Sun, 06 Nov 1994 08:49:37 +0000", InvalidDate),
839 (b"Fri, 29 Feb 2023 00:00:00 GMT", InvalidDate),
840 (b"Thu, 31 Apr 2025 00:00:00 GMT", InvalidDate),
841 (b"Sun, 06 Nov 1994 24:00:00 GMT", InvalidDate),
842 (b"Sun, 06 Nov 1994 23:60:00 GMT", InvalidDate),
843 (b"Sun, 06 Nov 1994 23:59:60 GMT", InvalidDate),
844 (b"Sat, 01 Jan 0000 00:00:00 GMT", OutOfRange),
845 (b"Sun, 06 Nov 1994 08:49:37 GMTx", TrailingData),
846 (b"Sun, 06 Nov 1994 08:49:37 GM", InvalidDate),
847 (b"Sun, 6 Nov 1994 08:49:37 GMT", InvalidDate),
848 (b"Sun Nov 6 08:49:37 1994", InvalidDate),
849 (b"Sunday, 06-Nov-1994 08:49:37 GMT", InvalidDate),
850 (b"Sun, 00 Nov 1994 08:49:37 GMT", InvalidDate),
851 (b"", InvalidDate),
852 (b"garbage", InvalidDate),
853 (b"Sun, 06 Nov 1994 08:49:37 GMT, x", TrailingData),
854 ];
855 for (v, kind) in cases {
856 assert_eq!(
857 at(v).unwrap_err().kind(),
858 *kind,
859 "{}",
860 std::string::String::from_utf8_lossy(v)
861 );
862 }
863 }
864
865 #[test]
866 fn test_http_date_leap_year_and_leap_second() {
867 assert!(at(b"Thu, 29 Feb 2024 00:00:00 GMT").is_ok());
868 let v = b"Sun, 06 Nov 1994 08:49:60 GMT";
869 assert!(at(v).is_err());
870 let l = Limits::DEFAULT.allow_leap_second(true);
871 let d = HttpDate::parse_at_with(v, NOW_2026, &l).unwrap();
872 assert_eq!(d.civil().second, 59);
873 }
874
875 #[test]
876 fn test_http_date_range_ends() {
877 assert_eq!(&HttpDate::MAX.format(), b"Fri, 31 Dec 9999 23:59:59 GMT");
878 assert_eq!(&HttpDate::MIN.format(), b"Mon, 01 Jan 0001 00:00:00 GMT");
879 assert_eq!(at(b"Fri, 31 Dec 9999 23:59:59 GMT").unwrap(), HttpDate::MAX);
880 assert_eq!(at(b"Mon, 01 Jan 0001 00:00:00 GMT").unwrap(), HttpDate::MIN);
881 assert!(HttpDate::from_unix(MIN_SECS - 1).is_err());
882 assert!(HttpDate::from_unix(MAX_SECS + 1).is_err());
883 assert_eq!(MIN_SECS, -62_135_596_800);
884 assert_eq!(MAX_SECS, 253_402_300_799);
885 }
886
887 #[test]
888 fn test_rfc850_fifty_year_rule() {
889 let y = |v: &[u8]| at(v).unwrap().civil().year;
890 assert_eq!(y(b"Wednesday, 01-Jan-76 00:00:00 GMT"), 2076);
891 assert_eq!(y(b"Saturday, 01-Jan-77 00:00:00 GMT"), 1977);
892 assert_eq!(y(b"Wednesday, 21-Oct-76 00:00:00 GMT"), 2076);
893 assert_eq!(y(b"Wednesday, 21-Oct-26 00:00:00 GMT"), 2026);
894 assert_eq!(y(b"Tuesday, 01-Jan-80 00:00:00 GMT"), 1980);
895 let now_2099 = HttpDate::parse_imf_fixdate(b"Thu, 01 Jan 2099 00:00:00 GMT")
897 .unwrap()
898 .unix();
899 let d = HttpDate::parse_at(b"Friday, 01-Jan-00 00:00:00 GMT", now_2099).unwrap();
900 assert_eq!(d.civil().year, 2100);
901 let d = HttpDate::parse_at(b"Saturday, 01-Jan-50 00:00:00 GMT", now_2099).unwrap();
902 assert_eq!(d.civil().year, 2050);
903 }
904
905 #[test]
906 fn test_asctime_day_forms() {
907 assert!(at(b"Wed Nov 16 08:49:37 1994").is_ok());
908 assert_eq!(
909 at(b"Sun Nov 6 08:49:37 1994 ").unwrap().unix(),
910 784_111_777
911 );
912 assert_eq!(
913 at(b"Sun Nov 6 08:49:37 1994x").unwrap_err().kind(),
914 ErrorKind::TrailingData
915 );
916 }
917
918 #[test]
919 fn test_format_parse_round_trip_sampled() {
920 let mut secs = MIN_SECS;
921 while secs < MAX_SECS {
922 let d = HttpDate::from_unix(secs).unwrap();
923 assert_eq!(HttpDate::parse_imf_fixdate(&d.format()).unwrap(), d);
924 secs += 7_919_993; }
926 }
927
928 #[cfg(feature = "std")]
929 #[test]
930 fn test_system_time_conversions() {
931 use std::time::{Duration, UNIX_EPOCH};
932 let t = UNIX_EPOCH + Duration::from_millis(1500);
933 assert_eq!(HttpDate::from_system_time(t).unwrap().unix(), 1);
934 let before = UNIX_EPOCH - Duration::from_millis(1500);
935 assert_eq!(HttpDate::from_system_time(before).unwrap().unix(), -2);
936 let d = HttpDate::from_unix(784_111_777).unwrap();
937 assert_eq!(
938 HttpDate::from_system_time(d.to_system_time().unwrap()).unwrap(),
939 d
940 );
941 assert!(HttpDate::now() > d);
942 assert_eq!(http_date(b"Sun, 06 Nov 1994 08:49:37 GMT").unwrap(), d);
943 }
944}