usecrate::{
zone::{iana::IanaParserBorrowed, models, InvalidOffsetError, UtcOffset},
DateTime, Time, TimeZoneInfo, ZonedDateTime,
}; use core::str::FromStr; use icu_calendar::{AnyCalendarKind, AsCalendar, Date, DateError, Iso, RangeError}; use ixdtf::{
encoding::Utf8,
parsers::IxdtfParser,
records::{
DateRecord, IxdtfParseRecord, TimeRecord, TimeZoneAnnotation, TimeZoneRecord,
UtcOffsetRecord, UtcOffsetRecordOrZ,
},
ParseError as Rfc9557ParseError,
};
/// The error type for parsing RFC 9557 strings. #[derive(Debug, PartialEq, displaydoc::Display)] #[non_exhaustive] pubenum ParseError { /// Syntax error. #[displaydoc("Syntax error in the RFC 9557 string: {0}")]
Syntax(Rfc9557ParseError), /// Parsed record is out of valid date range. #[displaydoc("Value out of range: {0}")]
Range(RangeError), /// Parsed date and time records were not a valid ISO date. #[displaydoc("Parsed date and time records were not a valid ISO date: {0}")]
Date(DateError), /// There were missing fields required to parse component.
MissingFields, /// There were two offsets provided that were not consistent with each other.
InconsistentTimeUtcOffsets, /// There was an invalid Offset.
InvalidOffsetError, /// Parsed fractional digits had excessive precision beyond nanosecond.
ExcessivePrecision, /// The set of time zone fields was not expected for the given type. /// For example, if a named time zone was present with offset-only parsing, /// or an offset was present with named-time-zone-only parsing. #[displaydoc("The set of time zone fields was not expected for the given type")]
MismatchedTimeZoneFields, /// An unknown calendar was provided.
UnknownCalendar, /// Expected a different calendar. #[displaydoc("Expected calendar {0} but found calendar {1}")]
MismatchedCalendar(AnyCalendarKind, AnyCalendarKind), /// A timezone calculation is required to interpret this string, which is not supported. /// /// # Example /// ``` /// use icu::calendar::Iso; /// use icu::time::{ZonedDateTime, ParseError, zone::IanaParser}; /// /// // This timestamp is in UTC, and requires a time zone calculation in order to display a Zurich time. /// assert_eq!( /// ZonedDateTime::try_lenient_from_str("2024-08-12T12:32:00Z[Europe/Zurich]", Iso, IanaParser::new()).unwrap_err(), /// ParseError::RequiresCalculation, /// ); /// /// // These timestamps are in local time /// ZonedDateTime::try_lenient_from_str("2024-08-12T14:32:00+02:00[Europe/Zurich]", Iso, IanaParser::new()).unwrap(); /// ZonedDateTime::try_lenient_from_str("2024-08-12T14:32:00[Europe/Zurich]", Iso, IanaParser::new()).unwrap(); /// ``` #[displaydoc( "A timezone calculation is required to interpret this string, which is not supported"
)]
RequiresCalculation,
}
iflet Some(offset) = self.offset { let offset = UtcOffset::try_from_utc_offset_record(offset)?; if zone.offset().is_some_and(|i| i != offset) { return Err(ParseError::RequiresCalculation);
}
zone = zone.id().with_offset(Some(offset));
} let date = Date::<Iso>::try_new_iso(self.date.year, self.date.month, self.date.day)?; let time = Time::try_from_time_record(&self.time)?;
Ok(zone.at_date_time_iso(DateTime { date, time }))
}
#[allow(deprecated)] fn all( self,
iana_parser: IanaParserBorrowed<'_>,
) -> Result<TimeZoneInfo<models::AtTime>, ParseError> { let Some(offset) = self.offset else { return Err(ParseError::MismatchedTimeZoneFields);
}; let Some(iana_identifier) = self.iana_identifier else { return Err(ParseError::MismatchedTimeZoneFields);
}; let time_zone_id = iana_parser.parse_from_utf8(iana_identifier); let date = Date::try_new_iso(self.date.year, self.date.month, self.date.day)?; let time = Time::try_from_time_record(&self.time)?; let offset = UtcOffset::try_from_utc_offset_record(offset)?;
Ok(time_zone_id
.with_offset(Some(offset))
.at_date_time_iso(DateTime { date, time }))
}
#[allow(deprecated)] fn full( self,
iana_parser: IanaParserBorrowed<'_>,
offset_calculator: crate::zone::VariantOffsetsCalculatorBorrowed,
) -> Result<TimeZoneInfo<models::Full>, ParseError> { let Some(offset) = self.offset else { return Err(ParseError::MismatchedTimeZoneFields);
}; let Some(iana_identifier) = self.iana_identifier else { return Err(ParseError::MismatchedTimeZoneFields);
}; let time_zone_id = iana_parser.parse_from_utf8(iana_identifier); let date = Date::try_new_iso(self.date.year, self.date.month, self.date.day)?; let time = Time::try_from_time_record(&self.time)?; let offset = UtcOffset::try_from_utc_offset_record(offset)?;
Ok(time_zone_id
.with_offset(Some(offset))
.at_date_time_iso(DateTime { date, time })
.infer_variant(offset_calculator))
}
}
impl<A: AsCalendar> ZonedDateTime<A, UtcOffset> { /// Create a [`ZonedDateTime`] in any calendar from an RFC 9557 string. /// /// Returns an error if the string has a calendar annotation that does not /// match the calendar argument, unless the argument is [`Iso`]. /// /// This function is "strict": the string should have only an offset and no named time zone. pubfn try_offset_only_from_str(rfc_9557_str: &str, calendar: A) -> Result<Self, ParseError> { Self::try_offset_only_from_utf8(rfc_9557_str.as_bytes(), calendar)
}
/// Create a [`ZonedDateTime`] in any calendar from RFC 9557 syntax UTF-8 bytes. /// /// See [`Self:try_offset_only_from_str`](Self::try_offset_only_from_str). pubfn try_offset_only_from_utf8(rfc_9557_str: &[u8], calendar: A) -> Result<Self, ParseError> { let ixdtf_record = IxdtfParser::from_utf8(rfc_9557_str).parse()?; let date = Date::try_from_ixdtf_record(&ixdtf_record, calendar)?; let time = Time::try_from_ixdtf_record(&ixdtf_record)?; let zone = Intermediate::try_from_ixdtf_record(&ixdtf_record)?.offset_only()?;
Ok(ZonedDateTime { date, time, zone })
}
}
impl<A: AsCalendar> ZonedDateTime<A, TimeZoneInfo<models::AtTime>> { /// Create a [`ZonedDateTime`] in any calendar from an RFC 9557 string. /// /// Returns an error if the string has a calendar annotation that does not /// match the calendar argument, unless the argument is [`Iso`]. /// /// This function is "strict": the string should have only a named time zone and no offset. pubfn try_location_only_from_str(
rfc_9557_str: &str,
calendar: A,
iana_parser: IanaParserBorrowed,
) -> Result<Self, ParseError> { Self::try_location_only_from_utf8(rfc_9557_str.as_bytes(), calendar, iana_parser)
}
/// Create a [`ZonedDateTime`] in any calendar from RFC 9557 UTF-8 bytes. /// /// See [`Self::try_location_only_from_str`]. pubfn try_location_only_from_utf8(
rfc_9557_str: &[u8],
calendar: A,
iana_parser: IanaParserBorrowed,
) -> Result<Self, ParseError> { let ixdtf_record = IxdtfParser::from_utf8(rfc_9557_str).parse()?; let date = Date::try_from_ixdtf_record(&ixdtf_record, calendar)?; let time = Time::try_from_ixdtf_record(&ixdtf_record)?; let zone =
Intermediate::try_from_ixdtf_record(&ixdtf_record)?.location_only(iana_parser)?;
Ok(ZonedDateTime { date, time, zone })
}
/// Create a [`ZonedDateTime`] in any calendar from an RFC 9557 string. /// /// Returns an error if the string has a calendar annotation that does not /// match the calendar argument, unless the argument is [`Iso`]. /// /// This function is "lenient": the string can have an offset, and named time zone, both, or /// neither. If the named time zone is missing, it is returned as Etc/Unknown. pubfn try_lenient_from_str(
rfc_9557_str: &str,
calendar: A,
iana_parser: IanaParserBorrowed,
) -> Result<Self, ParseError> { Self::try_lenient_from_utf8(rfc_9557_str.as_bytes(), calendar, iana_parser)
}
/// Create a [`ZonedDateTime`] in any calendar from RFC 9557 UTF-8 bytes. /// /// See [`Self::try_lenient_from_str`]. pubfn try_lenient_from_utf8(
rfc_9557_str: &[u8],
calendar: A,
iana_parser: IanaParserBorrowed,
) -> Result<Self, ParseError> { let ixdtf_record = IxdtfParser::from_utf8(rfc_9557_str).parse()?; let date = Date::try_from_ixdtf_record(&ixdtf_record, calendar)?; let time = Time::try_from_ixdtf_record(&ixdtf_record)?; let zone = Intermediate::try_from_ixdtf_record(&ixdtf_record)?.lenient(iana_parser)?;
Ok(ZonedDateTime { date, time, zone })
}
/// Create a [`ZonedDateTime`] in any calendar from an RFC 9557 string. /// /// Returns an error if the string has a calendar annotation that does not /// match the calendar argument, unless the argument is [`Iso`]. /// /// The string should have both an offset and a named time zone. /// /// For more information on RFC 9557, see the [`ixdtf`] crate. /// /// # Examples /// /// Basic usage: /// /// ``` /// use icu::calendar::cal::Hebrew; /// use icu::locale::subtags::subtag; /// use icu::time::{ /// zone::{IanaParser, TimeZoneVariant, UtcOffset}, /// TimeZone, TimeZoneInfo, ZonedDateTime, /// }; /// /// let zoneddatetime = ZonedDateTime::try_strict_from_str( /// "2024-08-08T12:08:19-05:00[America/Chicago][u-ca=hebrew]", /// Hebrew, /// IanaParser::new(), /// ) /// .unwrap(); /// /// assert_eq!(zoneddatetime.date.extended_year(), 5784); /// assert_eq!( /// zoneddatetime.date.month().standard_code, /// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M11")) /// ); /// assert_eq!(zoneddatetime.date.day_of_month().0, 4); /// /// assert_eq!(zoneddatetime.time.hour.number(), 12); /// assert_eq!(zoneddatetime.time.minute.number(), 8); /// assert_eq!(zoneddatetime.time.second.number(), 19); /// assert_eq!(zoneddatetime.time.subsecond.number(), 0); /// assert_eq!(zoneddatetime.zone.id(), TimeZone(subtag!("uschi"))); /// assert_eq!( /// zoneddatetime.zone.offset(), /// Some(UtcOffset::try_from_seconds(-18000).unwrap()) /// ); /// let _ = zoneddatetime.zone.zone_name_timestamp(); /// ``` /// /// An RFC 9557 string can provide a time zone in two parts: the DateTime UTC Offset or the Time Zone /// Annotation. A DateTime UTC Offset is the time offset as laid out by RFC 3339; meanwhile, the Time /// Zone Annotation is the annotation laid out by RFC 9557 and is defined as a UTC offset or IANA Time /// Zone identifier. /// /// ## DateTime UTC Offsets /// /// Below is an example of a time zone from a DateTime UTC Offset. The syntax here is familiar to a RFC 3339 /// DateTime string. /// /// ``` /// use icu::calendar::Iso; /// use icu::time::{zone::UtcOffset, TimeZoneInfo, ZonedDateTime}; /// /// let tz_from_offset = ZonedDateTime::try_offset_only_from_str( /// "2024-08-08T12:08:19-05:00", /// Iso, /// ) /// .unwrap(); /// /// assert_eq!( /// tz_from_offset.zone, /// UtcOffset::try_from_seconds(-18000).unwrap() /// ); /// ``` /// /// ## Time Zone Annotations /// /// Below is an example of a time zone being provided by a time zone annotation. /// /// ``` /// use icu::calendar::Iso; /// use icu::locale::subtags::subtag; /// use icu::time::{ /// zone::{IanaParser, TimeZoneVariant, UtcOffset}, /// TimeZone, TimeZoneInfo, ZonedDateTime, /// }; /// /// let tz_from_offset_annotation = ZonedDateTime::try_offset_only_from_str( /// "2024-08-08T12:08:19[-05:00]", /// Iso, /// ) /// .unwrap(); /// let tz_from_iana_annotation = ZonedDateTime::try_location_only_from_str( /// "2024-08-08T12:08:19[America/Chicago]", /// Iso, /// IanaParser::new(), /// ) /// .unwrap(); /// /// assert_eq!( /// tz_from_offset_annotation.zone, /// UtcOffset::try_from_seconds(-18000).unwrap() /// ); /// /// assert_eq!( /// tz_from_iana_annotation.zone.id(), /// TimeZone(subtag!("uschi")) /// ); /// assert_eq!(tz_from_iana_annotation.zone.offset(), None); /// ``` /// /// ## UTC Offset and time zone annotations. /// /// An RFC 9557 string may contain both a UTC Offset and time zone annotation. This is fine as long as /// the time zone parts can be deemed as inconsistent or unknown consistency. /// /// ### DateTime UTC offset with UTC Offset annotation. /// /// These annotations must always be consistent as they should be either the same value or are inconsistent. /// /// ``` /// use icu::calendar::Iso; /// use icu::time::{ /// zone::UtcOffset, ParseError, TimeZone, TimeZoneInfo, ZonedDateTime, /// }; /// use tinystr::tinystr; /// /// let consistent_tz_from_both = ZonedDateTime::try_offset_only_from_str( /// "2024-08-08T12:08:19-05:00[-05:00]", /// Iso, /// ) /// .unwrap(); /// /// assert_eq!( /// consistent_tz_from_both.zone, /// UtcOffset::try_from_seconds(-18000).unwrap() /// ); /// /// let inconsistent_tz_from_both = ZonedDateTime::try_offset_only_from_str( /// "2024-08-08T12:08:19-05:00[+05:00]", /// Iso, /// ); /// /// assert!(matches!( /// inconsistent_tz_from_both, /// Err(ParseError::InconsistentTimeUtcOffsets) /// )); /// ``` pubfn try_strict_from_str(
rfc_9557_str: &str,
calendar: A,
iana_parser: IanaParserBorrowed,
) -> Result<Self, ParseError> { Self::try_strict_from_utf8(rfc_9557_str.as_bytes(), calendar, iana_parser)
}
/// Create a [`ZonedDateTime`] in any calendar from RFC 9557 UTF-8 bytes. /// /// See [`Self::try_strict_from_str`]. pubfn try_strict_from_utf8(
rfc_9557_str: &[u8],
calendar: A,
iana_parser: IanaParserBorrowed,
) -> Result<Self, ParseError> { let ixdtf_record = IxdtfParser::from_utf8(rfc_9557_str).parse()?; let date = Date::try_from_ixdtf_record(&ixdtf_record, calendar)?; let time = Time::try_from_ixdtf_record(&ixdtf_record)?; let zone = Intermediate::try_from_ixdtf_record(&ixdtf_record)?.all(iana_parser)?;
Ok(ZonedDateTime { date, time, zone })
}
}
#[allow(deprecated)] impl<A: AsCalendar> ZonedDateTime<A, TimeZoneInfo<models::Full>> { /// Create a [`ZonedDateTime`] in any calendar from an RFC 9557 string. #[deprecated(since = "2.1.0", note = "use `try_strict_from_str`")] pubfn try_full_from_str(
rfc_9557_str: &str,
calendar: A,
iana_parser: IanaParserBorrowed,
offset_calculator: crate::zone::VariantOffsetsCalculatorBorrowed,
) -> Result<Self, ParseError> { Self::try_full_from_utf8(
rfc_9557_str.as_bytes(),
calendar,
iana_parser,
offset_calculator,
)
}
/// Create a [`ZonedDateTime`] in any calendar from RFC 9557 UTF-8 bytes. /// /// See [`Self::try_full_from_str`]. #[deprecated(since = "2.1.0", note = "use `try_strict_from_utf8`")] pubfn try_full_from_utf8(
rfc_9557_str: &[u8],
calendar: A,
iana_parser: IanaParserBorrowed,
offset_calculator: crate::zone::VariantOffsetsCalculatorBorrowed,
) -> Result<Self, ParseError> { let ixdtf_record = IxdtfParser::from_utf8(rfc_9557_str).parse()?; let date = Date::try_from_ixdtf_record(&ixdtf_record, calendar)?; let time = Time::try_from_ixdtf_record(&ixdtf_record)?; let zone = Intermediate::try_from_ixdtf_record(&ixdtf_record)?
.full(iana_parser, offset_calculator)?;
Ok(ZonedDateTime { date, time, zone })
}
}
impl FromStr for DateTime<Iso> { type Err = ParseError; fn from_str(rfc_9557_str: &str) -> Result<Self, Self::Err> { Self::try_from_str(rfc_9557_str, Iso)
}
}
impl<A: AsCalendar> DateTime<A> { /// Creates a [`DateTime`] in any calendar from an RFC 9557 string. /// /// Returns an error if the string has a calendar annotation that does not /// match the calendar argument, unless the argument is [`Iso`]. /// /// ✨ *Enabled with the `ixdtf` Cargo feature.* /// /// # Examples /// /// ``` /// use icu::calendar::cal::Hebrew; /// use icu::time::DateTime; /// /// let datetime = /// DateTime::try_from_str("2024-07-17T16:01:17.045[u-ca=hebrew]", Hebrew) /// .unwrap(); /// /// assert_eq!(datetime.date.era_year().year, 5784); /// assert_eq!( /// datetime.date.month().standard_code, /// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M10")) /// ); /// assert_eq!(datetime.date.day_of_month().0, 11); /// /// assert_eq!(datetime.time.hour.number(), 16); /// assert_eq!(datetime.time.minute.number(), 1); /// assert_eq!(datetime.time.second.number(), 17); /// assert_eq!(datetime.time.subsecond.number(), 45000000); /// ``` pubfn try_from_str(rfc_9557_str: &str, calendar: A) -> Result<Self, ParseError> { Self::try_from_utf8(rfc_9557_str.as_bytes(), calendar)
}
/// Creates a [`DateTime`] in any calendar from an RFC 9557 string. /// /// See [`Self::try_from_str()`]. /// /// ✨ *Enabled with the `ixdtf` Cargo feature.* pubfn try_from_utf8(rfc_9557_str: &[u8], calendar: A) -> Result<Self, ParseError> { let ixdtf_record = IxdtfParser::from_utf8(rfc_9557_str).parse()?; let date = Date::try_from_ixdtf_record(&ixdtf_record, calendar)?; let time = Time::try_from_ixdtf_record(&ixdtf_record)?;
Ok(Self { date, time })
}
}
impl Time { /// Creates a [`Time`] from an RFC 9557 string of a time. /// /// Does not support parsing an RFC 9557 string with a date and time; for that, use [`DateTime`]. /// /// ✨ *Enabled with the `ixdtf` Cargo feature.* /// /// # Examples /// /// ``` /// use icu::time::Time; /// /// let time = Time::try_from_str("16:01:17.045").unwrap(); /// /// assert_eq!(time.hour.number(), 16); /// assert_eq!(time.minute.number(), 1); /// assert_eq!(time.second.number(), 17); /// assert_eq!(time.subsecond.number(), 45000000); /// ``` pubfn try_from_str(rfc_9557_str: &str) -> Result<Self, ParseError> { Self::try_from_utf8(rfc_9557_str.as_bytes())
}
/// Creates a [`Time`] in the ISO-8601 calendar from an RFC 9557 string. /// /// ✨ *Enabled with the `ixdtf` Cargo feature.* /// /// See [`Self::try_from_str()`]. pubfn try_from_utf8(rfc_9557_str: &[u8]) -> Result<Self, ParseError> { let ixdtf_record = IxdtfParser::from_utf8(rfc_9557_str).parse_time()?; Self::try_from_ixdtf_record(&ixdtf_record)
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.