use either::Either;
use icu_calendar::{CalendarError, DateTime, Gregorian};
use icu_timezone::{CustomTimeZone, TimeZoneError};
pub fn parse_gregorian_from_str(input: &str) -> Result<DateTime<Gregorian>, CalendarError> {
#![allow(clippy::indexing_slicing)] let validate = |c, i| -> Result<(), CalendarError> {
if input.as_bytes()[i] != c {
Err(CalendarError::Parse)
} else {
Ok(())
}
};
if input.len() < 19 || input.len() == 20 {
return Err(CalendarError::Parse);
}
let year: i32 = input[0..4].parse()?;
validate(b'-', 4)?;
let month: u8 = input[5..7].parse()?;
validate(b'-', 7)?;
let day: u8 = input[8..10].parse()?;
validate(b'T', 10)?;
let hour: u8 = input[11..13].parse()?;
validate(b':', 13)?;
let minute: u8 = input[14..16].parse()?;
validate(b':', 16)?;
let second: u8 = input[17..19].parse()?;
let mut datetime =
DateTime::try_new_gregorian_datetime(year, month, day, hour, minute, second)?;
if input.len() > 20 {
validate(b'.', 19)?;
let fraction_str = &input[20..29.min(input.len())];
let fraction = fraction_str.parse::<u32>()?;
let nanoseconds = fraction * (10u32.pow(9 - fraction_str.len() as u32));
datetime.time = icu_calendar::types::Time::try_new(hour, minute, second, nanoseconds)?;
};
Ok(datetime)
}
pub fn parse_zoned_gregorian_from_str(
input: &str,
) -> Result<(DateTime<Gregorian>, CustomTimeZone), Either<CalendarError, TimeZoneError>> {
match input.rfind(&['+', '-', '\u{2212}', 'Z']) {
#[allow(clippy::indexing_slicing)] Some(index) => Ok((
parse_gregorian_from_str(&input[..index]).map_err(Either::Left)?,
input[index..].parse().map_err(Either::Right)?,
)),
None => Err(Either::Right(TimeZoneError::InvalidOffset)),
}
}