diff --git a/CHANGELOG.md b/CHANGELOG.md index 314d710..a39cfe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- Added support for tvOS, watchOS and visionOS. + ## [0.1.61] - 2024-09-16 ### Changed diff --git a/Cargo.toml b/Cargo.toml index c2915ec..bd62024 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,14 @@ fallback = [] [target.'cfg(target_os = "android")'.dependencies] android_system_properties = "0.1.5" -[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -core-foundation-sys = "0.8.3" +[target.'cfg(target_vendor = "apple")'.dependencies] +objc2-core-foundation = { version = "0.3.0", default-features = false, features = [ + "std", + "CFBase", + "CFDate", + "CFString", + "CFTimeZone", +] } [target.'cfg(target_os = "windows")'.dependencies] windows-core = { version = ">=0.50, <=0.52" } diff --git a/src/lib.rs b/src/lib.rs index 2b92c05..c48ffea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ mod ffi_utils; #[cfg_attr(any(target_os = "linux", target_os = "hurd"), path = "tz_linux.rs")] #[cfg_attr(target_os = "windows", path = "tz_windows.rs")] -#[cfg_attr(any(target_os = "macos", target_os = "ios"), path = "tz_macos.rs")] +#[cfg_attr(target_vendor = "apple", path = "tz_darwin.rs")] #[cfg_attr( all(target_arch = "wasm32", target_os = "unknown"), path = "tz_wasm32_unknown.rs" @@ -55,7 +55,7 @@ mod ffi_utils; #[cfg_attr(target_os = "aix", path = "tz_aix.rs")] #[cfg_attr(target_os = "android", path = "tz_android.rs")] #[cfg_attr(target_os = "haiku", path = "tz_haiku.rs")] -mod platform; +mod tz_darwin; /// Error types #[derive(Debug)] @@ -100,7 +100,7 @@ impl From for GetTimezoneError { /// about this function. #[inline] pub fn get_timezone() -> Result { - platform::get_timezone_inner() + tz_darwin::get_timezone_inner() } #[cfg(test)] diff --git a/src/tz_darwin.rs b/src/tz_darwin.rs new file mode 100644 index 0000000..63ecdbc --- /dev/null +++ b/src/tz_darwin.rs @@ -0,0 +1,18 @@ +use objc2_core_foundation::{CFTimeZoneCopySystem, CFTimeZoneGetName}; + +pub(crate) fn get_timezone_inner() -> Result { + get_timezone().ok_or(crate::GetTimezoneError::OsError) +} + +/// Get system time zone, and extract its name. +#[inline] +fn get_timezone() -> Option { + // SAFETY: No invariants to uphold. + // `CFRetained` will take care of memory management. + let tz = unsafe { CFTimeZoneCopySystem() }?; + + // SAFETY: No invariants to uphold (the `CFTimeZone` is valid). + let name = unsafe { CFTimeZoneGetName(&tz) }?; + + Some(name.to_string()) +} diff --git a/src/tz_macos.rs b/src/tz_macos.rs deleted file mode 100644 index 70c39e8..0000000 --- a/src/tz_macos.rs +++ /dev/null @@ -1,144 +0,0 @@ -pub(crate) fn get_timezone_inner() -> Result { - get_timezone().ok_or(crate::GetTimezoneError::OsError) -} - -#[inline] -fn get_timezone() -> Option { - // The longest name in the IANA time zone database is 25 ASCII characters long. - const MAX_LEN: usize = 32; - let mut buf = [0; MAX_LEN]; - - // Get system time zone, and borrow its name. - let tz = system_time_zone::SystemTimeZone::new()?; - let name = tz.name()?; - - // If the name is encoded in UTF-8, copy it directly. - let name = if let Some(name) = name.as_utf8() { - name - } else { - // Otherwise convert the name to UTF-8. - name.to_utf8(&mut buf)? - }; - - if name.is_empty() || name.len() >= MAX_LEN { - // The name should not be empty, or excessively long. - None - } else { - Some(name.to_owned()) - } -} - -mod system_time_zone { - //! create a safe wrapper around `CFTimeZoneRef` - - use core_foundation_sys::base::{CFRelease, CFTypeRef}; - use core_foundation_sys::timezone::{CFTimeZoneCopySystem, CFTimeZoneGetName, CFTimeZoneRef}; - - pub(crate) struct SystemTimeZone(CFTimeZoneRef); - - impl Drop for SystemTimeZone { - fn drop(&mut self) { - // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`. - unsafe { CFRelease(self.0 as CFTypeRef) }; - } - } - - impl SystemTimeZone { - pub(crate) fn new() -> Option { - // SAFETY: No invariants to uphold. We'll release the pointer when we don't need it anymore. - let v: CFTimeZoneRef = unsafe { CFTimeZoneCopySystem() }; - if v.is_null() { - None - } else { - Some(SystemTimeZone(v)) - } - } - - /// Get the time zone name as a [super::string_ref::StringRef]. - /// - /// The lifetime of the `StringRef` is bound to our lifetime. Mutable - /// access is also prevented by taking a reference to `self`. - pub(crate) fn name(&self) -> Option> { - // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`. - let string = unsafe { CFTimeZoneGetName(self.0) }; - if string.is_null() { - None - } else { - // SAFETY: here we ensure that `string` is a valid pointer. - Some(unsafe { super::string_ref::StringRef::new(string, self) }) - } - } - } -} - -mod string_ref { - //! create safe wrapper around `CFStringRef` - - use std::convert::TryInto; - - use core_foundation_sys::base::{Boolean, CFRange}; - use core_foundation_sys::string::{ - kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength, - CFStringRef, - }; - - pub(crate) struct StringRef<'a, T> { - string: CFStringRef, - // We exclude mutable access to the parent by taking a reference to the - // parent (rather than, for example, just using a marker to enforce the - // parent's lifetime). - _parent: &'a T, - } - - impl<'a, T> StringRef<'a, T> { - // SAFETY: `StringRef` must be valid pointer - pub(crate) unsafe fn new(string: CFStringRef, _parent: &'a T) -> Self { - Self { string, _parent } - } - - pub(crate) fn as_utf8(&self) -> Option<&'a str> { - // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. - let v = unsafe { CFStringGetCStringPtr(self.string, kCFStringEncodingUTF8) }; - if !v.is_null() { - // SAFETY: `CFStringGetCStringPtr()` returns NUL-terminated strings. - let v = unsafe { std::ffi::CStr::from_ptr(v) }; - if let Ok(v) = v.to_str() { - return Some(v); - } - } - None - } - - pub(crate) fn to_utf8<'b>(&self, buf: &'b mut [u8]) -> Option<&'b str> { - // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. - let length = unsafe { CFStringGetLength(self.string) }; - - let mut buf_bytes = 0; - let range = CFRange { - location: 0, - length, - }; - - let converted_bytes = unsafe { - // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. - CFStringGetBytes( - self.string, - range, - kCFStringEncodingUTF8, - b'\0', - false as Boolean, - buf.as_mut_ptr(), - buf.len() as isize, - &mut buf_bytes, - ) - }; - if converted_bytes != length { - return None; - } - - let len = buf_bytes.try_into().ok()?; - let s = buf.get(..len)?; - std::str::from_utf8(s).ok() - } - } -}