1use std::fmt;
47#[cfg(feature = "chrono")]
48pub mod chrono;
49
50#[derive(Debug, PartialEq, Eq, 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
75#[derive(Debug, PartialEq, Eq, Clone, Copy)]
76enum PunctuationTok {
77 Comma,
78 Space,
79}
80
81#[derive(Debug, PartialEq, Eq, Clone, Copy)]
83pub struct Date {
84 year: i32,
86 month: i32,
88 day: i32,
90}
91
92impl Date {
93 pub fn new(year: i32, month: i32, day: i32) -> Result<Self, DecodeError> {
100 if !(0..=9999).contains(&year) || !(1..=12).contains(&month) || !(1..=31).contains(&day) {
101 return Err(DecodeError::new("Date out of range"));
102 }
103 Ok(Self { year, month, day })
104 }
105
106 #[must_use]
107 pub fn year(&self) -> i32 {
108 self.year
109 }
110
111 #[must_use]
112 pub fn month(&self) -> i32 {
113 self.month
114 }
115
116 #[must_use]
117 pub fn day(&self) -> i32 {
118 self.day
119 }
120}
121
122#[derive(Debug, PartialEq, Eq, Clone, Copy)]
127pub struct Time {
128 hour: i32,
130 minute: i32,
132 second: i32,
134}
135
136impl Time {
137 pub fn new(hour: i32, minute: i32, second: i32) -> Result<Self, DecodeError> {
144 if !(0..=23).contains(&hour) || !(0..=59).contains(&minute) || !(0..=59).contains(&second) {
145 return Err(DecodeError::new("Time out of range"));
146 }
147 Ok(Self {
148 hour,
149 minute,
150 second,
151 })
152 }
153
154 #[must_use]
155 pub fn hour(&self) -> i32 {
156 self.hour
157 }
158
159 #[must_use]
160 pub fn minute(&self) -> i32 {
161 self.minute
162 }
163
164 #[must_use]
165 pub fn second(&self) -> i32 {
166 self.second
167 }
168}
169
170#[derive(Debug, PartialEq, Eq, Clone, Copy)]
174pub struct DateTime {
175 pub dayname: DayName,
177 pub date: Date,
179 pub time: Time,
181}
182
183#[derive(Debug, PartialEq, Eq, Clone, Copy)]
190pub struct HttpDate {
191 format: Format,
192 dt: DateTime,
193}
194
195#[derive(Debug, PartialEq, Eq, Clone, Copy)]
200enum Format {
201 ImfFixdate,
202 Rfc850,
203 Asctime,
204}
205
206impl HttpDate {
207 #[must_use]
209 pub fn imf_fixdate(dt: DateTime) -> Self {
210 Self {
211 format: Format::ImfFixdate,
212 dt,
213 }
214 }
215
216 pub fn rfc850(dt: DateTime) -> Result<Self, DecodeError> {
225 if !(0..=99).contains(&dt.date.year) {
226 return Err(DecodeError::new("RFC 850 year out of range (0-99)"));
227 }
228 Ok(Self {
229 format: Format::Rfc850,
230 dt,
231 })
232 }
233
234 #[must_use]
236 pub fn asctime(dt: DateTime) -> Self {
237 Self {
238 format: Format::Asctime,
239 dt,
240 }
241 }
242
243 #[must_use]
245 pub fn datetime(&self) -> &DateTime {
246 &self.dt
247 }
248
249 #[must_use]
250 pub fn is_imf_fixdate(&self) -> bool {
251 self.format == Format::ImfFixdate
252 }
253
254 #[must_use]
255 pub fn is_rfc850(&self) -> bool {
256 self.format == Format::Rfc850
257 }
258
259 #[must_use]
260 pub fn is_asctime(&self) -> bool {
261 self.format == Format::Asctime
262 }
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct DecodeError(String);
269
270impl DecodeError {
271 pub fn new(msg: impl Into<String>) -> Self {
273 Self(msg.into())
274 }
275}
276
277impl fmt::Display for DecodeError {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 write!(f, "DecodeError: {}", self.0)
280 }
281}
282
283impl std::error::Error for DecodeError {}
284
285struct Decoder<'a> {
287 buf: &'a str,
288 pos: usize,
289}
290
291impl Decoder<'_> {
292 fn new(buf: &str) -> Decoder<'_> {
293 Decoder { buf, pos: 0 }
294 }
295
296 #[inline]
297 fn advance(&mut self, n: usize) {
298 self.pos += n;
299 }
300
301 #[inline]
302 fn byte_at(&self, idx: usize) -> Result<u8, DecodeError> {
303 self.buf
304 .as_bytes()
305 .get(idx)
306 .copied()
307 .ok_or_else(|| DecodeError::new("Unexpected end of input"))
308 }
309
310 fn expect(&mut self, expected: u8) -> Result<(), DecodeError> {
311 let actual = self.byte_at(self.pos)?;
312 if actual == expected {
313 self.advance(1);
314 Ok(())
315 } else {
316 Err(DecodeError::new(format!(
317 "Expected byte {}, but found {}",
318 expected as char, actual as char
319 )))
320 }
321 }
322
323 #[inline]
324 fn space(&mut self) -> Result<(), DecodeError> {
325 self.expect(b' ')
326 }
327
328 #[inline]
329 fn comma(&mut self) -> Result<(), DecodeError> {
330 self.expect(b',')
331 }
332
333 #[inline]
334 fn colon(&mut self) -> Result<(), DecodeError> {
335 self.expect(b':')
336 }
337
338 fn month(&mut self) -> Result<i32, DecodeError> {
339 let m = self
340 .buf
341 .get(self.pos..self.pos + 3)
342 .ok_or_else(|| DecodeError::new("Unexpected end of input"))?;
343 let n = match m {
344 "Jan" => 1,
345 "Feb" => 2,
346 "Mar" => 3,
347 "Apr" => 4,
348 "May" => 5,
349 "Jun" => 6,
350 "Jul" => 7,
351 "Aug" => 8,
352 "Sep" => 9,
353 "Oct" => 10,
354 "Nov" => 11,
355 "Dec" => 12,
356 _ => {
357 return Err(DecodeError::new(format!("Invalid month value: {m}")));
358 }
359 };
360 self.advance(3);
361 Ok(n)
362 }
363
364 fn digits(&mut self, n: usize) -> Result<i32, DecodeError> {
365 let buf = self.buf.as_bytes();
366 let mut value: i32 = 0;
367 for i in 0..n {
368 let pos = self.pos + i;
369 match buf.get(pos) {
370 Some(&c @ b'0'..=b'9') => {
371 value = value * 10 + i32::from(c - b'0');
372 }
373 Some(&c) => {
374 return Err(DecodeError::new(format!(
375 "Expected digit at position {}, but found {}",
376 pos, c as char
377 )));
378 }
379 None => return Err(DecodeError::new("Unexpected end of input")),
380 }
381 }
382 self.advance(n);
383 Ok(value)
384 }
385
386 fn year(&mut self) -> Result<i32, DecodeError> {
387 self.digits(4)
388 }
389
390 fn day(&mut self) -> Result<i32, DecodeError> {
391 self.digits(2)
392 }
393
394 fn string(&mut self) -> String {
395 let mut out = String::with_capacity(5);
396 while self.pos < self.buf.len() {
397 let c = self.buf.as_bytes()[self.pos];
398 if !c.is_ascii_alphabetic() {
399 break;
400 }
401 out.push(c as char);
402 self.advance(1);
403 }
404 out
405 }
406
407 fn dayname_tok(&mut self) -> Result<DayNameTok, DecodeError> {
408 let s = self.string();
409 let dayname = match s.as_str() {
410 "Mon" => DayNameTok::Short(DayName::Mon),
411 "Tue" => DayNameTok::Short(DayName::Tue),
412 "Wed" => DayNameTok::Short(DayName::Wed),
413 "Thu" => DayNameTok::Short(DayName::Thu),
414 "Fri" => DayNameTok::Short(DayName::Fri),
415 "Sat" => DayNameTok::Short(DayName::Sat),
416 "Sun" => DayNameTok::Short(DayName::Sun),
417 "Monday" => DayNameTok::Long(DayName::Mon),
418 "Tuesday" => DayNameTok::Long(DayName::Tue),
419 "Wednesday" => DayNameTok::Long(DayName::Wed),
420 "Thursday" => DayNameTok::Long(DayName::Thu),
421 "Friday" => DayNameTok::Long(DayName::Fri),
422 "Saturday" => DayNameTok::Long(DayName::Sat),
423 "Sunday" => DayNameTok::Long(DayName::Sun),
424 _ => return Err(DecodeError::new(format!("Invalid day name: {s}"))),
425 };
426 Ok(dayname)
427 }
428
429 fn punctuation_tok(&mut self) -> Result<PunctuationTok, DecodeError> {
430 let c = self.byte_at(self.pos)?;
431 let tok = match c {
432 b',' => PunctuationTok::Comma,
433 b' ' => PunctuationTok::Space,
434 _ => {
435 return Err(DecodeError::new(format!(
436 "Expected punctuation at position {}, but found {}",
437 self.pos, c as char
438 )));
439 }
440 };
441 self.advance(1);
442 Ok(tok)
443 }
444
445 fn date1(&mut self) -> Result<Date, DecodeError> {
446 let day = self.day()?;
447 self.space()?;
448 let month = self.month()?;
449 self.space()?;
450 let year = self.year()?;
451 Date::new(year, month, day)
452 }
453
454 fn time(&mut self) -> Result<Time, DecodeError> {
455 let hour = self.digits(2)?;
456 self.colon()?;
457 let minute = self.digits(2)?;
458 self.colon()?;
459 let second = self.digits(2)?;
460 Time::new(hour, minute, second)
461 }
462
463 fn gmt(&mut self) -> Result<(), DecodeError> {
464 let s = self.string();
465 if s != "GMT" {
466 return Err(DecodeError::new(format!("Expected 'GMT', but found '{s}'")));
467 }
468 Ok(())
469 }
470
471 fn imf_fixdate(&mut self, dayname: DayName) -> Result<HttpDate, DecodeError> {
473 self.space()?;
474 let date = self.date1()?;
475 self.space()?;
476 let time = self.time()?;
477 self.space()?;
478 self.gmt()?;
479 Ok(HttpDate::imf_fixdate(DateTime {
480 dayname,
481 date,
482 time,
483 }))
484 }
485
486 fn date2(&mut self) -> Result<Date, DecodeError> {
487 let day = self.day()?;
488 self.expect(b'-')?;
489 let month = self.month()?;
490 self.expect(b'-')?;
491 let year = self.digits(2)?;
492 Date::new(year, month, day)
493 }
494
495 fn rfc850_date(&mut self, dayname: DayName) -> Result<HttpDate, DecodeError> {
497 self.comma()?;
498 self.space()?;
499 let date = self.date2()?;
500 self.space()?;
501 let time = self.time()?;
502 self.space()?;
503 self.gmt()?;
504 let date = DateTime {
505 dayname,
506 date,
507 time,
508 };
509 HttpDate::rfc850(date)
510 }
511
512 fn date3(&mut self) -> Result<(i32, i32), DecodeError> {
513 let m = self.month()?;
515 self.space()?;
516 let d = match self.byte_at(self.pos)? {
518 b' ' => {
519 self.space()?;
520 self.digits(1)?
521 }
522 _ => self.digits(2)?,
523 };
524 Ok((m, d))
525 }
526
527 fn asctime_date(&mut self, dayname: DayName) -> Result<HttpDate, DecodeError> {
529 let (month, day) = self.date3()?;
530 self.space()?;
531 let time = self.time()?;
532 self.space()?;
533 let year = self.year()?;
534 let date = Date::new(year, month, day)?;
535 Ok(HttpDate::asctime(DateTime {
536 dayname,
537 date,
538 time,
539 }))
540 }
541}
542
543pub fn decode(buf: &str) -> Result<HttpDate, DecodeError> {
601 let mut decoder = Decoder::new(buf);
602 let date = match decoder.dayname_tok()? {
603 DayNameTok::Long(dayname) => decoder.rfc850_date(dayname),
604 DayNameTok::Short(dayname) => match decoder.punctuation_tok()? {
605 PunctuationTok::Comma => decoder.imf_fixdate(dayname),
606 PunctuationTok::Space => decoder.asctime_date(dayname),
607 },
608 }?;
609 if decoder.pos != decoder.buf.len() {
612 return Err(DecodeError::new(format!(
613 "Trailing data after HTTP date at position {}",
614 decoder.pos
615 )));
616 }
617 Ok(date)
618}
619
620impl DayName {
623 fn short(self) -> &'static str {
625 match self {
626 DayName::Mon => "Mon",
627 DayName::Tue => "Tue",
628 DayName::Wed => "Wed",
629 DayName::Thu => "Thu",
630 DayName::Fri => "Fri",
631 DayName::Sat => "Sat",
632 DayName::Sun => "Sun",
633 }
634 }
635
636 fn long(self) -> &'static str {
638 match self {
639 DayName::Mon => "Monday",
640 DayName::Tue => "Tuesday",
641 DayName::Wed => "Wednesday",
642 DayName::Thu => "Thursday",
643 DayName::Fri => "Friday",
644 DayName::Sat => "Saturday",
645 DayName::Sun => "Sunday",
646 }
647 }
648}
649
650fn month_name(month: i32) -> &'static str {
652 match month {
653 1 => "Jan",
654 2 => "Feb",
655 3 => "Mar",
656 4 => "Apr",
657 5 => "May",
658 6 => "Jun",
659 7 => "Jul",
660 8 => "Aug",
661 9 => "Sep",
662 10 => "Oct",
663 11 => "Nov",
664 12 => "Dec",
665 _ => unreachable!("month {month} is out of range 1..=12"),
666 }
667}
668
669#[must_use]
685pub fn encode(date: &HttpDate) -> String {
686 let dt = date.datetime();
687 let month = month_name(dt.date.month());
688 let day = dt.date.day();
689 let year = dt.date.year();
690 let time = dt.time;
691 match date.format {
692 Format::ImfFixdate => format!(
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 => format!(
705 "{}, {:02}-{}-{:02} {:02}:{:02}:{:02} GMT",
706 dt.dayname.long(),
707 day,
708 month,
709 year,
710 time.hour(),
711 time.minute(),
712 time.second(),
713 ),
714 Format::Asctime => format!(
715 "{} {} {:2} {:02}:{:02}:{:02} {:04}",
716 dt.dayname.short(),
717 month,
718 day,
719 time.hour(),
720 time.minute(),
721 time.second(),
722 year,
723 ),
724 }
725}
726
727impl fmt::Display for HttpDate {
728 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
729 f.write_str(&encode(self))
730 }
731}
732
733#[cfg(test)]
736mod tests {
737 use super::*;
738
739 #[test]
740 fn expect_matching_byte_advances_position() {
741 let mut d = Decoder::new("abc");
742 assert_eq!(d.expect(b'a'), Ok(()));
743 assert_eq!(d.pos, 1);
744 }
745
746 #[test]
747 fn expect_mismatched_byte_returns_error() {
748 let mut d = Decoder::new("abc");
749 let result = d.expect(b'b');
750 assert!(result.is_err());
751 assert_eq!(d.pos, 0); }
753
754 #[test]
755 fn expect_at_end_of_input_returns_error() {
756 let mut d = Decoder::new("ab");
757 d.pos = 2;
758 let err = d.expect(b'a').unwrap_err();
759 assert_eq!(err.to_string(), "DecodeError: Unexpected end of input");
760 assert_eq!(d.pos, 2);
761 }
762
763 #[test]
764 fn parsing_punctuated_sequence_advances_through_input() {
765 let mut d = Decoder::new("Sun, 06");
766 d.expect(b'S').unwrap();
768 d.expect(b'u').unwrap();
769 d.expect(b'n').unwrap();
770 d.comma().unwrap();
771 d.space().unwrap();
772 d.expect(b'0').unwrap();
774 d.expect(b'6').unwrap();
775 assert_eq!(d.pos, 7); }
777
778 #[test]
779 fn parsing_date_sequence_with_month_advances_through_input() {
780 let mut d = Decoder::new("Sun, 06 Nov 1994");
781 d.expect(b'S').unwrap();
783 d.expect(b'u').unwrap();
784 d.expect(b'n').unwrap();
785 d.comma().unwrap();
786 d.space().unwrap();
787 assert_eq!(d.day().unwrap(), 6);
789 d.space().unwrap();
790 let month = d.month().unwrap();
792 assert_eq!(month, 11);
793 d.space().unwrap();
794 assert_eq!(d.year().unwrap(), 1994);
796 assert_eq!(d.pos, 16); }
798
799 #[test]
800 fn string_reads_alphabetic_run_and_advances() {
801 let mut d = Decoder::new("Sun, 06");
802 assert_eq!(d.string(), "Sun");
803 assert_eq!(d.pos, 3); }
805
806 #[test]
807 fn string_reads_to_end_of_input() {
808 let mut d = Decoder::new("GMT");
809 assert_eq!(d.string(), "GMT");
810 assert_eq!(d.pos, 3); }
812
813 #[test]
814 fn string_returns_empty_when_current_byte_is_not_alphabetic() {
815 let mut d = Decoder::new("123");
816 assert_eq!(d.string(), "");
817 assert_eq!(d.pos, 0); }
819
820 #[test]
821 fn string_returns_empty_at_end_of_input() {
822 let mut d = Decoder::new("Sun,");
823 d.pos = 4; assert_eq!(d.string(), "");
825 assert_eq!(d.pos, 4); }
827
828 #[test]
831 fn dayname_tok_parses_all_names() {
832 let cases = [
833 ("Mon", DayNameTok::Short(DayName::Mon)),
834 ("Tue", DayNameTok::Short(DayName::Tue)),
835 ("Wed", DayNameTok::Short(DayName::Wed)),
836 ("Thu", DayNameTok::Short(DayName::Thu)),
837 ("Fri", DayNameTok::Short(DayName::Fri)),
838 ("Sat", DayNameTok::Short(DayName::Sat)),
839 ("Sun", DayNameTok::Short(DayName::Sun)),
840 ("Monday", DayNameTok::Long(DayName::Mon)),
841 ("Tuesday", DayNameTok::Long(DayName::Tue)),
842 ("Wednesday", DayNameTok::Long(DayName::Wed)),
843 ("Thursday", DayNameTok::Long(DayName::Thu)),
844 ("Friday", DayNameTok::Long(DayName::Fri)),
845 ("Saturday", DayNameTok::Long(DayName::Sat)),
846 ("Sunday", DayNameTok::Long(DayName::Sun)),
847 ];
848 for (s, expected) in cases {
849 let mut d = Decoder::new(s);
850 assert_eq!(d.dayname_tok(), Ok(expected), "day name {s}");
851 assert_eq!(d.pos, s.len(), "pos after {s}");
852 }
853 }
854
855 #[test]
856 fn dayname_tok_rejects_invalid_name() {
857 let mut d = Decoder::new("Funday");
858 let result = d.dayname_tok();
859 assert!(result.is_err());
860 assert_eq!(d.pos, 6); }
862
863 #[test]
864 fn dayname_tok_is_case_sensitive() {
865 let mut d = Decoder::new("mon");
866 let result = d.dayname_tok();
867 assert!(result.is_err());
868 assert_eq!(d.pos, 3); }
870
871 #[test]
872 fn dayname_tok_rejects_empty_run() {
873 let mut d = Decoder::new(", 06 Nov 1994");
874 let result = d.dayname_tok();
875 assert!(result.is_err());
876 assert_eq!(d.pos, 0); }
878
879 #[test]
880 fn dayname_tok_rejects_truncated_long_name() {
881 let mut d = Decoder::new("Wednes");
882 let result = d.dayname_tok();
883 assert!(result.is_err());
884 assert_eq!(d.pos, 6); }
886
887 #[test]
890 fn punctuation_tok_parses_comma() {
891 let mut d = Decoder::new(",06 Nov");
892 assert_eq!(d.punctuation_tok(), Ok(PunctuationTok::Comma));
893 assert_eq!(d.pos, 1);
894 }
895
896 #[test]
897 fn punctuation_tok_parses_space() {
898 let mut d = Decoder::new(" 06 Nov");
899 assert_eq!(d.punctuation_tok(), Ok(PunctuationTok::Space));
900 assert_eq!(d.pos, 1);
901 }
902
903 #[test]
904 fn punctuation_tok_rejects_non_punctuation() {
905 let mut d = Decoder::new("06 Nov");
906 let err = d.punctuation_tok().unwrap_err();
907 assert!(
908 err.to_string()
909 .contains("Expected punctuation at position 0, but found 0")
910 );
911 assert_eq!(d.pos, 0); }
913
914 #[test]
915 fn punctuation_tok_at_end_of_input_returns_error() {
916 let mut d = Decoder::new("06");
917 d.pos = 2; let err = d.punctuation_tok().unwrap_err();
919 assert_eq!(err.to_string(), "DecodeError: Unexpected end of input");
920 assert_eq!(d.pos, 2);
921 }
922
923 #[test]
926 fn date1_parses_valid_date() {
927 let mut d = Decoder::new("06 Nov 1994");
928 assert_eq!(d.date1(), Date::new(1994, 11, 6));
929 assert_eq!(d.pos, 11); }
931
932 #[test]
933 fn date1_parses_leading_zero_day() {
934 let mut d = Decoder::new("01 Jan 2000");
935 assert_eq!(d.date1(), Date::new(2000, 1, 1));
936 assert_eq!(d.pos, 11);
937 }
938
939 #[test]
940 fn date1_rejects_non_digit_day() {
941 let mut d = Decoder::new("0x Nov 1994");
942 let err = d.date1().unwrap_err();
943 assert!(
944 err.to_string()
945 .contains("Expected digit at position 1, but found x")
946 );
947 }
948
949 #[test]
950 fn date1_rejects_missing_space_after_day() {
951 let mut d = Decoder::new("06Nov 1994");
952 let err = d.date1().unwrap_err();
953 assert!(err.to_string().contains("but found N"));
954 }
955
956 #[test]
957 fn date1_rejects_invalid_month() {
958 let mut d = Decoder::new("06 Xyz 1994");
959 let err = d.date1().unwrap_err();
960 assert!(err.to_string().contains("Invalid month value: Xyz"));
961 }
962
963 #[test]
964 fn date1_rejects_truncated_year() {
965 let mut d = Decoder::new("06 Nov 19");
966 let err = d.date1().unwrap_err();
967 assert_eq!(err.to_string(), "DecodeError: Unexpected end of input");
968 }
969
970 #[test]
971 fn time_parses_valid_time() {
972 let mut d = Decoder::new("08:49:37 GMT");
973 assert_eq!(d.time(), Time::new(8, 49, 37));
974 assert_eq!(d.pos, 8); }
976
977 #[test]
978 fn time_rejects_missing_colon() {
979 let mut d = Decoder::new("0849:37");
980 let err = d.time().unwrap_err();
981 assert!(err.to_string().contains("but found 4"));
982 }
983
984 #[test]
985 fn time_rejects_non_digit_minute() {
986 let mut d = Decoder::new("08:x9:37");
987 let err = d.time().unwrap_err();
988 assert!(
989 err.to_string()
990 .contains("Expected digit at position 3, but found x")
991 );
992 }
993
994 #[test]
995 fn time_rejects_truncated_seconds() {
996 let mut d = Decoder::new("08:49:3");
997 let err = d.time().unwrap_err();
998 assert_eq!(err.to_string(), "DecodeError: Unexpected end of input");
999 }
1000
1001 fn expect_imf_fixdate(decoded: &HttpDate) -> DateTime {
1004 assert!(decoded.is_imf_fixdate(), "expected ImfFixdate");
1005 *decoded.datetime()
1006 }
1007
1008 fn expect_rfc850(decoded: &HttpDate) -> DateTime {
1009 assert!(decoded.is_rfc850(), "expected Rfc850");
1010 *decoded.datetime()
1011 }
1012
1013 fn expect_asctime(decoded: &HttpDate) -> DateTime {
1014 assert!(decoded.is_asctime(), "expected Asctime");
1015 *decoded.datetime()
1016 }
1017
1018 #[test]
1019 fn decode_parses_imf_fixdate() {
1020 let dt = expect_imf_fixdate(&decode("Sun, 06 Nov 1994 08:49:37 GMT").unwrap());
1021 assert_eq!(dt.dayname, DayName::Sun);
1022 assert_eq!(dt.date, Date::new(1994, 11, 6).unwrap());
1023 assert_eq!(dt.time, Time::new(8, 49, 37).unwrap());
1024 }
1025
1026 #[test]
1027 fn decode_parses_rfc850_date() {
1028 let dt = expect_rfc850(&decode("Sunday, 06-Nov-94 08:49:37 GMT").unwrap());
1029 assert_eq!(dt.dayname, DayName::Sun);
1030 assert_eq!(dt.date, Date::new(94, 11, 6).unwrap());
1031 assert_eq!(dt.time, Time::new(8, 49, 37).unwrap());
1032 }
1033
1034 #[test]
1035 fn decode_parses_asctime() {
1036 let dt = expect_asctime(&decode("Sun Nov 6 08:49:37 1994").unwrap());
1037 assert_eq!(dt.dayname, DayName::Sun);
1038 assert_eq!(dt.date, Date::new(1994, 11, 6).unwrap());
1039 assert_eq!(dt.time, Time::new(8, 49, 37).unwrap());
1040 }
1041
1042 #[test]
1043 fn decode_parses_all_imf_fixdate_weekdays() {
1044 let cases = [
1045 ("Mon", DayName::Mon),
1046 ("Tue", DayName::Tue),
1047 ("Wed", DayName::Wed),
1048 ("Thu", DayName::Thu),
1049 ("Fri", DayName::Fri),
1050 ("Sat", DayName::Sat),
1051 ("Sun", DayName::Sun),
1052 ];
1053 for (name, dayname) in cases {
1054 let input = format!("{name}, 06 Nov 1994 08:49:37 GMT");
1055 let dt = expect_imf_fixdate(&decode(&input).unwrap());
1056 assert_eq!(dt.dayname, dayname, "weekday {name}");
1057 }
1058 }
1059
1060 #[test]
1061 fn decode_rejects_invalid_dayname() {
1062 let err = decode("Funday, 06 Nov 1994 08:49:37 GMT").expect_err("expected decode to fail");
1063 assert!(err.to_string().contains("Invalid day name: Funday"));
1064 }
1065
1066 #[test]
1067 fn decode_rejects_missing_punctuation() {
1068 let err = decode("Sun 06 Nov 1994").expect_err("expected decode to fail");
1072 assert!(err.to_string().contains("Invalid month value: 06"));
1073 }
1074
1075 #[test]
1076 fn decode_rejects_imf_fixdate_missing_gmt() {
1077 let err = decode("Sun, 06 Nov 1994 08:49:37 ").expect_err("expected decode to fail");
1078 assert!(err.to_string().contains("Expected 'GMT', but found ''"));
1079 }
1080
1081 #[test]
1082 fn decode_rejects_truncated_input() {
1083 let err = decode("Sun, 06 Nov").expect_err("expected decode to fail");
1084 assert_eq!(err.to_string(), "DecodeError: Unexpected end of input");
1085 }
1086
1087 #[test]
1088 fn decode_rejects_empty_input() {
1089 let err = decode("").expect_err("expected decode to fail");
1090 assert_eq!(err.to_string(), "DecodeError: Invalid day name: ");
1091 }
1092
1093 #[test]
1094 fn decode_rejects_trailing_data() {
1095 for input in [
1096 "Sun, 06 Nov 1994 08:49:37 GMT ",
1097 "Sun, 06 Nov 1994 08:49:37 GMT 123",
1098 "Sun, 06 Nov 1994 08:49:37 GMT.",
1099 "Sunday, 06-Nov-94 08:49:37 GMT extra",
1100 "Sun Nov 6 08:49:37 1994!",
1101 ] {
1102 let err = decode(input)
1103 .err()
1104 .unwrap_or_else(|| panic!("expected {input:?} to be rejected"));
1105 assert!(
1106 err.to_string().contains("Trailing data"),
1107 "{input:?} failed for a different reason: {err}"
1108 );
1109 }
1110 }
1111
1112 #[test]
1113 fn decode_rejects_out_of_range_components() {
1114 for input in [
1115 "Sun, 00 Nov 1994 08:49:37 GMT",
1116 "Sun, 32 Nov 1994 08:49:37 GMT",
1117 "Sunday, 32-Nov-94 08:49:37 GMT",
1118 "Sun Nov 32 08:49:37 1994",
1119 "Sun Nov 0 08:49:37 1994", "Sun, 06 Nov 1994 24:49:37 GMT",
1121 "Sun, 06 Nov 1994 08:60:37 GMT",
1122 "Sun, 06 Nov 1994 08:49:60 GMT",
1123 ] {
1124 let err = decode(input)
1125 .err()
1126 .unwrap_or_else(|| panic!("expected {input:?} to be rejected"));
1127 assert!(
1128 err.to_string().contains("out of range"),
1129 "{input:?} failed for a different reason: {err}"
1130 );
1131 }
1132 }
1133
1134 #[test]
1135 fn decode_accepts_boundary_components() {
1136 assert!(decode("Sun, 31 Nov 1994 08:49:37 GMT").is_ok());
1137 assert!(decode("Sun, 06 Nov 1994 23:59:59 GMT").is_ok());
1138 }
1139
1140 fn sun_nov_6(year: i32) -> DateTime {
1142 DateTime {
1143 dayname: DayName::Sun,
1144 date: Date::new(year, 11, 6).unwrap(),
1145 time: Time::new(8, 49, 37).unwrap(),
1146 }
1147 }
1148
1149 #[test]
1150 fn date_new_rejects_out_of_range() {
1151 assert!(Date::new(1994, 11, 6).is_ok());
1152 assert!(Date::new(-1, 11, 6).is_err());
1153 assert!(Date::new(10_000, 11, 6).is_err());
1154 assert!(Date::new(1994, 0, 6).is_err());
1155 assert!(Date::new(1994, 13, 6).is_err());
1156 assert!(Date::new(1994, 11, 0).is_err());
1157 assert!(Date::new(1994, 11, 32).is_err());
1158 }
1159
1160 #[test]
1161 fn time_new_rejects_out_of_range() {
1162 assert!(Time::new(8, 49, 37).is_ok());
1163 assert!(Time::new(24, 0, 0).is_err());
1164 assert!(Time::new(0, 60, 0).is_err());
1165 assert!(Time::new(0, 0, 60).is_err());
1166 assert!(Time::new(-1, 0, 0).is_err());
1167 }
1168
1169 #[test]
1170 fn rfc850_constructor_rejects_year_above_99() {
1171 assert!(HttpDate::rfc850(sun_nov_6(99)).is_ok());
1172 assert!(HttpDate::rfc850(sun_nov_6(100)).is_err());
1173 assert!(HttpDate::rfc850(sun_nov_6(1994)).is_err());
1174 }
1175
1176 #[test]
1177 fn encode_round_trips_constructed_values() {
1178 let cases = [
1179 HttpDate::imf_fixdate(sun_nov_6(1994)),
1180 HttpDate::rfc850(sun_nov_6(94)).unwrap(),
1181 HttpDate::asctime(sun_nov_6(1994)),
1182 ];
1183 for date in cases {
1184 let s = encode(&date);
1185 assert_eq!(decode(&s).unwrap(), date, "encode produced {s:?}");
1186 }
1187 }
1188}