1use {
21 log::trace, rust_icu_common as common, rust_icu_sys as sys, rust_icu_sys::versioned_function,
22 rust_icu_sys::*, rust_icu_uenum as uenum, rust_icu_ustring as ustring, std::convert::TryFrom,
23 std::ffi,
24};
25
26#[derive(Debug)]
31pub struct UCalendar {
32 rep: *mut sys::UCalendar,
37}
38
39impl Drop for UCalendar {
40 fn drop(&mut self) {
44 unsafe {
45 versioned_function!(ucal_close)(self.rep);
46 };
47 }
48}
49
50impl UCalendar {
51 fn new_from_uchar(
55 zone_id: &ustring::UChar,
56 locale: &str,
57 cal_type: sys::UCalendarType,
58 ) -> Result<UCalendar, common::Error> {
59 let mut status = common::Error::OK_CODE;
60 let asciiz_locale = ffi::CString::new(locale).map_err(common::Error::wrapper)?;
61 let raw_ucal = unsafe {
66 versioned_function!(ucal_open)(
67 zone_id.as_c_ptr(),
68 zone_id.len() as i32,
69 asciiz_locale.as_ptr(),
70 cal_type,
71 &mut status,
72 ) as *mut sys::UCalendar
73 };
74 common::Error::ok_or_warning(status)?;
75 Ok(UCalendar { rep: raw_ucal })
76 }
77
78 pub fn new(
82 zone_id: &str,
83 locale: &str,
84 cal_type: sys::UCalendarType,
85 ) -> Result<UCalendar, common::Error> {
86 let zone_id_uchar = ustring::UChar::try_from(zone_id)?;
87 Self::new_from_uchar(&zone_id_uchar, locale, cal_type)
88 }
89 pub fn as_c_calendar(&self) -> *const sys::UCalendar {
92 self.rep
93 }
94
95 pub fn set_millis(&mut self, date_time: sys::UDate) -> Result<(), common::Error> {
99 let mut status = common::Error::OK_CODE;
100 unsafe {
101 versioned_function!(ucal_setMillis)(self.rep, date_time, &mut status);
102 };
103 common::Error::ok_or_warning(status)
104 }
105
106 pub fn get_millis(&self) -> Result<sys::UDate, common::Error> {
110 let mut status = common::Error::OK_CODE;
111 let millis = unsafe { versioned_function!(ucal_getMillis)(self.rep, &mut status) };
112 common::Error::ok_or_warning(status)?;
113 Ok(millis)
114 }
115
116 pub fn set_date(&mut self, year: i32, month: i32, date: i32) -> Result<(), common::Error> {
122 let mut status = common::Error::OK_CODE;
123 unsafe {
124 versioned_function!(ucal_setDate)(self.rep, year, month, date, &mut status);
125 }
126 common::Error::ok_or_warning(status)?;
127 Ok(())
128 }
129
130 pub fn set_date_time(
136 &mut self,
137 year: i32,
138 month: i32,
139 date: i32,
140 hour: i32,
141 minute: i32,
142 second: i32,
143 ) -> Result<(), common::Error> {
144 let mut status = common::Error::OK_CODE;
145 unsafe {
146 versioned_function!(ucal_setDateTime)(
147 self.rep,
148 year,
149 month,
150 date,
151 hour,
152 minute,
153 second,
154 &mut status,
155 );
156 }
157 common::Error::ok_or_warning(status)?;
158 Ok(())
159 }
160
161 pub fn get_zone_offset(&self) -> Result<i32, common::Error> {
170 self.get(UCalendarDateFields::UCAL_ZONE_OFFSET)
171 }
172
173 pub fn get_dst_offset(&self) -> Result<i32, common::Error> {
180 self.get(UCalendarDateFields::UCAL_DST_OFFSET)
181 }
182
183 pub fn in_daylight_time(&self) -> Result<bool, common::Error> {
187 let mut status = common::Error::OK_CODE;
188 let in_daylight_time: sys::UBool =
189 unsafe { versioned_function!(ucal_inDaylightTime)(self.as_c_calendar(), &mut status) };
190 common::Error::ok_or_warning(status)?;
191 Ok(in_daylight_time != 0)
192 }
193
194 pub fn get(&self, field: UCalendarDateFields) -> Result<i32, common::Error> {
198 let mut status: UErrorCode = common::Error::OK_CODE;
199 let value =
200 unsafe { versioned_function!(ucal_get)(self.as_c_calendar(), field, &mut status) };
201 common::Error::ok_or_warning(status)?;
202 Ok(value)
203 }
204}
205
206pub fn set_default_time_zone(zone_id: &str) -> Result<(), common::Error> {
208 let mut status = common::Error::OK_CODE;
209 let mut zone_id_uchar = ustring::UChar::try_from(zone_id)?;
210 zone_id_uchar.make_z();
211 unsafe {
213 assert!(common::Error::is_ok(status));
214 versioned_function!(ucal_setDefaultTimeZone)(zone_id_uchar.as_c_ptr(), &mut status);
215 };
216 common::Error::ok_or_warning(status)
217}
218
219pub fn get_default_time_zone() -> Result<String, common::Error> {
221 let mut capacity = 64;
223
224 loop {
226 let mut status = common::Error::OK_CODE;
227 let mut uchar = ustring::UChar::new_with_capacity(capacity);
228
229 let len = unsafe {
230 assert!(common::Error::is_ok(status));
231 versioned_function!(ucal_getDefaultTimeZone)(
232 uchar.as_mut_c_ptr(),
233 capacity as i32,
234 &mut status
235 )
236 } as usize;
237
238 if status == sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR
239 || (common::Error::is_ok(status) && len > capacity)
240 {
241 capacity = len;
242 continue; }
244
245 common::Error::ok_or_warning(status)?;
246 uchar.resize(len);
247 trace!("result: {:?}", uchar);
248 return String::try_from(&uchar);
249 }
250}
251
252pub fn get_tz_data_version() -> Result<String, common::Error> {
254 let mut status = common::Error::OK_CODE;
255
256 let tz_data_version = unsafe {
257 let raw_cstring = versioned_function!(ucal_getTZDataVersion)(&mut status);
258 common::Error::ok_or_warning(status)?;
259 ffi::CStr::from_ptr(raw_cstring)
260 .to_string_lossy()
261 .into_owned()
262 };
263
264 Ok(tz_data_version)
265}
266
267pub fn get_now() -> f64 {
271 unsafe { versioned_function!(ucal_getNow)() as f64 }
272}
273
274pub fn country_time_zones(country: &str) -> Result<uenum::Enumeration, common::Error> {
278 uenum::ucal_open_country_time_zones(country)
279}
280
281pub fn time_zone_id_enumeration(
285 zone_type: sys::USystemTimeZoneType,
286 region: Option<&str>,
287 raw_offset: Option<i32>,
288) -> Result<uenum::Enumeration, common::Error> {
289 uenum::ucal_open_time_zone_id_enumeration(zone_type, region, raw_offset)
290}
291
292pub fn time_zones() -> Result<uenum::Enumeration, common::Error> {
296 uenum::open_time_zones()
297}
298
299#[cfg(test)]
300mod tests {
301 use {
302 super::{UCalendar, *},
303 regex::Regex,
304 std::collections::HashSet,
305 };
306
307 #[test]
308 fn test_time_zones() {
309 let tz_iter = time_zones().expect("time zones opened");
310 assert_eq!(
311 tz_iter
312 .map(|r| { r.expect("time zone is available") })
313 .take(3)
314 .collect::<Vec<String>>(),
315 vec!["ACT", "AET", "AGT"]
316 );
317 }
318
319 #[test]
320 fn test_time_zone_id_enumeration_no_filters() {
321 let tz_iter =
322 time_zone_id_enumeration(sys::USystemTimeZoneType::UCAL_ZONE_TYPE_ANY, None, None)
323 .expect("time_zone_id_enumeration() opened");
324
325 let from_enumeration = tz_iter
326 .map(|r| r.expect("timezone is available"))
327 .collect::<Vec<String>>();
328
329 let from_time_zones = time_zones()
330 .expect("time_zones() opened")
331 .map(|r| r.expect("time zone is available"))
332 .collect::<Vec<String>>();
333
334 assert!(!from_time_zones.is_empty());
335
336 assert_eq!(from_enumeration, from_time_zones);
337 }
338
339 #[test]
340 fn test_time_zone_id_enumeration_by_type_region() {
341 let tz_iter = time_zone_id_enumeration(
342 sys::USystemTimeZoneType::UCAL_ZONE_TYPE_CANONICAL,
343 Some("us"),
344 None,
345 )
346 .expect("time_zone_id_enumeration() opened");
347 assert_eq!(
348 tz_iter
349 .map(|r| { r.expect("time zone is available") })
350 .take(3)
351 .collect::<Vec<String>>(),
352 vec!["America/Adak", "America/Anchorage", "America/Boise"]
353 );
354 }
355
356 #[test]
357 fn test_time_zone_id_enumeration_by_offset() {
358 let tz_iter = time_zone_id_enumeration(
359 sys::USystemTimeZoneType::UCAL_ZONE_TYPE_ANY,
360 None,
361 Some(0), )
363 .expect("time_zone_id_enumeration() opened");
364 let tz_ids = tz_iter
365 .map(|r| r.expect("time zone is available"))
366 .collect::<HashSet<String>>();
367
368 assert!(tz_ids.contains("UTC"));
369 assert!(!tz_ids.contains("Etc/GMT-1"));
370 }
371
372 #[test]
373 fn test_country_time_zones() {
374 let tz_iter = country_time_zones("us").expect("time zones available");
375 assert_eq!(
376 tz_iter
377 .map(|r| { r.expect("time zone is available") })
378 .take(3)
379 .collect::<Vec<String>>(),
380 vec!["AST", "America/Adak", "America/Anchorage"]
381 );
382 }
383
384 #[test]
385 fn test_default_time_zone() {
386 super::set_default_time_zone("America/Adak").expect("time zone set with success");
387 assert_eq!(
388 super::get_default_time_zone().expect("time zone obtained"),
389 "America/Adak",
390 );
391 }
392
393 #[test]
394 fn test_get_tz_data_version() {
395 let re = Regex::new(r"^[0-9][0-9][0-9][0-9][a-z][a-z0-9]*$").expect("valid regex");
396 let tz_version = super::get_tz_data_version().expect("get_tz_data_version works");
397 assert!(re.is_match(&tz_version), "version was: {:?}", &tz_version);
398 }
399
400 #[test]
401 fn test_get_set_millis() -> Result<(), common::Error> {
402 let now = get_now();
403 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
404 assert!((now - cal.get_millis()?).abs() <= 1000f64);
408
409 let arbitrary_delta_ms = 17.0;
410 let date = now + arbitrary_delta_ms;
411 cal.set_millis(date)?;
412 assert_eq!(cal.get_millis()?, date);
413 Ok(())
414 }
415
416 #[test]
417 fn test_set_date() -> Result<(), common::Error> {
418 let time_a = 1588899600000f64;
422 let time_b = 1588640400000f64;
424
425 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
426 cal.set_millis(time_a)?;
427 cal.set_date(2020, UCalendarMonths::UCAL_MAY as i32, 4)?;
428 assert_eq!(cal.get_millis()?, time_b);
429
430 Ok(())
431 }
432
433 #[test]
434 fn test_set_date_time() -> Result<(), common::Error> {
435 let time_a = 1588901215898f64;
439 let time_b = 1588640400898f64;
441
442 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
443 cal.set_millis(time_a)?;
444 cal.set_date_time(2020, UCalendarMonths::UCAL_MAY as i32, 4, 21, 0, 0)?;
445
446 assert_eq!(cal.get_millis()?, time_b);
447
448 Ok(())
449 }
450
451 #[test]
452 fn test_get() -> Result<(), common::Error> {
453 let date_time = 1588901215898f64;
457
458 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
459 cal.set_millis(date_time)?;
460
461 assert_eq!(cal.get(UCalendarDateFields::UCAL_DAY_OF_MONTH)?, 7);
462
463 assert_eq!(cal.get(UCalendarDateFields::UCAL_MILLISECOND)?, 898);
464
465 Ok(())
466 }
467
468 #[test]
469 fn test_offsets_and_daylight_time() -> Result<(), common::Error> {
470 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
471
472 let expected_zone_offset_ms: i32 = -5 * 60 * 60 * 1000;
474 let expected_dst_offset_ms: i32 = 1 * 60 * 60 * 1000;
476
477 cal.set_date_time(2020, UCalendarMonths::UCAL_MAY as i32, 7, 21, 0, 0)?;
478 assert_eq!(cal.get_zone_offset()?, expected_zone_offset_ms);
479 assert_eq!(cal.get_dst_offset()?, expected_dst_offset_ms);
480 assert!(cal.in_daylight_time()?);
481
482 let expected_zone_offset: i32 = -5 * 60 * 60 * 1000;
484 let expected_dst_offset: i32 = 0;
486
487 cal.set_date_time(2020, UCalendarMonths::UCAL_JANUARY as i32, 15, 12, 0, 0)?;
488 assert_eq!(cal.get_zone_offset()?, expected_zone_offset);
489 assert_eq!(cal.get_dst_offset()?, expected_dst_offset);
490 assert!(!cal.in_daylight_time()?);
491
492 Ok(())
493 }
494}