Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  ixdtf.rs

  Sprache: Rust
 

// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::{
    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]
pub enum 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,
}

impl core::error::Error for ParseError {}

impl From<Rfc9557ParseError> for ParseError {
    fn from(value: Rfc9557ParseError) -> Self {
        Self::Syntax(value)
    }
}

impl From<RangeError> for ParseError {
    fn from(value: RangeError) -> Self {
        Self::Range(value)
    }
}

impl From<DateError> for ParseError {
    fn from(value: DateError) -> Self {
        Self::Date(value)
    }
}

impl From<InvalidOffsetError> for ParseError {
    fn from(_: InvalidOffsetError) -> Self {
        Self::InvalidOffsetError
    }
}

impl From<icu_calendar::ParseError> for ParseError {
    fn from(value: icu_calendar::ParseError) -> Self {
        match value {
            icu_calendar::ParseError::MissingFields => Self::MissingFields,
            icu_calendar::ParseError::Range(r) => Self::Range(r),
            icu_calendar::ParseError::Syntax(s) => Self::Syntax(s),
            icu_calendar::ParseError::UnknownCalendar => Self::UnknownCalendar,
            _ => unreachable!(),
        }
    }
}

impl UtcOffset {
    fn try_from_utc_offset_record(record: UtcOffsetRecord) -> Result<Self, ParseError> {
        let hour_seconds = i32::from(record.hour()) * 3600;
        let minute_seconds = i32::from(record.minute()) * 60;
        Self::try_from_seconds(
            i32::from(record.sign() as i8)
                * (hour_seconds + minute_seconds + i32::from(record.second().unwrap_or(0))),
        )
        .map_err(Into::into)
    }
}

struct Intermediate<'a> {
    offset: Option<UtcOffsetRecord>,
    is_z: bool,
    iana_identifier: Option<&'a [u8]>,
    date: DateRecord,
    time: TimeRecord,
}

impl<'a> Intermediate<'a> {
    fn try_from_ixdtf_record(
        ixdtf_record: &'a IxdtfParseRecord<'_, Utf8>,
    ) -> Result<Self, ParseError> {
        let (offset, is_z, iana_identifier) = match ixdtf_record {
            // empty
            IxdtfParseRecord {
                offset: None,
                tz: None,
                ..
            } => (None, false, None),
            // -0800
            IxdtfParseRecord {
                offset: Some(UtcOffsetRecordOrZ::Offset(offset)),
                tz: None,
                ..
            } => (Some(*offset), false, None),
            // Z
            IxdtfParseRecord {
                offset: Some(UtcOffsetRecordOrZ::Z),
                tz: None,
                ..
            } => (None, true, None),
            // [-0800]
            IxdtfParseRecord {
                offset: None,
                tz:
                    Some(TimeZoneAnnotation {
                        tz: TimeZoneRecord::Offset(offset),
                        ..
                    }),
                ..
            } => (Some(UtcOffsetRecord::MinutePrecision(*offset)), false, None),
            // -0800[-0800]
            IxdtfParseRecord {
                offset: Some(UtcOffsetRecordOrZ::Offset(offset)),
                tz:
                    Some(TimeZoneAnnotation {
                        tz: TimeZoneRecord::Offset(offset1),
                        ..
                    }),
                ..
            } => {
                let annotation_offset = UtcOffsetRecord::MinutePrecision(*offset1);
                if offset != &annotation_offset {
                    return Err(ParseError::InconsistentTimeUtcOffsets);
                }
                (Some(*offset), false, None)
            }
            // -0800[America/Los_Angeles]
            IxdtfParseRecord {
                offset: Some(UtcOffsetRecordOrZ::Offset(offset)),
                tz:
                    Some(TimeZoneAnnotation {
                        tz: TimeZoneRecord::Name(iana_identifier),
                        ..
                    }),
                ..
            } => (Some(*offset), false, Some(*iana_identifier)),
            // Z[-0800]
            IxdtfParseRecord {
                offset: Some(UtcOffsetRecordOrZ::Z),
                tz:
                    Some(TimeZoneAnnotation {
                        tz: TimeZoneRecord::Offset(offset),
                        ..
                    }),
                ..
            } => (Some(UtcOffsetRecord::MinutePrecision(*offset)), true, None),
            // Z[America/Los_Angeles]
            IxdtfParseRecord {
                offset: Some(UtcOffsetRecordOrZ::Z),
                tz:
                    Some(TimeZoneAnnotation {
                        tz: TimeZoneRecord::Name(iana_identifier),
                        ..
                    }),
                ..
            } => (None, true, Some(*iana_identifier)),
            // [America/Los_Angeles]
            IxdtfParseRecord {
                offset: None,
                tz:
                    Some(TimeZoneAnnotation {
                        tz: TimeZoneRecord::Name(iana_identifier),
                        ..
                    }),
                ..
            } => (None, false, Some(*iana_identifier)),
            // non_exhaustive match: maybe something like [u-tz=uslax] in the future
            IxdtfParseRecord {
                tz: Some(TimeZoneAnnotation { tz, .. }),
                ..
            } => {
                debug_assert!(false"unexpected TimeZoneRecord: {tz:?}");
                (None, false, None)
            }
        };
        let IxdtfParseRecord {
            date: Some(date),
            time: Some(time),
            ..
        } = *ixdtf_record
        else {
            // Date or time was missing
            return Err(ParseError::MismatchedTimeZoneFields);
        };
        Ok(Self {
            offset,
            is_z,
            iana_identifier,
            date,
            time,
        })
    }

    fn offset_only(self) -> Result<UtcOffset, ParseError> {
        let None = self.iana_identifier else {
            return Err(ParseError::MismatchedTimeZoneFields);
        };
        if self.is_z {
            if let Some(offset) = self.offset {
                if offset != UtcOffsetRecord::zero() {
                    return Err(ParseError::RequiresCalculation);
                }
            }
            return Ok(UtcOffset::zero());
        }
        let Some(offset) = self.offset else {
            return Err(ParseError::MismatchedTimeZoneFields);
        };
        UtcOffset::try_from_utc_offset_record(offset)
    }

    fn location_only(
        self,
        iana_parser: IanaParserBorrowed<'_>,
    ) -> Result<TimeZoneInfo<models::AtTime>, ParseError> {
        let None = self.offset else {
            return Err(ParseError::MismatchedTimeZoneFields);
        };
        let Some(iana_identifier) = self.iana_identifier else {
            if self.is_z {
                return Err(ParseError::RequiresCalculation);
            }
            return Err(ParseError::MismatchedTimeZoneFields);
        };
        let id = iana_parser.parse_from_utf8(iana_identifier);
        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(id
            .with_offset(None)
            .at_date_time_iso(DateTime { date, time }))
    }

    fn lenient(
        self,
        iana_parser: IanaParserBorrowed<'_>,
    ) -> Result<TimeZoneInfo<models::AtTime>, ParseError> {
        let mut zone = match self.iana_identifier {
            Some(iana_identifier) => {
                if self.is_z {
                    return Err(ParseError::RequiresCalculation);
                }
                iana_parser
                    .parse_from_utf8(iana_identifier)
                    .with_offset(None)
            }
            None if self.is_z => TimeZoneInfo::utc(),
            None => TimeZoneInfo::unknown(),
        };

        if let 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.
    pub fn 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).
    pub fn 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.
    pub fn 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`].
    pub fn 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.
    pub fn 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`].
    pub fn 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)
    /// ));
    /// ```
    pub fn 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`].
    pub fn 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`")]
    pub fn 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`")]
    pub fn 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<SelfSelf::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);
    /// ```
    pub fn 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.*
    pub fn 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);
    /// ```
    pub fn 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()`].
    pub fn 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)
    }

    fn try_from_ixdtf_record(
        ixdtf_record: &IxdtfParseRecord<'_, Utf8>,
    ) -> Result<Self, ParseError> {
        let time_record = ixdtf_record.time.ok_or(ParseError::MissingFields)?;
        Self::try_from_time_record(&time_record)
    }

    fn try_from_time_record(time_record: &TimeRecord) -> Result<Self, ParseError> {
        let nanosecond = time_record
            .fraction
            .map(|fraction| {
                fraction
                    .to_nanoseconds()
                    .ok_or(ParseError::ExcessivePrecision)
            })
            .transpose()?
            .unwrap_or_default();

        Ok(Self::try_new(
            time_record.hour,
            time_record.minute,
            time_record.second,
            nanosecond,
        )?)
    }
}

impl FromStr for Time {
    type Err = ParseError;
    fn from_str(rfc_9557_str: &str) -> Result<SelfSelf::Err> {
        Self::try_from_str(rfc_9557_str)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::TimeZone;

    #[test]
    fn max_possible_rfc_9557_utc_offset() {
        assert_eq!(
            ZonedDateTime::try_offset_only_from_str("2024-08-08T12:08:19+23:59:59.999999999", Iso)
                .unwrap_err(),
            ParseError::InvalidOffsetError
        );
    }

    #[test]
    fn zone_calculations() {
        ZonedDateTime::try_offset_only_from_str("2024-08-08T12:08:19Z", Iso).unwrap();
        assert_eq!(
            ZonedDateTime::try_offset_only_from_str("2024-08-08T12:08:19Z[+08:00]", Iso)
                .unwrap_err(),
            ParseError::RequiresCalculation
        );
        assert_eq!(
            ZonedDateTime::try_offset_only_from_str("2024-08-08T12:08:19Z[Europe/Zurich]", Iso)
                .unwrap_err(),
            ParseError::MismatchedTimeZoneFields
        );
    }

    #[test]
    fn future_zone() {
        let result = ZonedDateTime::try_location_only_from_str(
            "2024-08-08T12:08:19[Future/Zone]",
            Iso,
            IanaParserBorrowed::new(),
        )
        .unwrap();
        assert_eq!(result.zone.id(), TimeZone::UNKNOWN);
        assert_eq!(result.zone.offset(), None);
    }

    #[test]
    fn lax() {
        ZonedDateTime::try_location_only_from_str(
            "2024-10-18T15:44[America/Los_Angeles]",
            icu_calendar::cal::Gregorian,
            IanaParserBorrowed::new(),
        )
        .unwrap();
    }
}

Messung V0.5 in Prozent
C=82 H=97 G=89

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002