icu_capi 2.0.0-beta1

C interface to ICU4X
Documentation
// 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 ffi::TimeZoneInfo;

#[diplomat::bridge]
#[diplomat::abi_rename = "icu4x_{0}_mv1"]
#[diplomat::attr(auto, namespace = "icu4x")]
pub mod ffi {
    use alloc::boxed::Box;
    use core::fmt::Write;
    use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant};
    use tinystr::TinyAsciiStr;

    use crate::{datetime::ffi::IsoDateTime, errors::ffi::TimeZoneInvalidOffsetError};

    #[diplomat::opaque]
    #[diplomat::rust_link(icu::timezone::TimeZoneInfo, Struct)]
    pub struct TimeZoneInfo {
        pub(crate) time_zone_id: icu_timezone::TimeZoneBcp47Id,
        pub(crate) offset: Option<icu_timezone::UtcOffset>,
        pub(crate) zone_variant: Option<icu_timezone::ZoneVariant>,
        pub(crate) local_time: Option<(icu_calendar::Date<icu_calendar::Iso>, icu_calendar::Time)>,
    }

    impl TimeZoneInfo {
        /// Creates a time zone with no information.
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::unknown, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::unknown, FnInStruct, hidden)]
        #[diplomat::attr(supports = fallible_constructors, named_constructor)]
        pub fn unknown() -> Box<TimeZoneInfo> {
            Box::new(icu_timezone::TimeZoneInfo::unknown().into())
        }

        /// Creates a time zone for UTC (Coordinated Universal Time).
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::utc, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::zero, FnInStruct, hidden)]
        #[diplomat::attr(supports = fallible_constructors, named_constructor)]
        pub fn utc() -> Box<TimeZoneInfo> {
            Box::new(icu_timezone::TimeZoneInfo::utc().into())
        }

        /// Creates a time zone.
        #[diplomat::attr(auto, constructor)]
        pub fn from_parts(
            bcp47_id: &DiplomatStr,
            offset_seconds: i32,
            dst: bool,
        ) -> Box<TimeZoneInfo> {
            Box::new(Self {
                time_zone_id: TinyAsciiStr::try_from_utf8(bcp47_id)
                    .ok()
                    .map(TimeZoneBcp47Id)
                    .unwrap_or(TimeZoneBcp47Id::unknown()),
                offset: UtcOffset::try_from_seconds(offset_seconds).ok(),
                zone_variant: Some(if dst {
                    ZoneVariant::Daylight
                } else {
                    ZoneVariant::Standard
                }),
                local_time: None,
            })
        }

        /// Sets the `offset` field from offset seconds.
        ///
        /// Errors if the offset seconds are out of range.
        #[diplomat::rust_link(icu::timezone::UtcOffset, Struct, compact)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::with_offset, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::try_from_seconds, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::from_seconds_unchecked, FnInStruct, hidden)]
        pub fn try_set_offset_seconds(
            &mut self,
            offset_seconds: i32,
        ) -> Result<(), TimeZoneInvalidOffsetError> {
            self.offset = Some(icu_timezone::UtcOffset::try_from_seconds(offset_seconds)?);
            Ok(())
        }

        /// Sets the `offset` field from offset as eighths of an hour.
        #[diplomat::rust_link(icu::timezone::UtcOffset, Struct, compact)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::with_offset, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::from_eighths_of_hour, FnInStruct)]
        pub fn set_offset_eighths_of_hour(&mut self, offset_eighths_of_hour: i8) {
            self.offset = Some(icu_timezone::UtcOffset::from_eighths_of_hour(
                offset_eighths_of_hour,
            ));
        }

        /// Sets the `offset` field from a string.
        #[diplomat::rust_link(icu::timezone::UtcOffset, Struct, compact)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::with_offset, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::try_from_str, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::try_from_utf8, FnInStruct, hidden)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::from_str, FnInStruct, hidden)]
        pub fn try_set_offset_str(
            &mut self,
            offset: &DiplomatStr,
        ) -> Result<(), TimeZoneInvalidOffsetError> {
            self.offset = Some(
                icu_timezone::UtcOffset::try_from_utf8(offset)
                    .map_err(|_| TimeZoneInvalidOffsetError)?,
            );
            Ok(())
        }

        /// Gets the `offset` field from offset as eighths of an hour.
        #[diplomat::rust_link(icu::timezone::UtcOffset::to_eighths_of_hour, FnInStruct)]
        pub fn offset_eighths_of_hour(&self) -> Option<i8> {
            self.offset.map(icu_timezone::UtcOffset::to_eighths_of_hour)
        }

        /// Clears the `offset` field.
        #[diplomat::rust_link(icu::timezone::UtcOffset::offset_seconds, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset, Struct, compact)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::without_offset, FnInStruct, compact)]
        pub fn clear_offset(&mut self) {
            self.offset.take();
        }

        /// Returns the value of the `offset` field as offset seconds.
        ///
        /// Returns null if the `offset` field is empty.
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::offset, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset::to_seconds, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::UtcOffset, Struct, compact)]
        #[diplomat::attr(auto, getter)]
        pub fn offset_seconds(&self) -> Option<i32> {
            self.offset.map(icu_timezone::UtcOffset::to_seconds)
        }

        /// Returns whether the `offset` field is positive.
        ///
        /// Returns null if the `offset` field is empty.
        #[diplomat::rust_link(icu::timezone::UtcOffset::is_non_negative, FnInStruct)]
        #[diplomat::attr(auto, getter)]
        pub fn is_offset_non_negative(&self) -> Option<bool> {
            self.offset.map(icu_timezone::UtcOffset::is_non_negative)
        }

        /// Returns whether the `offset` field is zero.
        ///
        /// Returns null if the `offset` field is empty (which is not the same as zero).
        #[diplomat::rust_link(icu::timezone::UtcOffset::is_zero, FnInStruct)]
        #[diplomat::attr(auto, getter)]
        pub fn is_offset_zero(&self) -> Option<bool> {
            self.offset.map(icu_timezone::UtcOffset::is_zero)
        }

        /// Returns the hours part of the the `offset` field.
        ///
        /// Returns null if the `offset` field is empty.
        #[diplomat::rust_link(icu::timezone::UtcOffset::hours_part, FnInStruct)]
        #[diplomat::attr(auto, getter)]
        pub fn offset_hours_part(&self) -> Option<i32> {
            self.offset.map(|o| o.hours_part())
        }

        /// Returns the minutes part of the the `offset` field.
        ///
        /// Returns null if the `offset` field is empty.
        #[diplomat::rust_link(icu::timezone::UtcOffset::minutes_part, FnInStruct)]
        #[diplomat::attr(auto, getter)]
        pub fn offset_minutes_part(&self) -> Option<u32> {
            self.offset.map(|o| o.minutes_part())
        }

        /// Returns the seconds part of the the `offset` field.
        ///
        /// Returns null if the `offset` field is empty.
        #[diplomat::rust_link(icu::timezone::UtcOffset::seconds_part, FnInStruct)]
        #[diplomat::attr(auto, getter)]
        pub fn offset_seconds_part(&self) -> Option<u32> {
            self.offset.map(|o| o.seconds_part())
        }

        /// Sets the `time_zone_id` field from a BCP-47 string.
        ///
        /// Errors if the string is not a valid BCP-47 time zone ID.
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id, Struct, compact)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::from_str, FnInStruct, hidden)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::deref, FnInStruct, hidden)]
        #[diplomat::rust_link(
            icu::timezone::TimeZoneBcp47Id::Target,
            AssociatedTypeInStruct,
            hidden
        )]
        pub fn set_time_zone_id(&mut self, id: &DiplomatStr) {
            self.time_zone_id = tinystr::TinyAsciiStr::try_from_utf8(id)
                .map(TimeZoneBcp47Id)
                .unwrap_or(TimeZoneBcp47Id::unknown());
        }

        /// Sets the `time_zone_id` field from an IANA string by looking up
        /// the corresponding BCP-47 string.
        pub fn set_iana_time_zone_id(
            &mut self,
            mapper: &crate::timezone_mapper::ffi::TimeZoneIdMapper,
            id: &DiplomatStr,
        ) {
            self.time_zone_id = mapper.0.as_borrowed().iana_bytes_to_bcp47(id);
        }

        /// Writes the value of the `time_zone_id` field as a string.
        ///
        /// Returns null if the `time_zone_id` field is empty.
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::time_zone_id, FnInStruct)]
        #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id, Struct, compact)]
        #[diplomat::attr(auto, getter)]
        pub fn time_zone_id(&self, write: &mut diplomat_runtime::DiplomatWrite) {
            let _infallible = write.write_str(self.time_zone_id.0.as_str());
        }

        /// Clears the `zone_variant` field.
        #[diplomat::rust_link(icu::timezone::ZoneVariant, Enum, compact)]
        pub fn clear_zone_variant(&mut self) {
            self.zone_variant.take();
        }

        /// Sets the `zone_variant` field to standard time, which may or may
        /// not correspond to a display name with Standard in its name.
        #[diplomat::rust_link(icu::timezone::ZoneVariant::Standard, EnumVariant)]
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::with_zone_variant, FnInStruct)]
        pub fn set_standard_time(&mut self) {
            self.zone_variant = Some(icu_timezone::ZoneVariant::Standard)
        }

        /// Sets the `zone_variant` field to "daylight" time, which may or may
        /// not correspond to a display name with "Daylight" in its name.
        #[diplomat::rust_link(icu::timezone::ZoneVariant::Daylight, EnumVariant)]
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::with_zone_variant, FnInStruct)]
        pub fn set_daylight_time(&mut self) {
            self.zone_variant = Some(icu_timezone::ZoneVariant::Daylight)
        }

        /// Returns whether the `zone_variant` field is standard time.
        ///
        /// Returns null if the `zone_variant` field is empty.
        #[diplomat::rust_link(icu::timezone::ZoneVariant::Standard, EnumVariant)]
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::zone_variant, FnInStruct, compact)]
        #[diplomat::attr(auto, getter)]
        pub fn is_standard_time(&self) -> Option<bool> {
            Some(self.zone_variant? == icu_timezone::ZoneVariant::Standard)
        }

        /// Returns whether the `zone_variant` field is daylight time.
        ///
        /// Returns null if the `zone_variant` field is empty.
        #[diplomat::rust_link(icu::timezone::ZoneVariant::Daylight, EnumVariant)]
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::zone_variant, FnInStruct, compact)]
        #[diplomat::attr(auto, getter)]
        pub fn is_daylight_time(&self) -> Option<bool> {
            Some(self.zone_variant? == icu_timezone::ZoneVariant::Daylight)
        }

        /// Sets the `local_time` field.
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::at_time, FnInStruct)]
        #[diplomat::attr(auto, setter = "local_time")]
        pub fn set_local_time(&mut self, datetime: &IsoDateTime) {
            self.local_time = Some((datetime.0.date, datetime.0.time));
        }

        /// Clears the `local_time` field.
        pub fn clear_local_time(&mut self) {
            self.local_time.take();
        }

        /// Returns a copy of the `local_time` field.
        #[diplomat::rust_link(icu::timezone::TimeZoneInfo::local_time, FnInStruct)]
        #[diplomat::attr(auto, getter)]
        pub fn get_local_time(&self) -> Option<Box<IsoDateTime>> {
            self.local_time
                .map(|(date, time)| Box::new(IsoDateTime(icu_calendar::DateTime { date, time })))
        }
    }
}

impl From<icu_timezone::TimeZoneInfo<icu_timezone::models::Base>> for TimeZoneInfo {
    fn from(other: icu_timezone::TimeZoneInfo<icu_timezone::models::Base>) -> Self {
        Self {
            time_zone_id: other.time_zone_id(),
            offset: other.offset(),
            zone_variant: None,
            local_time: None,
        }
    }
}