1use crate::{
20 error::{ErrorKind, Result, WriteError, err},
21 limits::Limits,
22 text::trim_ows,
23 write::Sink,
24};
25
26#[must_use]
36pub const fn is_leap_year(year: i32) -> bool {
37 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
38}
39
40#[must_use]
52pub const fn days_in_month(year: i32, month: u8) -> u8 {
53 match month {
54 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
55 4 | 6 | 9 | 11 => 30,
56 2 if is_leap_year(year) => 29,
57 2 => 28,
58 _ => 0,
59 }
60}
61
62#[must_use]
78pub const fn days_from_civil(year: i32, month: u8, day: u8) -> i64 {
79 let m = month as i64;
80 let y = if m <= 2 { year as i64 - 1 } else { year as i64 };
81 let era = if y >= 0 { y } else { y - 399 } / 400;
82 let yoe = y - era * 400;
83 let mp = (m + 9) % 12;
84 let doy = (153 * mp + 2) / 5 + day as i64 - 1;
85 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
86 era * 146_097 + doe - 719_468
87}
88
89const DAYS_LIMIT: i64 = 784_352_296_671;
92
93#[must_use]
109pub const fn civil_from_days(days: i64) -> (i32, u8, u8) {
110 let days = if days > DAYS_LIMIT {
111 DAYS_LIMIT
112 } else if days < -DAYS_LIMIT {
113 -DAYS_LIMIT
114 } else {
115 days
116 };
117 let z = days + 719_468;
118 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
119 let doe = z - era * 146_097;
120 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
121 let y = yoe + era * 400;
122 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
123 let mp = (5 * doy + 2) / 153;
124 let d = doy - (153 * mp + 2) / 5 + 1;
125 let m = if mp < 10 { mp + 3 } else { mp - 9 };
126 let y = if m <= 2 { y + 1 } else { y };
127 let y = if y > i32::MAX as i64 {
128 i32::MAX
129 } else if y < i32::MIN as i64 {
130 i32::MIN
131 } else {
132 y as i32
133 };
134 (y, m as u8, d as u8)
136}
137
138#[must_use]
147pub const fn weekday_from_days(days: i64) -> u8 {
148 ((days.rem_euclid(7) + 3) % 7 + 1) as u8
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
162pub struct Civil {
163 pub year: u16,
165 pub month: u8,
167 pub day: u8,
169 pub hour: u8,
171 pub minute: u8,
173 pub second: u8,
175 pub weekday: u8,
177}
178
179#[non_exhaustive]
181#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
182pub enum DateFormat {
183 ImfFixdate,
185 Rfc850,
187 Asctime,
189}
190
191const SECS_PER_DAY: i64 = 86_400;
192const MIN_SECS: i64 = days_from_civil(1, 1, 1) * SECS_PER_DAY;
193const MAX_SECS: i64 = days_from_civil(9999, 12, 31) * SECS_PER_DAY + SECS_PER_DAY - 1;
194
195const DAY_NAMES: [&[u8; 3]; 7] = [b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat", b"Sun"];
196const LONG_DAY_NAMES: [&[u8]; 7] = [
197 b"Monday",
198 b"Tuesday",
199 b"Wednesday",
200 b"Thursday",
201 b"Friday",
202 b"Saturday",
203 b"Sunday",
204];
205const MONTH_NAMES: [&[u8; 3]; 12] = [
206 b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec",
207];
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
223pub struct HttpDate {
224 secs: i64,
225}
226
227impl HttpDate {
228 pub const MIN: Self = Self { secs: MIN_SECS };
230 pub const MAX: Self = Self { secs: MAX_SECS };
232
233 pub const fn from_unix(secs: i64) -> Result<Self> {
239 if secs < MIN_SECS || secs > MAX_SECS {
240 return err(ErrorKind::OutOfRange, 0);
241 }
242 Ok(Self { secs })
243 }
244
245 #[must_use]
247 pub const fn unix(self) -> i64 {
248 self.secs
249 }
250
251 #[must_use]
253 pub fn civil(self) -> Civil {
254 let days = self.secs.div_euclid(SECS_PER_DAY);
255 let rem = self.secs.rem_euclid(SECS_PER_DAY);
256 let (year, month, day) = civil_from_days(days);
257 Civil {
258 year: u16::try_from(year).unwrap_or(0),
260 month,
261 day,
262 hour: (rem / 3600) as u8,
263 minute: (rem % 3600 / 60) as u8,
264 second: (rem % 60) as u8,
265 weekday: weekday_from_days(days),
266 }
267 }
268
269 #[must_use]
271 pub fn format(self) -> [u8; 29] {
272 let c = self.civil();
273 let mut out = *b"Thu, 01 Jan 1970 00:00:00 GMT";
274 let name = DAY_NAMES
275 .get(usize::from(c.weekday).saturating_sub(1))
276 .copied()
277 .unwrap_or(b"Mon");
278 let month = MONTH_NAMES
279 .get(usize::from(c.month).saturating_sub(1))
280 .copied()
281 .unwrap_or(b"Jan");
282 put(&mut out, 0, name);
283 two(&mut out, 5, c.day);
284 put(&mut out, 8, month);
285 two(&mut out, 12, (c.year / 100) as u8);
286 two(&mut out, 14, (c.year % 100) as u8);
287 two(&mut out, 17, c.hour);
288 two(&mut out, 20, c.minute);
289 two(&mut out, 23, c.second);
290 out
291 }
292
293 pub fn write_to(self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
299 out.put(&self.format())?;
300 Ok(())
301 }
302
303 pub fn parse_imf_fixdate(value: &[u8]) -> Result<Self> {
311 let (lead, v) = trimmed(value);
312 imf_fixdate(v, &Limits::DEFAULT).map_err(|e| e.shifted(lead))
313 }
314
315 pub fn parse_at(value: &[u8], now: i64) -> Result<Self> {
329 Self::parse_detailed_with(value, now, &Limits::DEFAULT).map(|(d, _)| d)
330 }
331
332 pub fn parse_at_with(value: &[u8], now: i64, limits: &Limits) -> Result<Self> {
339 Self::parse_detailed_with(value, now, limits).map(|(d, _)| d)
340 }
341
342 pub fn parse_detailed(value: &[u8], now: i64) -> Result<(Self, DateFormat)> {
358 Self::parse_detailed_with(value, now, &Limits::DEFAULT)
359 }
360
361 fn parse_detailed_with(value: &[u8], now: i64, limits: &Limits) -> Result<(Self, DateFormat)> {
362 let (lead, v) = trimmed(value);
363 let result = match (v.get(3), v.iter().position(|&b| b == b',')) {
364 (Some(b','), _) => imf_fixdate(v, limits).map(|d| (d, DateFormat::ImfFixdate)),
365 (Some(b' '), _) => asctime(v, limits).map(|d| (d, DateFormat::Asctime)),
366 (_, Some(_)) => rfc850(v, now, limits).map(|d| (d, DateFormat::Rfc850)),
367 _ => err(ErrorKind::InvalidDate, 0),
368 };
369 result.map_err(|e| e.shifted(lead))
370 }
371
372 #[cfg(feature = "std")]
375 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
376 #[must_use]
377 pub fn now() -> Self {
378 match Self::from_system_time(std::time::SystemTime::now()) {
379 Ok(d) => d,
380 Err(_) => Self::MAX,
381 }
382 }
383
384 #[cfg(feature = "std")]
391 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
392 pub fn from_system_time(t: std::time::SystemTime) -> Result<Self> {
393 let secs = match t.duration_since(std::time::UNIX_EPOCH) {
394 Ok(d) => i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
395 Err(before) => {
396 let d = before.duration();
397 let whole = i64::try_from(d.as_secs()).unwrap_or(i64::MAX);
398 let extra = i64::from(d.subsec_nanos() > 0);
399 0i64.saturating_sub(whole).saturating_sub(extra)
400 }
401 };
402 Self::from_unix(secs)
403 }
404
405 #[cfg(feature = "std")]
408 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
409 #[must_use]
410 pub fn to_system_time(self) -> Option<std::time::SystemTime> {
411 let d = std::time::Duration::from_secs(self.secs.unsigned_abs());
412 if self.secs >= 0 {
413 std::time::UNIX_EPOCH.checked_add(d)
414 } else {
415 std::time::UNIX_EPOCH.checked_sub(d)
416 }
417 }
418}
419
420pub fn http_date_at(value: &[u8], now: i64) -> Result<HttpDate> {
436 HttpDate::parse_at(value, now)
437}
438
439#[cfg(feature = "std")]
454#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
455pub fn http_date(value: &[u8]) -> Result<HttpDate> {
456 HttpDate::parse_at(value, HttpDate::now().unix())
457}
458
459fn trimmed(value: &[u8]) -> (usize, &[u8]) {
460 let lead = crate::text::skip_ows(value);
461 (lead, trim_ows(value))
462}
463
464fn put(out: &mut [u8], at: usize, bytes: &[u8]) {
465 if let Some(dst) = out.get_mut(at..at + bytes.len()) {
466 dst.copy_from_slice(bytes);
467 }
468}
469
470fn two(out: &mut [u8], at: usize, v: u8) {
471 put(out, at, &[b'0' + v / 10 % 10, b'0' + v % 10]);
472}
473
474fn digits(v: &[u8], at: usize, n: usize) -> Result<u32> {
476 let Some(ds) = v.get(at..at + n) else {
477 return err(ErrorKind::InvalidDate, v.len().min(at));
478 };
479 let mut x = 0u32;
480 for (i, &d) in ds.iter().enumerate() {
481 if !d.is_ascii_digit() {
482 return err(ErrorKind::InvalidDate, at + i);
483 }
484 x = x * 10 + u32::from(d - b'0');
485 }
486 Ok(x)
487}
488
489fn expect(v: &[u8], at: usize, lit: &[u8]) -> Result<()> {
490 match v.get(at..at + lit.len()) {
491 Some(s) if s == lit => Ok(()),
492 _ => err(ErrorKind::InvalidDate, at),
493 }
494}
495
496fn month(v: &[u8], at: usize) -> Result<u8> {
498 let name = v.get(at..at + 3).unwrap_or(&[]);
499 match MONTH_NAMES.iter().position(|m| m.as_slice() == name) {
500 Some(i) => Ok(i as u8 + 1),
502 None => err(ErrorKind::InvalidDate, at),
503 }
504}
505
506fn short_day(v: &[u8]) -> Result<u8> {
508 let name = v.get(..3).unwrap_or(&[]);
509 match DAY_NAMES.iter().position(|d| d.as_slice() == name) {
510 Some(i) => Ok(i as u8 + 1),
511 None => err(ErrorKind::InvalidDate, 0),
512 }
513}
514
515fn time(v: &[u8], at: usize, limits: &Limits) -> Result<i64> {
517 let h = digits(v, at, 2)?;
518 expect(v, at + 2, b":")?;
519 let m = digits(v, at + 3, 2)?;
520 expect(v, at + 5, b":")?;
521 let mut s = digits(v, at + 6, 2)?;
522 if h > 23 {
523 return err(ErrorKind::InvalidDate, at);
524 }
525 if m > 59 {
526 return err(ErrorKind::InvalidDate, at + 3);
527 }
528 if s == 60 && limits.allow_leap_second {
529 s = 59;
530 }
531 if s > 59 {
532 return err(ErrorKind::InvalidDate, at + 6);
533 }
534 Ok(i64::from(h * 3600 + m * 60 + s))
535}
536
537fn assemble(
539 year: u32,
540 month: u8,
541 day: u32,
542 secs: i64,
543 weekday: u8,
544 year_at: usize,
545 day_at: usize,
546) -> Result<HttpDate> {
547 if year == 0 || year > 9999 {
548 return err(ErrorKind::OutOfRange, year_at);
549 }
550 let y = year as i32;
552 if day == 0 || day > u32::from(days_in_month(y, month)) {
553 return err(ErrorKind::InvalidDate, day_at);
554 }
555 let days = days_from_civil(y, month, day as u8);
556 if weekday_from_days(days) != weekday {
557 return err(ErrorKind::WeekdayMismatch, 0);
558 }
559 HttpDate::from_unix(days * SECS_PER_DAY + secs)
560}
561
562fn trailing(v: &[u8], len: usize) -> Result<()> {
563 if v.len() > len {
564 return err(ErrorKind::TrailingData, len);
565 }
566 Ok(())
567}
568
569fn imf_fixdate(v: &[u8], limits: &Limits) -> Result<HttpDate> {
571 let weekday = short_day(v)?;
572 expect(v, 3, b", ")?;
573 let day = digits(v, 5, 2)?;
574 expect(v, 7, b" ")?;
575 let m = month(v, 8)?;
576 expect(v, 11, b" ")?;
577 let year = digits(v, 12, 4)?;
578 expect(v, 16, b" ")?;
579 let secs = time(v, 17, limits)?;
580 expect(v, 25, b" GMT")?;
581 trailing(v, 29)?;
582 assemble(year, m, day, secs, weekday, 12, 5)
583}
584
585fn asctime(v: &[u8], limits: &Limits) -> Result<HttpDate> {
587 let weekday = short_day(v)?;
588 expect(v, 3, b" ")?;
589 let m = month(v, 4)?;
590 expect(v, 7, b" ")?;
591 let day = if v.get(8) == Some(&b' ') {
592 digits(v, 9, 1)?
593 } else {
594 digits(v, 8, 2)?
595 };
596 expect(v, 10, b" ")?;
597 let secs = time(v, 11, limits)?;
598 expect(v, 19, b" ")?;
599 let year = digits(v, 20, 4)?;
600 trailing(v, 24)?;
601 assemble(year, m, day, secs, weekday, 20, 8)
602}
603
604fn rfc850(v: &[u8], now: i64, limits: &Limits) -> Result<HttpDate> {
606 let comma = v.iter().position(|&b| b == b',').unwrap_or(0);
607 let name = v.get(..comma).unwrap_or(&[]);
608 let Some(day_index) = LONG_DAY_NAMES.iter().position(|d| *d == name) else {
609 return err(ErrorKind::InvalidDate, 0);
610 };
611 let weekday = day_index as u8 + 1;
612 let at = comma + 2;
613 expect(v, comma, b", ")?;
614 let day = digits(v, at, 2)?;
615 expect(v, at + 2, b"-")?;
616 let m = month(v, at + 3)?;
617 expect(v, at + 6, b"-")?;
618 let yy = digits(v, at + 7, 2)?;
619 expect(v, at + 9, b" ")?;
620 let secs = time(v, at + 10, limits)?;
621 expect(v, at + 18, b" GMT")?;
622 trailing(v, at + 22)?;
623 let year = resolve_two_digit_year(yy, m, day, secs, now);
624 assemble(year, m, day, secs, weekday, at + 7, at)
625}
626
627fn resolve_two_digit_year(yy: u32, month: u8, day: u32, secs: i64, now: i64) -> u32 {
632 let now = HttpDate::from_unix(now.clamp(MIN_SECS, MAX_SECS))
633 .unwrap_or(HttpDate::MIN)
634 .civil();
635 let now_key = (
636 u32::from(now.month),
637 u32::from(now.day),
638 i64::from(now.hour) * 3600 + i64::from(now.minute) * 60 + i64::from(now.second),
639 );
640 let key = (u32::from(month), day, secs);
641 let now_year = u32::from(now.year);
642 let mut year = now_year - now_year % 100 + yy;
643 if (year, key) > (now_year + 50, now_key) {
645 year = year.saturating_sub(100);
646 } else if (year, key) <= (now_year.saturating_sub(50), now_key) {
647 year += 100;
648 }
649 year
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655
656 const NOW_2026: i64 = 1_792_540_800; fn at(v: &[u8]) -> Result<HttpDate> {
659 HttpDate::parse_at(v, NOW_2026)
660 }
661
662 #[test]
663 fn test_calendar_round_trips_every_day_of_four_centuries() {
664 let start = days_from_civil(1600, 1, 1);
665 for days in start..start + 146_097 {
666 let (y, m, d) = civil_from_days(days);
667 assert_eq!(days_from_civil(y, m, d), days);
668 assert!(d >= 1 && d <= days_in_month(y, m));
669 }
670 assert_eq!(civil_from_days(i64::MAX).0, civil_from_days(DAYS_LIMIT).0);
671 assert_eq!(civil_from_days(i64::MIN).0, civil_from_days(-DAYS_LIMIT).0);
672 }
673
674 #[test]
675 fn test_weekday_known_dates() {
676 assert_eq!(weekday_from_days(days_from_civil(1994, 11, 6)), 7);
677 assert_eq!(weekday_from_days(days_from_civil(2000, 1, 1)), 6);
678 assert_eq!(weekday_from_days(days_from_civil(1, 1, 1)), 1);
679 assert_eq!(weekday_from_days(-1), 3);
680 }
681
682 #[test]
683 fn test_http_date_rfc_examples_all_formats() {
684 let d = HttpDate::from_unix(784_111_777).unwrap();
685 assert_eq!(at(b"Sun, 06 Nov 1994 08:49:37 GMT").unwrap(), d);
686 assert_eq!(at(b"Sunday, 06-Nov-94 08:49:37 GMT").unwrap(), d);
687 assert_eq!(at(b"Sun Nov 6 08:49:37 1994").unwrap(), d);
688 assert_eq!(at(b" Sun, 06 Nov 1994 08:49:37 GMT \t").unwrap(), d);
689 }
690
691 #[test]
692 fn test_http_date_rejections_table() {
693 use ErrorKind::*;
694 let cases: &[(&[u8], ErrorKind)] = &[
695 (b"Mon, 06 Nov 1994 08:49:37 GMT", WeekdayMismatch),
696 (b"sun, 06 Nov 1994 08:49:37 GMT", InvalidDate),
697 (b"Sun, 06 nov 1994 08:49:37 GMT", InvalidDate),
698 (b"Sun, 06 Nov 1994 08:49:37 UTC", InvalidDate),
699 (b"Sun, 06 Nov 1994 08:49:37 +0000", InvalidDate),
700 (b"Fri, 29 Feb 2023 00:00:00 GMT", InvalidDate),
701 (b"Thu, 31 Apr 2025 00:00:00 GMT", InvalidDate),
702 (b"Sun, 06 Nov 1994 24:00:00 GMT", InvalidDate),
703 (b"Sun, 06 Nov 1994 23:60:00 GMT", InvalidDate),
704 (b"Sun, 06 Nov 1994 23:59:60 GMT", InvalidDate),
705 (b"Sat, 01 Jan 0000 00:00:00 GMT", OutOfRange),
706 (b"Sun, 06 Nov 1994 08:49:37 GMTx", TrailingData),
707 (b"Sun, 06 Nov 1994 08:49:37 GM", InvalidDate),
708 (b"Sun, 6 Nov 1994 08:49:37 GMT", InvalidDate),
709 (b"Sun Nov 6 08:49:37 1994", InvalidDate),
710 (b"Sunday, 06-Nov-1994 08:49:37 GMT", InvalidDate),
711 (b"Sun, 00 Nov 1994 08:49:37 GMT", InvalidDate),
712 (b"", InvalidDate),
713 (b"garbage", InvalidDate),
714 (b"Sun, 06 Nov 1994 08:49:37 GMT, x", TrailingData),
715 ];
716 for (v, kind) in cases {
717 assert_eq!(
718 at(v).unwrap_err().kind(),
719 *kind,
720 "{}",
721 std::string::String::from_utf8_lossy(v)
722 );
723 }
724 }
725
726 #[test]
727 fn test_http_date_leap_year_and_leap_second() {
728 assert!(at(b"Thu, 29 Feb 2024 00:00:00 GMT").is_ok());
729 let v = b"Sun, 06 Nov 1994 08:49:60 GMT";
730 assert!(at(v).is_err());
731 let l = Limits::DEFAULT.allow_leap_second(true);
732 let d = HttpDate::parse_at_with(v, NOW_2026, &l).unwrap();
733 assert_eq!(d.civil().second, 59);
734 }
735
736 #[test]
737 fn test_http_date_range_ends() {
738 assert_eq!(&HttpDate::MAX.format(), b"Fri, 31 Dec 9999 23:59:59 GMT");
739 assert_eq!(&HttpDate::MIN.format(), b"Mon, 01 Jan 0001 00:00:00 GMT");
740 assert_eq!(at(b"Fri, 31 Dec 9999 23:59:59 GMT").unwrap(), HttpDate::MAX);
741 assert_eq!(at(b"Mon, 01 Jan 0001 00:00:00 GMT").unwrap(), HttpDate::MIN);
742 assert!(HttpDate::from_unix(MIN_SECS - 1).is_err());
743 assert!(HttpDate::from_unix(MAX_SECS + 1).is_err());
744 assert_eq!(MIN_SECS, -62_135_596_800);
745 assert_eq!(MAX_SECS, 253_402_300_799);
746 }
747
748 #[test]
749 fn test_rfc850_fifty_year_rule() {
750 let y = |v: &[u8]| at(v).unwrap().civil().year;
751 assert_eq!(y(b"Wednesday, 01-Jan-76 00:00:00 GMT"), 2076);
752 assert_eq!(y(b"Saturday, 01-Jan-77 00:00:00 GMT"), 1977);
753 assert_eq!(y(b"Wednesday, 21-Oct-76 00:00:00 GMT"), 2076);
754 assert_eq!(y(b"Wednesday, 21-Oct-26 00:00:00 GMT"), 2026);
755 assert_eq!(y(b"Tuesday, 01-Jan-80 00:00:00 GMT"), 1980);
756 let now_2099 = HttpDate::parse_imf_fixdate(b"Thu, 01 Jan 2099 00:00:00 GMT")
758 .unwrap()
759 .unix();
760 let d = HttpDate::parse_at(b"Friday, 01-Jan-00 00:00:00 GMT", now_2099).unwrap();
761 assert_eq!(d.civil().year, 2100);
762 let d = HttpDate::parse_at(b"Saturday, 01-Jan-50 00:00:00 GMT", now_2099).unwrap();
763 assert_eq!(d.civil().year, 2050);
764 }
765
766 #[test]
767 fn test_asctime_day_forms() {
768 assert!(at(b"Wed Nov 16 08:49:37 1994").is_ok());
769 assert_eq!(
770 at(b"Sun Nov 6 08:49:37 1994 ").unwrap().unix(),
771 784_111_777
772 );
773 assert_eq!(
774 at(b"Sun Nov 6 08:49:37 1994x").unwrap_err().kind(),
775 ErrorKind::TrailingData
776 );
777 }
778
779 #[test]
780 fn test_format_parse_round_trip_sampled() {
781 let mut secs = MIN_SECS;
782 while secs < MAX_SECS {
783 let d = HttpDate::from_unix(secs).unwrap();
784 assert_eq!(HttpDate::parse_imf_fixdate(&d.format()).unwrap(), d);
785 secs += 7_919_993; }
787 }
788
789 #[cfg(feature = "std")]
790 #[test]
791 fn test_system_time_conversions() {
792 use std::time::{Duration, UNIX_EPOCH};
793 let t = UNIX_EPOCH + Duration::from_millis(1500);
794 assert_eq!(HttpDate::from_system_time(t).unwrap().unix(), 1);
795 let before = UNIX_EPOCH - Duration::from_millis(1500);
796 assert_eq!(HttpDate::from_system_time(before).unwrap().unix(), -2);
797 let d = HttpDate::from_unix(784_111_777).unwrap();
798 assert_eq!(
799 HttpDate::from_system_time(d.to_system_time().unwrap()).unwrap(),
800 d
801 );
802 assert!(HttpDate::now() > d);
803 assert_eq!(http_date(b"Sun, 06 Nov 1994 08:49:37 GMT").unwrap(), d);
804 }
805}