use {
log::trace, rust_icu_common as common, rust_icu_sys as sys, rust_icu_sys::versioned_function,
rust_icu_sys::*, rust_icu_uenum as uenum, rust_icu_ustring as ustring, std::convert::TryFrom,
std::ffi,
};
#[derive(Debug)]
pub struct UCalendar {
rep: *mut sys::UCalendar,
}
impl Drop for UCalendar {
fn drop(&mut self) {
unsafe {
versioned_function!(ucal_close)(self.rep);
};
}
}
impl UCalendar {
fn new_from_uchar(
zone_id: &ustring::UChar,
locale: &str,
cal_type: sys::UCalendarType,
) -> Result<UCalendar, common::Error> {
let mut status = common::Error::OK_CODE;
let asciiz_locale = ffi::CString::new(locale).map_err(common::Error::wrapper)?;
let raw_ucal = unsafe {
versioned_function!(ucal_open)(
zone_id.as_c_ptr(),
zone_id.len() as i32,
asciiz_locale.as_ptr(),
cal_type,
&mut status,
) as *mut sys::UCalendar
};
common::Error::ok_or_warning(status)?;
Ok(UCalendar { rep: raw_ucal })
}
pub fn new(
zone_id: &str,
locale: &str,
cal_type: sys::UCalendarType,
) -> Result<UCalendar, common::Error> {
let zone_id_uchar = ustring::UChar::try_from(zone_id)?;
Self::new_from_uchar(&zone_id_uchar, locale, cal_type)
}
pub fn as_c_calendar(&self) -> *const sys::UCalendar {
self.rep
}
pub fn set_millis(&mut self, date_time: sys::UDate) -> Result<(), common::Error> {
let mut status = common::Error::OK_CODE;
unsafe {
versioned_function!(ucal_setMillis)(self.rep, date_time, &mut status);
};
common::Error::ok_or_warning(status)
}
pub fn get_millis(&self) -> Result<sys::UDate, common::Error> {
let mut status = common::Error::OK_CODE;
let millis = unsafe { versioned_function!(ucal_getMillis)(self.rep, &mut status) };
common::Error::ok_or_warning(status)?;
Ok(millis)
}
pub fn set_date(&mut self, year: i32, month: i32, date: i32) -> Result<(), common::Error> {
let mut status = common::Error::OK_CODE;
unsafe {
versioned_function!(ucal_setDate)(self.rep, year, month, date, &mut status);
}
common::Error::ok_or_warning(status)?;
Ok(())
}
pub fn set_date_time(
&mut self,
year: i32,
month: i32,
date: i32,
hour: i32,
minute: i32,
second: i32,
) -> Result<(), common::Error> {
let mut status = common::Error::OK_CODE;
unsafe {
versioned_function!(ucal_setDateTime)(
self.rep,
year,
month,
date,
hour,
minute,
second,
&mut status,
);
}
common::Error::ok_or_warning(status)?;
Ok(())
}
pub fn get_zone_offset(&self) -> Result<i32, common::Error> {
self.get(UCalendarDateFields::UCAL_ZONE_OFFSET)
}
pub fn get_dst_offset(&self) -> Result<i32, common::Error> {
self.get(UCalendarDateFields::UCAL_DST_OFFSET)
}
pub fn in_daylight_time(&self) -> Result<bool, common::Error> {
let mut status = common::Error::OK_CODE;
let in_daylight_time: sys::UBool =
unsafe { versioned_function!(ucal_inDaylightTime)(self.as_c_calendar(), &mut status) };
common::Error::ok_or_warning(status)?;
Ok(in_daylight_time != 0)
}
pub fn get(&self, field: UCalendarDateFields) -> Result<i32, common::Error> {
let mut status: UErrorCode = common::Error::OK_CODE;
let value =
unsafe { versioned_function!(ucal_get)(self.as_c_calendar(), field, &mut status) };
common::Error::ok_or_warning(status)?;
Ok(value)
}
}
pub fn set_default_time_zone(zone_id: &str) -> Result<(), common::Error> {
let mut status = common::Error::OK_CODE;
let mut zone_id_uchar = ustring::UChar::try_from(zone_id)?;
zone_id_uchar.make_z();
unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(ucal_setDefaultTimeZone)(zone_id_uchar.as_c_ptr(), &mut status);
};
common::Error::ok_or_warning(status)
}
pub fn get_default_time_zone() -> Result<String, common::Error> {
let mut status = common::Error::OK_CODE;
let time_zone_length = unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(ucal_getDefaultTimeZone)(std::ptr::null_mut(), 0, &mut status)
} as usize;
common::Error::ok_preflight(status)?;
let mut status = common::Error::OK_CODE;
let mut uchar = ustring::UChar::new_with_capacity(time_zone_length);
trace!("length: {}", time_zone_length);
unsafe {
assert!(common::Error::is_ok(status));
versioned_function!(ucal_getDefaultTimeZone)(
uchar.as_mut_c_ptr(),
time_zone_length as i32,
&mut status,
)
};
common::Error::ok_or_warning(status)?;
trace!("result: {:?}", uchar);
String::try_from(&uchar)
}
pub fn get_tz_data_version() -> Result<String, common::Error> {
let mut status = common::Error::OK_CODE;
let tz_data_version = unsafe {
let raw_cstring = versioned_function!(ucal_getTZDataVersion)(&mut status);
common::Error::ok_or_warning(status)?;
ffi::CStr::from_ptr(raw_cstring)
.to_string_lossy()
.into_owned()
};
Ok(tz_data_version)
}
pub fn get_now() -> f64 {
unsafe { versioned_function!(ucal_getNow)() as f64 }
}
pub fn country_time_zones(country: &str) -> Result<uenum::Enumeration, common::Error> {
uenum::ucal_open_country_time_zones(country)
}
pub fn time_zone_id_enumeration(
zone_type: sys::USystemTimeZoneType,
region: Option<&str>,
raw_offset: Option<i32>,
) -> Result<uenum::Enumeration, common::Error> {
uenum::ucal_open_time_zone_id_enumeration(zone_type, region, raw_offset)
}
pub fn time_zones() -> Result<uenum::Enumeration, common::Error> {
uenum::open_time_zones()
}
#[cfg(test)]
mod tests {
use {
super::{UCalendar, *},
regex::Regex,
std::collections::HashSet,
};
#[test]
fn test_time_zones() {
let tz_iter = time_zones().expect("time zones opened");
assert_eq!(
tz_iter
.map(|r| { r.expect("time zone is available") })
.take(3)
.collect::<Vec<String>>(),
vec!["ACT", "AET", "AGT"]
);
}
#[test]
fn test_time_zone_id_enumeration_no_filters() {
let tz_iter =
time_zone_id_enumeration(sys::USystemTimeZoneType::UCAL_ZONE_TYPE_ANY, None, None)
.expect("time_zone_id_enumeration() opened");
let from_enumeration = tz_iter
.map(|r| r.expect("timezone is available"))
.collect::<Vec<String>>();
let from_time_zones = time_zones()
.expect("time_zones() opened")
.map(|r| r.expect("time zone is available"))
.collect::<Vec<String>>();
assert!(!from_time_zones.is_empty());
assert_eq!(from_enumeration, from_time_zones);
}
#[test]
fn test_time_zone_id_enumeration_by_type_region() {
let tz_iter = time_zone_id_enumeration(
sys::USystemTimeZoneType::UCAL_ZONE_TYPE_CANONICAL,
Some("us"),
None,
)
.expect("time_zone_id_enumeration() opened");
assert_eq!(
tz_iter
.map(|r| { r.expect("time zone is available") })
.take(3)
.collect::<Vec<String>>(),
vec!["America/Adak", "America/Anchorage", "America/Boise"]
);
}
#[test]
fn test_time_zone_id_enumeration_by_offset() {
let tz_iter = time_zone_id_enumeration(
sys::USystemTimeZoneType::UCAL_ZONE_TYPE_ANY,
None,
Some(0), )
.expect("time_zone_id_enumeration() opened");
let tz_ids = tz_iter
.map(|r| r.expect("time zone is available"))
.collect::<HashSet<String>>();
assert!(tz_ids.contains("UTC"));
assert!(!tz_ids.contains("Etc/GMT-1"));
}
#[test]
fn test_country_time_zones() {
let tz_iter = country_time_zones("us").expect("time zones available");
assert_eq!(
tz_iter
.map(|r| { r.expect("time zone is available") })
.take(3)
.collect::<Vec<String>>(),
vec!["AST", "America/Adak", "America/Anchorage"]
);
}
#[test]
fn test_default_time_zone() {
super::set_default_time_zone("America/Adak").expect("time zone set with success");
assert_eq!(
super::get_default_time_zone().expect("time zone obtained"),
"America/Adak",
);
}
#[test]
fn test_get_tz_data_version() {
let re = Regex::new(r"^[0-9][0-9][0-9][0-9][a-z][a-z0-9]*$").expect("valid regex");
let tz_version = super::get_tz_data_version().expect("get_tz_data_version works");
assert!(re.is_match(&tz_version), "version was: {:?}", &tz_version);
}
#[test]
fn test_get_set_millis() -> Result<(), common::Error> {
let now = get_now();
let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
assert!((now - cal.get_millis()?).abs() <= 1000f64);
let arbitrary_delta_ms = 17.0;
let date = now + arbitrary_delta_ms;
cal.set_millis(date)?;
assert_eq!(cal.get_millis()?, date);
Ok(())
}
#[test]
fn test_set_date() -> Result<(), common::Error> {
let time_a = 1588899600000f64;
let time_b = 1588640400000f64;
let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
cal.set_millis(time_a)?;
cal.set_date(2020, UCalendarMonths::UCAL_MAY as i32, 4)?;
assert_eq!(cal.get_millis()?, time_b);
Ok(())
}
#[test]
fn test_set_date_time() -> Result<(), common::Error> {
let time_a = 1588901215898f64;
let time_b = 1588640400898f64;
let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
cal.set_millis(time_a)?;
cal.set_date_time(2020, UCalendarMonths::UCAL_MAY as i32, 4, 21, 0, 0)?;
assert_eq!(cal.get_millis()?, time_b);
Ok(())
}
#[test]
fn test_get() -> Result<(), common::Error> {
let date_time = 1588901215898f64;
let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
cal.set_millis(date_time)?;
assert_eq!(cal.get(UCalendarDateFields::UCAL_DAY_OF_MONTH)?, 7);
assert_eq!(cal.get(UCalendarDateFields::UCAL_MILLISECOND)?, 898);
Ok(())
}
#[test]
fn test_offsets_and_daylight_time() -> Result<(), common::Error> {
let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
let expected_zone_offset_ms: i32 = -5 * 60 * 60 * 1000;
let expected_dst_offset_ms: i32 = 1 * 60 * 60 * 1000;
cal.set_date_time(2020, UCalendarMonths::UCAL_MAY as i32, 7, 21, 0, 0)?;
assert_eq!(cal.get_zone_offset()?, expected_zone_offset_ms);
assert_eq!(cal.get_dst_offset()?, expected_dst_offset_ms);
assert!(cal.in_daylight_time()?);
let expected_zone_offset: i32 = -5 * 60 * 60 * 1000;
let expected_dst_offset: i32 = 0;
cal.set_date_time(2020, UCalendarMonths::UCAL_JANUARY as i32, 15, 12, 0, 0)?;
assert_eq!(cal.get_zone_offset()?, expected_zone_offset);
assert_eq!(cal.get_dst_offset()?, expected_dst_offset);
assert!(!cal.in_daylight_time()?);
Ok(())
}
}