use crate::error::ErrorMessage;
use crate::error::TemporalError;
use crate::iso::IsoTime;
use crate::parsers;
use crate::provider::TimeZoneProvider;
use crate::Calendar;
use crate::TemporalResult;
use crate::TemporalUnwrap;
use crate::TimeZone;
use crate::UtcOffset;
use icu_calendar::AnyCalendarKind;
use ixdtf::records::DateRecord;
use ixdtf::records::UtcOffsetRecordOrZ;
fn extract_kind(calendar: Option<&[u8]>) -> TemporalResult<AnyCalendarKind> {
Ok(calendar
.map(Calendar::try_kind_from_utf8)
.transpose()?
.unwrap_or(AnyCalendarKind::Iso))
}
#[derive(Copy, Clone, Debug)]
pub struct ParsedDate {
pub(crate) record: DateRecord,
pub(crate) calendar: AnyCalendarKind,
}
impl ParsedDate {
pub fn from_utf8(s: &[u8]) -> TemporalResult<Self> {
let parse_record = parsers::parse_date_time(s)?;
let calendar = extract_kind(parse_record.calendar)?;
let record = parse_record.date.temporal_unwrap()?;
let _time = parse_record
.time
.map(IsoTime::from_time_record)
.transpose()?
.unwrap_or_default();
Ok(Self { record, calendar })
}
pub fn year_month_from_utf8(s: &[u8]) -> TemporalResult<Self> {
let parse_record = parsers::parse_year_month(s)?;
let calendar = extract_kind(parse_record.calendar)?;
let record = parse_record.date.temporal_unwrap()?;
let _time = parse_record
.time
.map(IsoTime::from_time_record)
.transpose()?
.unwrap_or_default();
Ok(Self { record, calendar })
}
pub fn month_day_from_utf8(s: &[u8]) -> TemporalResult<Self> {
let parse_record = parsers::parse_month_day(s)?;
let calendar = extract_kind(parse_record.calendar)?;
let record = parse_record.date.temporal_unwrap()?;
let _time = parse_record
.time
.map(IsoTime::from_time_record)
.transpose()?
.unwrap_or_default();
Ok(Self { record, calendar })
}
}
#[derive(Copy, Clone, Debug)]
pub struct ParsedDateTime {
pub(crate) date: ParsedDate,
pub(crate) time: IsoTime,
}
impl ParsedDateTime {
pub fn from_utf8(s: &[u8]) -> TemporalResult<Self> {
let parse_record = parsers::parse_date_time(s)?;
let calendar = extract_kind(parse_record.calendar)?;
let time = parse_record
.time
.map(IsoTime::from_time_record)
.transpose()?
.unwrap_or_default();
let record = parse_record.date.temporal_unwrap()?;
Ok(Self {
date: ParsedDate { record, calendar },
time,
})
}
}
#[derive(Clone, Debug)]
pub struct ParsedZonedDateTime {
pub(crate) date: ParsedDate,
pub(crate) time: Option<IsoTime>,
pub(crate) has_utc_designator: bool,
pub(crate) match_minutes: bool,
pub(crate) offset: Option<UtcOffset>,
pub(crate) timezone: TimeZone,
}
impl ParsedZonedDateTime {
#[cfg(feature = "compiled_data")]
pub fn from_utf8(source: &[u8]) -> TemporalResult<Self> {
Self::from_utf8_with_provider(source, &*crate::builtins::TZ_PROVIDER)
}
pub fn from_utf8_with_provider(
source: &[u8],
provider: &(impl TimeZoneProvider + ?Sized),
) -> TemporalResult<Self> {
let mut match_minutes = true;
let parse_result = parsers::parse_zoned_date_time(source)?;
let annotation = parse_result.tz.temporal_unwrap()?;
let time_zone = TimeZone::from_time_zone_record(annotation.tz, provider)?;
let (offset, has_utc_designator) = match parse_result.offset {
Some(UtcOffsetRecordOrZ::Z) => (None, true),
Some(UtcOffsetRecordOrZ::Offset(offset)) => {
if offset.second().is_some() {
match_minutes = false;
}
(Some(UtcOffset::from_ixdtf_record(offset)?), false)
}
None => (None, false),
};
let calendar = extract_kind(parse_result.calendar)?;
let Some(date) = parse_result.date else {
return Err(TemporalError::range().with_enum(ErrorMessage::ParserNeedsDate));
};
let time = parse_result
.time
.map(IsoTime::from_time_record)
.transpose()?;
Ok(Self {
date: ParsedDate {
record: date,
calendar,
},
time,
has_utc_designator,
match_minutes,
offset,
timezone: time_zone,
})
}
}
#[test]
fn test_parsed_intermediate_invalid_dates() {
assert!(ParsedDate::from_utf8(b"2025-02-29").is_err());
assert!(ParsedDate::from_utf8(b"2025-02-28").is_ok());
assert!(ParsedDate::year_month_from_utf8(b"202513").is_err());
assert!(ParsedDate::year_month_from_utf8(b"202512").is_ok());
assert!(ParsedDate::month_day_from_utf8(b"1000").is_err());
assert!(ParsedDate::month_day_from_utf8(b"1001").is_ok());
assert!(ParsedDateTime::from_utf8(b"2025-02-28T25:69:69").is_err());
assert!(ParsedDateTime::from_utf8(b"2025-02-28T23:59:59").is_ok());
#[cfg(feature = "compiled_data")]
assert!(ParsedZonedDateTime::from_utf8(b"2025-02-29T00:00:00Z[utc]").is_err());
#[cfg(feature = "compiled_data")]
assert!(ParsedZonedDateTime::from_utf8(b"2025-02-28T00:00:00Z[utc]").is_ok());
}