use crate::{ParseCalendarError, PrimitiveValue, Value};
pub fn parse_g_year_month(input: impl AsRef<str>) -> Result<Value, ParseCalendarError> {
let (year, suffix) = parse_year_prefix(input.as_ref(), true)?;
let month_input = suffix
.strip_prefix('-')
.ok_or(ParseCalendarError::InvalidLexical)?;
let month = two_digits(month_input.as_bytes().get(..2))?;
let value =
crate::primitive::GYearMonth::new(year, month).ok_or(ParseCalendarError::OutOfRange)?;
let timezone = match month_input.get(2..) {
Some("") => None,
Some(suffix) => Some(
suffix
.parse()
.map_err(|_| ParseCalendarError::InvalidLexical)?,
),
None => return Err(ParseCalendarError::InvalidLexical),
};
Ok(PrimitiveValue::GYearMonth(value.with_timezone(timezone)).into())
}
pub fn parse_g_year(input: impl AsRef<str>) -> Result<Value, ParseCalendarError> {
let (year, suffix) = parse_year_prefix(input.as_ref(), true)?;
let timezone = if suffix.is_empty() {
None
} else {
Some(
suffix
.parse()
.map_err(|_| ParseCalendarError::InvalidLexical)?,
)
};
Ok(PrimitiveValue::GYear(crate::primitive::GYear::new(year).with_timezone(timezone)).into())
}
#[cfg(feature = "jiff")]
pub(crate) fn year_prefix(input: &str) -> Result<(i32, &str), ParseCalendarError> {
parse_year_prefix(input, false)
}
fn parse_year_prefix(
input: &str,
allow_negative_zero: bool,
) -> Result<(i32, &str), ParseCalendarError> {
let negative = input.starts_with('-');
let unsigned = input.strip_prefix('-').unwrap_or(input);
let digits = unsigned.bytes().take_while(u8::is_ascii_digit).count();
if digits < 4 || (digits > 4 && unsigned.starts_with('0')) {
return Err(ParseCalendarError::InvalidLexical);
}
let end = digits + usize::from(negative);
let year = input[..end]
.parse::<i32>()
.map_err(|_| ParseCalendarError::OutOfRange)?;
if negative && year == 0 && !allow_negative_zero {
return Err(ParseCalendarError::InvalidLexical);
}
Ok((year, &input[end..]))
}
pub fn parse_g_month_day(input: impl AsRef<str>) -> Result<Value, ParseCalendarError> {
let input = input.as_ref();
let bytes = input.as_bytes();
if !bytes.starts_with(b"--") || bytes.get(4) != Some(&b'-') {
return Err(ParseCalendarError::InvalidLexical);
}
let month = two_digits(bytes.get(2..4))?;
let day = two_digits(bytes.get(5..7))?;
let value =
crate::primitive::GMonthDay::new(month, day).ok_or(ParseCalendarError::OutOfRange)?;
let timezone = match input.get(7..) {
Some("") => None,
Some(suffix) => Some(
suffix
.parse()
.map_err(|_| ParseCalendarError::InvalidLexical)?,
),
None => return Err(ParseCalendarError::InvalidLexical),
};
Ok(PrimitiveValue::GMonthDay(value.with_timezone(timezone)).into())
}
pub fn parse_g_day(input: impl AsRef<str>) -> Result<Value, ParseCalendarError> {
let input = input.as_ref();
let bytes = input.as_bytes();
if !bytes.starts_with(b"---") {
return Err(ParseCalendarError::InvalidLexical);
}
let day = two_digits(bytes.get(3..5))?;
let day = crate::primitive::GDay::new(day).ok_or(ParseCalendarError::OutOfRange)?;
let timezone = match input.get(5..) {
Some("") => None,
Some(suffix) => Some(
suffix
.parse()
.map_err(|_| ParseCalendarError::InvalidLexical)?,
),
None => return Err(ParseCalendarError::InvalidLexical),
};
Ok(PrimitiveValue::GDay(day.with_timezone(timezone)).into())
}
pub fn parse_g_month(input: impl AsRef<str>) -> Result<Value, ParseCalendarError> {
let input = input.as_ref();
let bytes = input.as_bytes();
if !bytes.starts_with(b"--") {
return Err(ParseCalendarError::InvalidLexical);
}
let month = two_digits(bytes.get(2..4))?;
let month = crate::primitive::GMonth::new(month).ok_or(ParseCalendarError::OutOfRange)?;
let timezone = match input.get(4..) {
Some("") => None,
Some(suffix) => Some(
suffix
.parse()
.map_err(|_| ParseCalendarError::InvalidLexical)?,
),
None => return Err(ParseCalendarError::InvalidLexical),
};
Ok(PrimitiveValue::GMonth(month.with_timezone(timezone)).into())
}
fn two_digits(bytes: Option<&[u8]>) -> Result<u8, ParseCalendarError> {
match bytes {
Some([a, b]) if a.is_ascii_digit() && b.is_ascii_digit() => Ok((a - b'0') * 10 + b - b'0'),
_ => Err(ParseCalendarError::InvalidLexical),
}
}