1use std::fmt;
47#[cfg(feature = "chrono")]
48pub mod chrono;
49
50#[derive(Clone, Copy)]
51enum DayNameTok {
52 Short(DayName),
53 Long(DayName),
54}
55
56#[derive(Debug, PartialEq, Eq, Clone, Copy)]
58pub enum DayName {
59 Mon,
61 Tue,
63 Wed,
65 Thu,
67 Fri,
69 Sat,
71 Sun,
73}
74
75const DAYS: [DayName; 7] = [
76 DayName::Mon,
77 DayName::Tue,
78 DayName::Wed,
79 DayName::Thu,
80 DayName::Fri,
81 DayName::Sat,
82 DayName::Sun,
83];
84
85const MONTHS: [&str; 12] = [
87 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
88];
89
90#[derive(Clone, Copy)]
91enum PunctuationTok {
92 Comma,
93 Space,
94}
95
96#[derive(Debug, PartialEq, Eq, Clone, Copy)]
98pub struct Date {
99 year: u16,
101 month: u8,
103 day: u8,
105}
106
107impl Date {
108 pub fn new(year: u16, month: u8, day: u8) -> Result<Self, Error> {
115 if year > 9999 || month == 0 || month > 12 || day == 0 || day > 31 {
116 return Err(Error::new("date out of range"));
117 }
118 Ok(Self { year, month, day })
119 }
120
121 #[must_use]
122 pub fn year(&self) -> u16 {
123 self.year
124 }
125
126 #[must_use]
127 pub fn month(&self) -> u8 {
128 self.month
129 }
130
131 #[must_use]
132 pub fn day(&self) -> u8 {
133 self.day
134 }
135}
136
137#[derive(Debug, PartialEq, Eq, Clone, Copy)]
142pub struct Time {
143 hour: u8,
145 minute: u8,
147 second: u8,
149}
150
151impl Time {
152 pub fn new(hour: u8, minute: u8, second: u8) -> Result<Self, Error> {
159 if hour > 23 || minute > 59 || second > 59 {
160 return Err(Error::new("time out of range"));
161 }
162 Ok(Self {
163 hour,
164 minute,
165 second,
166 })
167 }
168
169 #[must_use]
170 pub fn hour(&self) -> u8 {
171 self.hour
172 }
173
174 #[must_use]
175 pub fn minute(&self) -> u8 {
176 self.minute
177 }
178
179 #[must_use]
180 pub fn second(&self) -> u8 {
181 self.second
182 }
183}
184
185#[derive(Debug, PartialEq, Eq, Clone, Copy)]
189pub struct DateTime {
190 pub dayname: DayName,
192 pub date: Date,
194 pub time: Time,
196}
197
198#[derive(Debug, PartialEq, Eq, Clone, Copy)]
205pub struct HttpDate {
206 format: Format,
207 dt: DateTime,
208}
209
210#[derive(Debug, PartialEq, Eq, Clone, Copy)]
215enum Format {
216 ImfFixdate,
217 Rfc850,
218 Asctime,
219}
220
221impl HttpDate {
222 #[must_use]
224 pub fn imf_fixdate(dt: DateTime) -> Self {
225 Self {
226 format: Format::ImfFixdate,
227 dt,
228 }
229 }
230
231 pub fn rfc850(dt: DateTime) -> Result<Self, Error> {
240 if dt.date.year > 99 {
241 return Err(Error::new("RFC 850 year out of range (0-99)"));
242 }
243 Ok(Self {
244 format: Format::Rfc850,
245 dt,
246 })
247 }
248
249 #[must_use]
251 pub fn asctime(dt: DateTime) -> Self {
252 Self {
253 format: Format::Asctime,
254 dt,
255 }
256 }
257
258 #[must_use]
260 pub fn datetime(&self) -> &DateTime {
261 &self.dt
262 }
263
264 #[must_use]
265 pub fn is_imf_fixdate(&self) -> bool {
266 self.format == Format::ImfFixdate
267 }
268
269 #[must_use]
270 pub fn is_rfc850(&self) -> bool {
271 self.format == Format::Rfc850
272 }
273
274 #[must_use]
275 pub fn is_asctime(&self) -> bool {
276 self.format == Format::Asctime
277 }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub struct Error {
284 msg: &'static str,
285 pos: Option<usize>,
286}
287
288impl Error {
289 #[must_use]
291 const fn new(msg: &'static str) -> Self {
292 Self { msg, pos: None }
293 }
294
295 const fn at(self, pos: usize) -> Self {
297 Self {
298 pos: Some(pos),
299 ..self
300 }
301 }
302
303 #[must_use]
304 pub const fn position(&self) -> Option<usize> {
305 self.pos
306 }
307}
308
309impl fmt::Display for Error {
310 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311 f.write_str(self.msg)?;
312 if let Some(pos) = self.pos {
313 write!(f, " at position {pos}")?;
314 }
315 Ok(())
316 }
317}
318
319impl std::error::Error for Error {}
320
321struct Decoder<'a> {
323 buf: &'a str,
324 pos: usize,
325}
326
327impl<'a> Decoder<'a> {
328 fn new(buf: &'a str) -> Self {
329 Decoder { buf, pos: 0 }
330 }
331
332 #[inline]
333 fn advance(&mut self, n: usize) {
334 self.pos += n;
335 }
336
337 #[inline]
338 fn byte_at(&self, idx: usize) -> Result<u8, Error> {
339 self.buf
340 .as_bytes()
341 .get(idx)
342 .copied()
343 .ok_or_else(|| Error::new("unexpected end of input").at(idx))
344 }
345
346 fn expect(&mut self, expected: u8) -> Result<(), Error> {
347 let actual = self.byte_at(self.pos)?;
348 if actual == expected {
349 self.advance(1);
350 Ok(())
351 } else {
352 Err(Error::new("unexpected character").at(self.pos))
353 }
354 }
355
356 #[inline]
357 fn space(&mut self) -> Result<(), Error> {
358 self.expect(b' ')
359 }
360
361 #[inline]
362 fn comma(&mut self) -> Result<(), Error> {
363 self.expect(b',')
364 }
365
366 #[inline]
367 fn colon(&mut self) -> Result<(), Error> {
368 self.expect(b':')
369 }
370
371 fn month(&mut self) -> Result<u8, Error> {
372 let m = self
373 .buf
374 .as_bytes()
375 .get(self.pos..self.pos + 3)
376 .ok_or_else(|| Error::new("unexpected end of input").at(self.pos))?;
377 let n = (1..)
378 .zip(MONTHS)
379 .find_map(|(n, name)| (name.as_bytes() == m).then_some(n))
380 .ok_or_else(|| Error::new("invalid month value").at(self.pos))?;
381 self.advance(3);
382 Ok(n)
383 }
384
385 fn digits(&mut self, n: usize) -> Result<u16, Error> {
386 let buf = self.buf.as_bytes();
387 let mut value: u16 = 0;
388 for i in 0..n {
389 let pos = self.pos + i;
390 match buf.get(pos) {
391 Some(&c @ b'0'..=b'9') => {
392 value = value * 10 + u16::from(c - b'0');
393 }
394 Some(_) => return Err(Error::new("expected digit").at(pos)),
395 None => return Err(Error::new("unexpected end of input").at(pos)),
396 }
397 }
398 self.advance(n);
399 Ok(value)
400 }
401
402 fn year(&mut self) -> Result<u16, Error> {
403 self.digits(4)
404 }
405
406 fn day(&mut self) -> Result<u8, Error> {
407 self.digits_u8(2)
408 }
409
410 fn digits_u8(&mut self, n: usize) -> Result<u8, Error> {
412 let value = self.digits(n)?;
413 Ok(u8::try_from(value).expect("at most two digits"))
414 }
415
416 fn string(&mut self) -> &'a str {
418 let start = self.pos;
419 while self.pos < self.buf.len() && self.buf.as_bytes()[self.pos].is_ascii_alphabetic() {
420 self.advance(1);
421 }
422 &self.buf[start..self.pos]
424 }
425
426 fn dayname_tok(&mut self) -> Result<DayNameTok, Error> {
427 let start = self.pos;
428 let s = self.string();
429 for d in DAYS {
430 if s == d.short() {
431 return Ok(DayNameTok::Short(d));
432 }
433 if s == d.long() {
434 return Ok(DayNameTok::Long(d));
435 }
436 }
437 Err(Error::new("invalid day name").at(start))
438 }
439
440 fn punctuation_tok(&mut self) -> Result<PunctuationTok, Error> {
441 let c = self.byte_at(self.pos)?;
442 let tok = match c {
443 b',' => PunctuationTok::Comma,
444 b' ' => PunctuationTok::Space,
445 _ => return Err(Error::new("expected ',' or ' ' after day name").at(self.pos)),
446 };
447 self.advance(1);
448 Ok(tok)
449 }
450
451 fn date1(&mut self) -> Result<Date, Error> {
452 let start = self.pos;
453 let day = self.day()?;
454 self.space()?;
455 let month = self.month()?;
456 self.space()?;
457 let year = self.year()?;
458 Date::new(year, month, day).map_err(|e| e.at(start))
459 }
460
461 fn time(&mut self) -> Result<Time, Error> {
462 let start = self.pos;
463 let hour = self.digits_u8(2)?;
464 self.colon()?;
465 let minute = self.digits_u8(2)?;
466 self.colon()?;
467 let second = self.digits_u8(2)?;
468 Time::new(hour, minute, second).map_err(|e| e.at(start))
469 }
470
471 fn gmt(&mut self) -> Result<(), Error> {
472 let start = self.pos;
473 if self.string() != "GMT" {
474 return Err(Error::new("expected 'GMT'").at(start));
475 }
476 Ok(())
477 }
478
479 fn imf_fixdate(&mut self, dayname: DayName) -> Result<HttpDate, Error> {
481 self.space()?;
482 let date = self.date1()?;
483 self.space()?;
484 let time = self.time()?;
485 self.space()?;
486 self.gmt()?;
487 Ok(HttpDate::imf_fixdate(DateTime {
488 dayname,
489 date,
490 time,
491 }))
492 }
493
494 fn date2(&mut self) -> Result<Date, Error> {
495 let start = self.pos;
496 let day = self.day()?;
497 self.expect(b'-')?;
498 let month = self.month()?;
499 self.expect(b'-')?;
500 let year = self.digits(2)?;
501 Date::new(year, month, day).map_err(|e| e.at(start))
502 }
503
504 fn rfc850_date(&mut self, dayname: DayName) -> Result<HttpDate, Error> {
506 self.comma()?;
507 self.space()?;
508 let date = self.date2()?;
509 self.space()?;
510 let time = self.time()?;
511 self.space()?;
512 self.gmt()?;
513 let date = DateTime {
514 dayname,
515 date,
516 time,
517 };
518 HttpDate::rfc850(date)
519 }
520
521 fn date3(&mut self) -> Result<(u8, u8), Error> {
522 let m = self.month()?;
524 self.space()?;
525 let d = match self.byte_at(self.pos)? {
527 b' ' => {
528 self.space()?;
529 self.digits_u8(1)?
530 }
531 _ => self.digits_u8(2)?,
532 };
533 Ok((m, d))
534 }
535
536 fn asctime_date(&mut self, dayname: DayName) -> Result<HttpDate, Error> {
538 let start = self.pos;
539 let (month, day) = self.date3()?;
540 self.space()?;
541 let time = self.time()?;
542 self.space()?;
543 let year = self.year()?;
544 let date = Date::new(year, month, day).map_err(|e| e.at(start))?;
545 Ok(HttpDate::asctime(DateTime {
546 dayname,
547 date,
548 time,
549 }))
550 }
551}
552
553pub fn decode(buf: &str) -> Result<HttpDate, Error> {
611 let mut decoder = Decoder::new(buf);
612 let date = match decoder.dayname_tok()? {
613 DayNameTok::Long(dayname) => decoder.rfc850_date(dayname),
614 DayNameTok::Short(dayname) => match decoder.punctuation_tok()? {
615 PunctuationTok::Comma => decoder.imf_fixdate(dayname),
616 PunctuationTok::Space => decoder.asctime_date(dayname),
617 },
618 }?;
619 if decoder.pos != decoder.buf.len() {
622 return Err(Error::new("trailing data after HTTP date").at(decoder.pos));
623 }
624 Ok(date)
625}
626
627impl DayName {
630 fn short(self) -> &'static str {
632 match self {
633 DayName::Mon => "Mon",
634 DayName::Tue => "Tue",
635 DayName::Wed => "Wed",
636 DayName::Thu => "Thu",
637 DayName::Fri => "Fri",
638 DayName::Sat => "Sat",
639 DayName::Sun => "Sun",
640 }
641 }
642
643 fn long(self) -> &'static str {
645 match self {
646 DayName::Mon => "Monday",
647 DayName::Tue => "Tuesday",
648 DayName::Wed => "Wednesday",
649 DayName::Thu => "Thursday",
650 DayName::Fri => "Friday",
651 DayName::Sat => "Saturday",
652 DayName::Sun => "Sunday",
653 }
654 }
655}
656
657fn month_name(month: u8) -> &'static str {
659 MONTHS[usize::from(month) - 1]
660}
661
662#[must_use]
678pub fn encode(date: &HttpDate) -> String {
679 date.to_string()
680}
681
682impl fmt::Display for HttpDate {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 let dt = &self.dt;
685 let month = month_name(dt.date.month());
686 let day = dt.date.day();
687 let year = dt.date.year();
688 let time = dt.time;
689
690 match self.format {
691 Format::ImfFixdate => write!(
692 f,
693 "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT",
694 dt.dayname.short(),
695 day,
696 month,
697 year,
698 time.hour(),
699 time.minute(),
700 time.second(),
701 ),
702 Format::Rfc850 => write!(
705 f,
706 "{}, {:02}-{}-{:02} {:02}:{:02}:{:02} GMT",
707 dt.dayname.long(),
708 day,
709 month,
710 year,
711 time.hour(),
712 time.minute(),
713 time.second(),
714 ),
715 Format::Asctime => write!(
716 f,
717 "{} {} {:2} {:02}:{:02}:{:02} {:04}",
718 dt.dayname.short(),
719 month,
720 day,
721 time.hour(),
722 time.minute(),
723 time.second(),
724 year,
725 ),
726 }
727 }
728}
729
730#[cfg(test)]
733mod tests {
734 use super::*;
735 use expect_test::expect;
736 use std::fmt::Write;
737
738 fn expect_imf_fixdate(decoded: &HttpDate) -> DateTime {
741 assert!(decoded.is_imf_fixdate(), "expected ImfFixdate");
742 *decoded.datetime()
743 }
744
745 fn expect_rfc850(decoded: &HttpDate) -> DateTime {
746 assert!(decoded.is_rfc850(), "expected Rfc850");
747 *decoded.datetime()
748 }
749
750 fn expect_asctime(decoded: &HttpDate) -> DateTime {
751 assert!(decoded.is_asctime(), "expected Asctime");
752 *decoded.datetime()
753 }
754
755 fn decode_all(inputs: &[&str]) -> String {
756 let mut out = String::new();
757 for input in inputs {
758 match decode(input) {
759 Ok(date) => writeln!(out, "{input:?} => {date}").unwrap(),
760 Err(err) => writeln!(out, "{input:?} => {err}").unwrap(),
761 }
762 }
763 out
764 }
765
766 #[test]
767 fn decode_out_of_range_and_boundaries() {
768 let actual = decode_all(&[
769 "Sun, 00 Nov 1994 08:49:37 GMT",
770 "Sun, 32 Nov 1994 08:49:37 GMT",
771 "Sunday, 32-Nov-94 08:49:37 GMT",
772 "Sun Nov 32 08:49:37 1994",
773 "Sun Nov 0 08:49:37 1994",
774 "Sun, 06 Nov 1994 24:49:37 GMT",
775 "Sun, 06 Nov 1994 08:60:37 GMT",
776 "Sun, 06 Nov 1994 08:49:60 GMT",
777 "Sun, 31 Nov 1994 08:49:37 GMT",
778 "Sun, 06 Nov 1994 23:59:59 GMT",
779 ]);
780 expect![[r#"
781 "Sun, 00 Nov 1994 08:49:37 GMT" => date out of range at position 5
782 "Sun, 32 Nov 1994 08:49:37 GMT" => date out of range at position 5
783 "Sunday, 32-Nov-94 08:49:37 GMT" => date out of range at position 8
784 "Sun Nov 32 08:49:37 1994" => date out of range at position 4
785 "Sun Nov 0 08:49:37 1994" => date out of range at position 4
786 "Sun, 06 Nov 1994 24:49:37 GMT" => time out of range at position 17
787 "Sun, 06 Nov 1994 08:60:37 GMT" => time out of range at position 17
788 "Sun, 06 Nov 1994 08:49:60 GMT" => time out of range at position 17
789 "Sun, 31 Nov 1994 08:49:37 GMT" => Sun, 31 Nov 1994 08:49:37 GMT
790 "Sun, 06 Nov 1994 23:59:59 GMT" => Sun, 06 Nov 1994 23:59:59 GMT
791 "#]]
792 .assert_eq(&actual);
793 }
794
795 #[test]
796 fn decode_parses_imf_fixdate() {
797 expect![[r"
798 Ok(
799 HttpDate {
800 format: ImfFixdate,
801 dt: DateTime {
802 dayname: Sun,
803 date: Date {
804 year: 1994,
805 month: 11,
806 day: 6,
807 },
808 time: Time {
809 hour: 8,
810 minute: 49,
811 second: 37,
812 },
813 },
814 },
815 )
816 "]]
817 .assert_debug_eq(&decode("Sun, 06 Nov 1994 08:49:37 GMT"));
818 }
819
820 #[test]
821 fn decode_parses_rfc850_date() {
822 let dt = expect_rfc850(&decode("Sunday, 06-Nov-94 08:49:37 GMT").unwrap());
823 assert_eq!(dt.dayname, DayName::Sun);
824 assert_eq!(dt.date, Date::new(94, 11, 6).unwrap());
825 assert_eq!(dt.time, Time::new(8, 49, 37).unwrap());
826 }
827
828 #[test]
829 fn decode_parses_asctime() {
830 let dt = expect_asctime(&decode("Sun Nov 6 08:49:37 1994").unwrap());
831 assert_eq!(dt.dayname, DayName::Sun);
832 assert_eq!(dt.date, Date::new(1994, 11, 6).unwrap());
833 assert_eq!(dt.time, Time::new(8, 49, 37).unwrap());
834 }
835
836 #[test]
837 fn decode_parses_all_imf_fixdate_weekdays() {
838 let cases = [
839 ("Mon", DayName::Mon),
840 ("Tue", DayName::Tue),
841 ("Wed", DayName::Wed),
842 ("Thu", DayName::Thu),
843 ("Fri", DayName::Fri),
844 ("Sat", DayName::Sat),
845 ("Sun", DayName::Sun),
846 ];
847 for (name, dayname) in cases {
848 let input = format!("{name}, 06 Nov 1994 08:49:37 GMT");
849 let dt = expect_imf_fixdate(&decode(&input).unwrap());
850 assert_eq!(dt.dayname, dayname, "weekday {name}");
851 }
852 }
853
854 #[test]
855 fn decode_rejects_malformed_input() {
856 let actual = decode_all(&[
857 "",
858 "Funday, 06 Nov 1994 08:49:37 GMT",
859 "mon, 06 Nov 1994 08:49:37 GMT",
860 "Wednes, 06-Nov-94 08:49:37 GMT",
861 ", 06 Nov 1994 08:49:37 GMT",
862 "Sun",
863 "Sun;06 Nov 1994 08:49:37 GMT",
864 "Sun,06 Nov 1994 08:49:37 GMT",
865 "Sun 06 Nov 1994",
866 "Sun, 0x Nov 1994 08:49:37 GMT",
867 "Sun, 06Nov 1994 08:49:37 GMT",
868 "Sun, 06 Xyz 1994 08:49:37 GMT",
869 "Sun, 06 Nov",
870 "Sun, 06 Nov 19",
871 "Sun, 06 Nov 1994 0849:37 GMT",
872 "Sun, 06 Nov 1994 08:x9:37 GMT",
873 "Sun, 06 Nov 1994 08:49:3",
874 "Sun, 06 Nov 1994 08:49:37 ",
875 "Sunday 06-Nov-94 08:49:37 GMT",
876 "Sunday, 06 Nov 94 08:49:37 GMT",
877 ]);
878 expect![[r#"
879 "" => invalid day name at position 0
880 "Funday, 06 Nov 1994 08:49:37 GMT" => invalid day name at position 0
881 "mon, 06 Nov 1994 08:49:37 GMT" => invalid day name at position 0
882 "Wednes, 06-Nov-94 08:49:37 GMT" => invalid day name at position 0
883 ", 06 Nov 1994 08:49:37 GMT" => invalid day name at position 0
884 "Sun" => unexpected end of input at position 3
885 "Sun;06 Nov 1994 08:49:37 GMT" => expected ',' or ' ' after day name at position 3
886 "Sun,06 Nov 1994 08:49:37 GMT" => unexpected character at position 4
887 "Sun 06 Nov 1994" => invalid month value at position 4
888 "Sun, 0x Nov 1994 08:49:37 GMT" => expected digit at position 6
889 "Sun, 06Nov 1994 08:49:37 GMT" => unexpected character at position 7
890 "Sun, 06 Xyz 1994 08:49:37 GMT" => invalid month value at position 8
891 "Sun, 06 Nov" => unexpected end of input at position 11
892 "Sun, 06 Nov 19" => unexpected end of input at position 14
893 "Sun, 06 Nov 1994 0849:37 GMT" => unexpected character at position 19
894 "Sun, 06 Nov 1994 08:x9:37 GMT" => expected digit at position 20
895 "Sun, 06 Nov 1994 08:49:3" => unexpected end of input at position 24
896 "Sun, 06 Nov 1994 08:49:37 " => expected 'GMT' at position 26
897 "Sunday 06-Nov-94 08:49:37 GMT" => unexpected character at position 6
898 "Sunday, 06 Nov 94 08:49:37 GMT" => unexpected character at position 10
899 "#]]
900 .assert_eq(&actual);
901 }
902
903 #[test]
904 fn decode_rejects_trailing_data() {
905 let actual = decode_all(&[
906 "Sun, 06 Nov 1994 08:49:37 GMT ",
907 "Sun, 06 Nov 1994 08:49:37 GMT 123",
908 "Sun, 06 Nov 1994 08:49:37 GMT.",
909 "Sunday, 06-Nov-94 08:49:37 GMT extra",
910 "Sun Nov 6 08:49:37 1994!",
911 ]);
912 expect![[r#"
913 "Sun, 06 Nov 1994 08:49:37 GMT " => trailing data after HTTP date at position 29
914 "Sun, 06 Nov 1994 08:49:37 GMT 123" => trailing data after HTTP date at position 29
915 "Sun, 06 Nov 1994 08:49:37 GMT." => trailing data after HTTP date at position 29
916 "Sunday, 06-Nov-94 08:49:37 GMT extra" => trailing data after HTTP date at position 30
917 "Sun Nov 6 08:49:37 1994!" => trailing data after HTTP date at position 24
918 "#]]
919 .assert_eq(&actual);
920 }
921
922 fn sun_nov_6(year: u16) -> DateTime {
924 DateTime {
925 dayname: DayName::Sun,
926 date: Date::new(year, 11, 6).unwrap(),
927 time: Time::new(8, 49, 37).unwrap(),
928 }
929 }
930
931 #[test]
932 fn date_new_rejects_out_of_range() {
933 assert!(Date::new(1994, 11, 6).is_ok());
934 assert!(Date::new(10_000, 11, 6).is_err());
935 assert!(Date::new(1994, 0, 6).is_err());
936 assert!(Date::new(1994, 13, 6).is_err());
937 assert!(Date::new(1994, 11, 0).is_err());
938 assert!(Date::new(1994, 11, 32).is_err());
939 }
940
941 #[test]
942 fn time_new_rejects_out_of_range() {
943 assert!(Time::new(8, 49, 37).is_ok());
944 assert!(Time::new(24, 0, 0).is_err());
945 assert!(Time::new(0, 60, 0).is_err());
946 assert!(Time::new(0, 0, 60).is_err());
947 }
948
949 #[test]
950 fn rfc850_constructor_rejects_year_above_99() {
951 assert!(HttpDate::rfc850(sun_nov_6(99)).is_ok());
952 assert!(HttpDate::rfc850(sun_nov_6(100)).is_err());
953 assert!(HttpDate::rfc850(sun_nov_6(1994)).is_err());
954 }
955
956 #[test]
957 fn encode_round_trips_constructed_values() {
958 let cases = [
959 HttpDate::imf_fixdate(sun_nov_6(1994)),
960 HttpDate::rfc850(sun_nov_6(94)).unwrap(),
961 HttpDate::asctime(sun_nov_6(1994)),
962 ];
963 for date in cases {
964 let s = encode(&date);
965 assert_eq!(decode(&s).unwrap(), date, "encode produced {s:?}");
966 }
967 }
968}