diff --git a/core-foundation-sys/src/base.rs b/core-foundation-sys/src/base.rs index 36ba705..2d63d8b 100644 --- a/core-foundation-sys/src/base.rs +++ b/core-foundation-sys/src/base.rs @@ -110,9 +110,9 @@ extern { /* CFType Reference */ - //fn CFCopyDescription //fn CFCopyTypeIDDescription //fn CFGetAllocator + pub fn CFCopyDescription(cf: CFTypeRef) -> CFStringRef; pub fn CFEqual(cf1: CFTypeRef, cf2: CFTypeRef) -> Boolean; pub fn CFGetRetainCount(cf: CFTypeRef) -> CFIndex; pub fn CFGetTypeID(cf: CFTypeRef) -> CFTypeID; diff --git a/core-foundation-sys/src/date.rs b/core-foundation-sys/src/date.rs index c6cac2d..6ae91f2 100644 --- a/core-foundation-sys/src/date.rs +++ b/core-foundation-sys/src/date.rs @@ -7,9 +7,28 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use libc::c_void; + +use base::{CFAllocatorRef, CFComparisonResult, CFTypeID}; + +#[repr(C)] +pub struct __CFDate(c_void); + +pub type CFDateRef = *const __CFDate; + pub type CFTimeInterval = f64; pub type CFAbsoluteTime = CFTimeInterval; extern { + pub static kCFAbsoluteTimeIntervalSince1904: CFTimeInterval; + pub static kCFAbsoluteTimeIntervalSince1970: CFTimeInterval; + pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; + + pub fn CFDateCreate(allocator: CFAllocatorRef, at: CFAbsoluteTime) -> CFDateRef; + pub fn CFDateGetAbsoluteTime(date: CFDateRef) -> CFAbsoluteTime; + pub fn CFDateGetTimeIntervalSinceDate(date: CFDateRef, other: CFDateRef) -> CFTimeInterval; + pub fn CFDateCompare(date: CFDateRef, other: CFDateRef, context: *mut c_void) -> CFComparisonResult; + + pub fn CFDateGetTypeID() -> CFTypeID; } diff --git a/core-foundation-sys/src/lib.rs b/core-foundation-sys/src/lib.rs index 932c353..d2744e2 100644 --- a/core-foundation-sys/src/lib.rs +++ b/core-foundation-sys/src/lib.rs @@ -25,5 +25,6 @@ pub mod propertylist; pub mod runloop; pub mod set; pub mod string; +pub mod timezone; pub mod url; pub mod uuid; diff --git a/core-foundation-sys/src/timezone.rs b/core-foundation-sys/src/timezone.rs new file mode 100644 index 0000000..0376974 --- /dev/null +++ b/core-foundation-sys/src/timezone.rs @@ -0,0 +1,27 @@ +// Copyright 2013-2015 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc::c_void; + +use base::{CFAllocatorRef, CFTypeID}; +use date::{CFTimeInterval, CFAbsoluteTime}; + +#[repr(C)] +pub struct __CFTimeZone(c_void); + +pub type CFTimeZoneRef = *const __CFTimeZone; + +extern { + pub fn CFTimeZoneCopySystem() -> CFTimeZoneRef; + pub fn CFTimeZoneCopyDefault() -> CFTimeZoneRef; + pub fn CFTimeZoneCreateWithTimeIntervalFromGMT(allocator: CFAllocatorRef, interval: CFTimeInterval) -> CFTimeZoneRef; + pub fn CFTimeZoneGetSecondsFromGMT(tz: CFTimeZoneRef, time: CFAbsoluteTime) -> CFTimeInterval; + + pub fn CFTimeZoneGetTypeID() -> CFTypeID; +} diff --git a/core-foundation/Cargo.toml b/core-foundation/Cargo.toml index 02127c0..c419bdd 100644 --- a/core-foundation/Cargo.toml +++ b/core-foundation/Cargo.toml @@ -13,9 +13,11 @@ version = "0.4.4" [dependencies] libc = "0.2" +chrono = { version = "0.4", optional = true } uuid = { version = "0.5", optional = true } [features] mac_os_10_7_support = ["core-foundation-sys/mac_os_10_7_support"] # backwards compatibility mac_os_10_8_features = ["core-foundation-sys/mac_os_10_8_features"] # enables new features +with-chrono = ["chrono"] with-uuid = ["uuid"] diff --git a/core-foundation/src/array.rs b/core-foundation/src/array.rs index 116249a..35052d3 100644 --- a/core-foundation/src/array.rs +++ b/core-foundation/src/array.rs @@ -54,6 +54,7 @@ impl<'a> ExactSizeIterator for CFArrayIterator<'a> { } impl_TCFType!(CFArray, CFArrayRef, CFArrayGetTypeID); +impl_CFTypeDescription!(CFArray); impl CFArray { /// Creates a new `CFArray` with the given elements, which must be `CFType` objects. diff --git a/core-foundation/src/base.rs b/core-foundation/src/base.rs index 308761c..4b56f1f 100644 --- a/core-foundation/src/base.rs +++ b/core-foundation/src/base.rs @@ -7,8 +7,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use std::fmt; + pub use core_foundation_sys::base::*; +use string::CFString; + pub trait CFIndexConvertible { /// Always use this method to construct a `CFIndex` value. It performs bounds checking to /// ensure the value is in range. @@ -29,6 +33,15 @@ impl CFIndexConvertible for usize { /// Superclass of all Core Foundation objects. pub struct CFType(CFTypeRef); +impl fmt::Debug for CFType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let desc = unsafe { + CFString::wrap_under_create_rule(CFCopyDescription(self.0)) + }; + desc.fmt(f) + } +} + impl Clone for CFType { #[inline] fn clone(&self) -> CFType { diff --git a/core-foundation/src/boolean.rs b/core-foundation/src/boolean.rs index 8e6ca3b..b8efb7c 100644 --- a/core-foundation/src/boolean.rs +++ b/core-foundation/src/boolean.rs @@ -28,6 +28,7 @@ impl Drop for CFBoolean { } impl_TCFType!(CFBoolean, CFBooleanRef, CFBooleanGetTypeID); +impl_CFTypeDescription!(CFBoolean); impl CFBoolean { pub fn true_value() -> CFBoolean { diff --git a/core-foundation/src/data.rs b/core-foundation/src/data.rs index 7d83a61..baa95f2 100644 --- a/core-foundation/src/data.rs +++ b/core-foundation/src/data.rs @@ -29,6 +29,7 @@ impl Drop for CFData { } impl_TCFType!(CFData, CFDataRef, CFDataGetTypeID); +impl_CFTypeDescription!(CFData); impl CFData { pub fn from_buffer(buffer: &[u8]) -> CFData { diff --git a/core-foundation/src/date.rs b/core-foundation/src/date.rs new file mode 100644 index 0000000..ee78cf8 --- /dev/null +++ b/core-foundation/src/date.rs @@ -0,0 +1,136 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Core Foundation date objects. + +pub use core_foundation_sys::date::*; +use core_foundation_sys::base::{CFRelease, kCFAllocatorDefault}; + +use base::TCFType; + +#[cfg(feature = "with-chrono")] +use chrono::NaiveDateTime; + +/// A date. +pub struct CFDate(CFDateRef); + +impl Drop for CFDate { + fn drop(&mut self) { + unsafe { + CFRelease(self.as_CFTypeRef()) + } + } +} + +impl_TCFType!(CFDate, CFDateRef, CFDateGetTypeID); +impl_CFTypeDescription!(CFDate); +impl_CFComparison!(CFDate, CFDateCompare); + +impl CFDate { + #[inline] + pub fn new(time: CFAbsoluteTime) -> CFDate { + unsafe { + let date_ref = CFDateCreate(kCFAllocatorDefault, time); + TCFType::wrap_under_create_rule(date_ref) + } + } + + #[inline] + pub fn now() -> CFDate { + CFDate::new(unsafe { CFAbsoluteTimeGetCurrent() }) + } + + #[inline] + pub fn abs_time(&self) -> CFAbsoluteTime { + unsafe { + CFDateGetAbsoluteTime(self.0) + } + } + + #[cfg(feature = "with-chrono")] + pub fn naive_utc(&self) -> NaiveDateTime { + let ts = unsafe { + self.abs_time() + kCFAbsoluteTimeIntervalSince1970 + }; + let (secs, nanos) = if ts.is_sign_positive() { + (ts.trunc() as i64, ts.fract()) + } else { + // nanoseconds can't be negative in NaiveDateTime + (ts.trunc() as i64 - 1, 1.0 - ts.fract().abs()) + }; + NaiveDateTime::from_timestamp(secs, (nanos * 1e9).floor() as u32) + } + + #[cfg(feature = "with-chrono")] + pub fn from_naive_utc(time: NaiveDateTime) -> CFDate { + let secs = time.timestamp(); + let nanos = time.timestamp_subsec_nanos(); + let ts = unsafe { + secs as f64 + (nanos as f64 / 1e9) - kCFAbsoluteTimeIntervalSince1970 + }; + CFDate::new(ts) + } +} + +#[cfg(test)] +mod test { + use super::CFDate; + use std::cmp::Ordering; + + #[cfg(feature = "with-chrono")] + use chrono::NaiveDateTime; + + #[cfg(feature = "with-chrono")] + fn approx_eq(a: f64, b: f64) -> bool { + use std::f64; + + let same_sign = a.is_sign_positive() == b.is_sign_positive(); + let equal = ((a - b).abs() / f64::min(a.abs() + b.abs(), f64::MAX)) < f64::EPSILON; + (same_sign && equal) + } + + #[test] + fn date_comparison() { + let now = CFDate::now(); + let past = CFDate::new(now.abs_time() - 1.0); + assert_eq!(now.cmp(&past), Ordering::Greater); + assert_eq!(now.cmp(&now), Ordering::Equal); + assert_eq!(past.cmp(&now), Ordering::Less); + } + + #[test] + fn date_equality() { + let now = CFDate::now(); + let same_time = CFDate::new(now.abs_time()); + assert_eq!(now, same_time); + } + + #[test] + #[cfg(feature = "with-chrono")] + fn date_chrono_conversion_positive() { + let date = CFDate::now(); + let datetime = date.naive_utc(); + let converted = CFDate::from_naive_utc(datetime); + assert!(approx_eq(date.abs_time(), converted.abs_time())); + } + + #[test] + #[cfg(feature = "with-chrono")] + fn date_chrono_conversion_negative() { + use super::kCFAbsoluteTimeIntervalSince1970; + + let ts = unsafe { + kCFAbsoluteTimeIntervalSince1970 - 420.0 + }; + let date = CFDate::new(ts); + let datetime: NaiveDateTime = date.naive_utc(); + let converted = CFDate::from_naive_utc(datetime); + assert!(approx_eq(date.abs_time(), converted.abs_time())); + } +} diff --git a/core-foundation/src/dictionary.rs b/core-foundation/src/dictionary.rs index 9953c4b..3e48802 100644 --- a/core-foundation/src/dictionary.rs +++ b/core-foundation/src/dictionary.rs @@ -30,6 +30,7 @@ impl Drop for CFDictionary { } impl_TCFType!(CFDictionary, CFDictionaryRef, CFDictionaryGetTypeID); +impl_CFTypeDescription!(CFDictionary); impl CFDictionary { pub fn from_CFType_pairs(pairs: &[(K, V)]) -> CFDictionary diff --git a/core-foundation/src/lib.rs b/core-foundation/src/lib.rs index ac79907..2c7a955 100644 --- a/core-foundation/src/lib.rs +++ b/core-foundation/src/lib.rs @@ -11,6 +11,9 @@ extern crate core_foundation_sys; extern crate libc; +#[cfg(feature = "with-chrono")] +extern crate chrono; + #[macro_export] macro_rules! impl_TCFType { ($ty:ident, $raw:ident, $ty_id:ident) => { @@ -66,6 +69,17 @@ macro_rules! impl_TCFType { } } +#[macro_export] +macro_rules! impl_CFTypeDescription { + ($ty:ident) => { + impl ::std::fmt::Debug for $ty { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + self.as_CFType().fmt(f) + } + } + } +} + #[macro_export] macro_rules! impl_CFComparison { ($ty:ident, $compare:ident) => { @@ -91,7 +105,7 @@ pub mod array; pub mod base; pub mod boolean; pub mod data; -pub use core_foundation_sys::date; // back compat +pub mod date; pub mod dictionary; pub mod error; pub mod number; @@ -101,6 +115,7 @@ pub mod url; pub mod bundle; pub mod propertylist; pub mod runloop; +pub mod timezone; pub mod uuid; #[cfg(test)] diff --git a/core-foundation/src/number.rs b/core-foundation/src/number.rs index 4c31e96..7263d21 100644 --- a/core-foundation/src/number.rs +++ b/core-foundation/src/number.rs @@ -27,6 +27,7 @@ impl Drop for CFNumber { } impl_TCFType!(CFNumber, CFNumberRef, CFNumberGetTypeID); +impl_CFTypeDescription!(CFNumber); impl_CFComparison!(CFNumber, CFNumberCompare); impl CFNumber { diff --git a/core-foundation/src/runloop.rs b/core-foundation/src/runloop.rs index ccc1f78..0313dec 100644 --- a/core-foundation/src/runloop.rs +++ b/core-foundation/src/runloop.rs @@ -13,9 +13,9 @@ pub use core_foundation_sys::runloop::*; use core_foundation_sys::base::{CFIndex, CFRelease}; use core_foundation_sys::base::{kCFAllocatorDefault, CFOptionFlags}; use core_foundation_sys::string::CFStringRef; -use core_foundation_sys::date::{CFAbsoluteTime, CFTimeInterval}; use base::{TCFType}; +use date::{CFAbsoluteTime, CFTimeInterval}; use string::{CFString}; pub struct CFRunLoop(CFRunLoopRef); @@ -29,6 +29,7 @@ impl Drop for CFRunLoop { } impl_TCFType!(CFRunLoop, CFRunLoopRef, CFRunLoopGetTypeID); +impl_CFTypeDescription!(CFRunLoop); impl CFRunLoop { pub fn get_current() -> CFRunLoop { @@ -107,14 +108,14 @@ impl CFRunLoopTimer { #[cfg(test)] mod test { use super::*; - use core_foundation_sys::date::{CFAbsoluteTime, CFAbsoluteTimeGetCurrent}; + use date::{CFDate, CFAbsoluteTime}; use std::mem; use libc::c_void; #[test] fn wait_200_milliseconds() { let run_loop = CFRunLoop::get_current(); - let mut now = unsafe { CFAbsoluteTimeGetCurrent() }; + let mut now = CFDate::now().abs_time(); let mut context = unsafe { CFRunLoopTimerContext { version: 0, info: mem::transmute(&mut now), @@ -134,7 +135,7 @@ mod test { extern "C" fn timer_popped(_timer: CFRunLoopTimerRef, _info: *mut c_void) { let previous_now_ptr: *const CFAbsoluteTime = unsafe { mem::transmute(_info) }; let previous_now = unsafe { *previous_now_ptr }; - let now = unsafe { CFAbsoluteTimeGetCurrent() }; + let now = CFDate::now().abs_time(); assert!(now - previous_now > 0.19 && now - previous_now < 0.21); CFRunLoop::get_current().stop(); } diff --git a/core-foundation/src/set.rs b/core-foundation/src/set.rs index 8224a20..4dab26f 100644 --- a/core-foundation/src/set.rs +++ b/core-foundation/src/set.rs @@ -29,6 +29,7 @@ impl Drop for CFSet { } impl_TCFType!(CFSet, CFSetRef, CFSetGetTypeID); +impl_CFTypeDescription!(CFSet); impl CFSet { /// Creates a new set from a list of `CFType` instances. diff --git a/core-foundation/src/timezone.rs b/core-foundation/src/timezone.rs new file mode 100644 index 0000000..5e2650d --- /dev/null +++ b/core-foundation/src/timezone.rs @@ -0,0 +1,101 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Core Foundation time zone objects. + +pub use core_foundation_sys::timezone::*; +use core_foundation_sys::base::{CFRelease, kCFAllocatorDefault}; + +use base::TCFType; +use date::{CFDate, CFTimeInterval}; + +#[cfg(feature = "with-chrono")] +use chrono::{FixedOffset, NaiveDateTime}; + +/// A time zone. +pub struct CFTimeZone(CFTimeZoneRef); + +impl Drop for CFTimeZone { + fn drop(&mut self) { + unsafe { + CFRelease(self.as_CFTypeRef()) + } + } +} + +impl_TCFType!(CFTimeZone, CFTimeZoneRef, CFTimeZoneGetTypeID); +impl_CFTypeDescription!(CFTimeZone); + +impl Default for CFTimeZone { + fn default() -> CFTimeZone { + unsafe { + let tz_ref = CFTimeZoneCopyDefault(); + TCFType::wrap_under_create_rule(tz_ref) + } + } +} + +impl CFTimeZone { + #[inline] + pub fn new(interval: CFTimeInterval) -> CFTimeZone { + unsafe { + let tz_ref = CFTimeZoneCreateWithTimeIntervalFromGMT(kCFAllocatorDefault, interval); + TCFType::wrap_under_create_rule(tz_ref) + } + } + + #[inline] + pub fn system() -> CFTimeZone { + unsafe { + let tz_ref = CFTimeZoneCopySystem(); + TCFType::wrap_under_create_rule(tz_ref) + } + } + + pub fn seconds_from_gmt(&self, date: CFDate) -> CFTimeInterval { + unsafe { + CFTimeZoneGetSecondsFromGMT(self.0, date.abs_time()) + } + } + + #[cfg(feature = "with-chrono")] + pub fn offset_at_date(&self, date: NaiveDateTime) -> FixedOffset { + let date = CFDate::from_naive_utc(date); + FixedOffset::east(self.seconds_from_gmt(date) as i32) + } + + #[cfg(feature = "with-chrono")] + pub fn from_offset(offset: FixedOffset) -> CFTimeZone { + CFTimeZone::new(offset.local_minus_utc() as f64) + } +} + +#[cfg(test)] +mod test { + use super::CFTimeZone; + + #[cfg(feature = "with-chrono")] + use chrono::{NaiveDateTime, FixedOffset}; + + #[test] + fn timezone_comparison() { + let system = CFTimeZone::system(); + let default = CFTimeZone::default(); + assert_eq!(system, default); + } + + #[test] + #[cfg(feature = "with-chrono")] + fn timezone_chrono_conversion() { + let offset = FixedOffset::west(28800); + let tz = CFTimeZone::from_offset(offset); + let converted = tz.offset_at_date(NaiveDateTime::from_timestamp(0, 0)); + assert_eq!(offset, converted); + } +} diff --git a/core-foundation/src/uuid.rs b/core-foundation/src/uuid.rs index f196567..3396c16 100644 --- a/core-foundation/src/uuid.rs +++ b/core-foundation/src/uuid.rs @@ -32,6 +32,7 @@ impl Drop for CFUUID { } impl_TCFType!(CFUUID, CFUUIDRef, CFUUIDGetTypeID); +impl_CFTypeDescription!(CFUUID); impl CFUUID { #[inline]