attotime/parse/
historic_date.rs

1//! Implementation of string parsing logic for `HistoricDate` types.
2
3use core::str::FromStr;
4
5use crate::{HistoricDate, Month, errors::HistoricDateParsingError};
6
7impl FromStr for HistoricDate {
8    type Err = HistoricDateParsingError;
9
10    /// Parses a `HistoricDate` based on some string. Accepts only the extended complete calendar
11    /// date format specified in ISO 8601 (see section 5.2.2.1), though in addition any number of
12    /// digits is accepted for each term.
13    fn from_str(string: &str) -> Result<Self, Self::Err> {
14        let (date, remainder) = Self::parse_partial(string)?;
15        if !remainder.is_empty() {
16            Err(HistoricDateParsingError::UnexpectedRemainder)
17        } else {
18            Ok(date)
19        }
20    }
21}
22
23impl HistoricDate {
24    /// Parses a `HistoricDate` based on some string. Accepts only the extended complete calendar
25    /// date format specified in ISO 8601 (see section 5.2.2.1), though in addition any number of
26    /// digits is accepted for the years term - to extend applicability of the format to a larger
27    /// time range.
28    ///
29    /// On success, returns the resulting `HistoricDate` and any remaining input that was not yet
30    /// parsed. On failure, returns a reason indicating why.
31    pub fn parse_partial(mut string: &str) -> Result<(Self, &str), HistoricDateParsingError> {
32        // Parse year component
33        let (year, consumed_bytes) = lexical_core::parse_partial(string.as_bytes())?;
34        string = string.get(consumed_bytes..).unwrap();
35
36        // Parse year-month delimiter
37        if string.starts_with('-') {
38            string = string.get(1..).unwrap();
39        } else {
40            return Err(HistoricDateParsingError::ExpectedYearMonthDelimiter);
41        }
42
43        let (month, consumed_bytes) = lexical_core::parse_partial(string.as_bytes())?;
44        if consumed_bytes != 2 {
45            return Err(HistoricDateParsingError::MonthRepresentationNotTwoDigits);
46        }
47        let month = Month::try_from(month)?;
48        string = string.get(consumed_bytes..).unwrap();
49
50        // Parse month-day delimiter
51        if string.starts_with('-') {
52            string = string.get(1..).unwrap();
53        } else {
54            return Err(HistoricDateParsingError::ExpectedMonthDayDelimiter);
55        }
56
57        // Parse day component
58        let (day, consumed_bytes) = lexical_core::parse_partial(string.as_bytes())?;
59        if consumed_bytes != 2 {
60            return Err(HistoricDateParsingError::DayRepresentationNotTwoDigits);
61        }
62        string = string.get(consumed_bytes..).unwrap();
63
64        Ok((HistoricDate::new(year, month, day)?, string))
65    }
66}
67
68/// Tests whether a given string parses to the same historic date as the passed year, month, and
69/// day arguments.
70#[cfg(test)]
71fn parse_known_historic_date(input: &str, year: i32, month: Month, day: u8) {
72    let date = HistoricDate::from_str(input).unwrap();
73    let expected_date = HistoricDate::new(year, month, day).unwrap();
74    assert_eq!(date, expected_date);
75}
76
77/// Verifies string parsing for some known valid dates.
78#[test]
79fn known_dates() {
80    use crate::Month::*;
81    parse_known_historic_date("2000-01-01", 2000, January, 1);
82    parse_known_historic_date("1999-01-01", 1999, January, 1);
83    parse_known_historic_date("1987-06-19", 1987, June, 19);
84    parse_known_historic_date("1988-01-27", 1988, January, 27);
85    parse_known_historic_date("1988-06-19", 1988, June, 19);
86    parse_known_historic_date("1900-01-01", 1900, January, 1);
87    parse_known_historic_date("1600-01-01", 1600, January, 1);
88    parse_known_historic_date("1600-12-31", 1600, December, 31);
89    parse_known_historic_date("837-04-10", 837, April, 10);
90    parse_known_historic_date("-123-12-31", -123, December, 31);
91    parse_known_historic_date("-122-01-01", -122, January, 1);
92    parse_known_historic_date("-1000-07-12", -1000, July, 12);
93    parse_known_historic_date("-1000-02-29", -1000, February, 29);
94    parse_known_historic_date("-1001-08-17", -1001, August, 17);
95    parse_known_historic_date("-4712-01-01", -4712, January, 1);
96}