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 status = common::Error::OK_CODE;
222
223 let time_zone_length = unsafe {
225 assert!(common::Error::is_ok(status));
226 versioned_function!(ucal_getDefaultTimeZone)(std::ptr::null_mut(), 0, &mut status)
227 } as usize;
228 common::Error::ok_preflight(status)?;
229
230 let mut status = common::Error::OK_CODE;
232 let mut uchar = ustring::UChar::new_with_capacity(time_zone_length);
233 trace!("length: {}", time_zone_length);
234
235 unsafe {
237 assert!(common::Error::is_ok(status));
238 versioned_function!(ucal_getDefaultTimeZone)(
239 uchar.as_mut_c_ptr(),
240 time_zone_length as i32,
241 &mut status,
242 )
243 };
244 common::Error::ok_or_warning(status)?;
245 trace!("result: {:?}", uchar);
246 String::try_from(&uchar)
247}
248
249pub fn get_tz_data_version() -> Result<String, common::Error> {
251 let mut status = common::Error::OK_CODE;
252
253 let tz_data_version = unsafe {
254 let raw_cstring = versioned_function!(ucal_getTZDataVersion)(&mut status);
255 common::Error::ok_or_warning(status)?;
256 ffi::CStr::from_ptr(raw_cstring)
257 .to_string_lossy()
258 .into_owned()
259 };
260
261 Ok(tz_data_version)
262}
263
264pub fn get_now() -> f64 {
268 unsafe { versioned_function!(ucal_getNow)() as f64 }
269}
270
271pub fn country_time_zones(country: &str) -> Result<uenum::Enumeration, common::Error> {
275 uenum::ucal_open_country_time_zones(country)
276}
277
278pub fn time_zone_id_enumeration(
282 zone_type: sys::USystemTimeZoneType,
283 region: Option<&str>,
284 raw_offset: Option<i32>,
285) -> Result<uenum::Enumeration, common::Error> {
286 uenum::ucal_open_time_zone_id_enumeration(zone_type, region, raw_offset)
287}
288
289pub fn time_zones() -> Result<uenum::Enumeration, common::Error> {
293 uenum::open_time_zones()
294}
295
296#[cfg(test)]
297mod tests {
298 use {
299 super::{UCalendar, *},
300 regex::Regex,
301 std::collections::HashSet,
302 };
303
304 #[test]
305 fn test_time_zones() {
306 let tz_iter = time_zones().expect("time zones opened");
307 assert_eq!(
308 tz_iter
309 .map(|r| { r.expect("time zone is available") })
310 .take(3)
311 .collect::<Vec<String>>(),
312 vec!["ACT", "AET", "AGT"]
313 );
314 }
315
316 #[test]
317 fn test_time_zone_id_enumeration_no_filters() {
318 let tz_iter =
319 time_zone_id_enumeration(sys::USystemTimeZoneType::UCAL_ZONE_TYPE_ANY, None, None)
320 .expect("time_zone_id_enumeration() opened");
321
322 let from_enumeration = tz_iter
323 .map(|r| r.expect("timezone is available"))
324 .collect::<Vec<String>>();
325
326 let from_time_zones = time_zones()
327 .expect("time_zones() opened")
328 .map(|r| r.expect("time zone is available"))
329 .collect::<Vec<String>>();
330
331 assert!(!from_time_zones.is_empty());
332
333 assert_eq!(from_enumeration, from_time_zones);
334 }
335
336 #[test]
337 fn test_time_zone_id_enumeration_by_type_region() {
338 let tz_iter = time_zone_id_enumeration(
339 sys::USystemTimeZoneType::UCAL_ZONE_TYPE_CANONICAL,
340 Some("us"),
341 None,
342 )
343 .expect("time_zone_id_enumeration() opened");
344 assert_eq!(
345 tz_iter
346 .map(|r| { r.expect("time zone is available") })
347 .take(3)
348 .collect::<Vec<String>>(),
349 vec!["America/Adak", "America/Anchorage", "America/Boise"]
350 );
351 }
352
353 #[test]
354 fn test_time_zone_id_enumeration_by_offset() {
355 let tz_iter = time_zone_id_enumeration(
356 sys::USystemTimeZoneType::UCAL_ZONE_TYPE_ANY,
357 None,
358 Some(0), )
360 .expect("time_zone_id_enumeration() opened");
361 let tz_ids = tz_iter
362 .map(|r| r.expect("time zone is available"))
363 .collect::<HashSet<String>>();
364
365 assert!(tz_ids.contains("UTC"));
366 assert!(!tz_ids.contains("Etc/GMT-1"));
367 }
368
369 #[test]
370 fn test_country_time_zones() {
371 let tz_iter = country_time_zones("us").expect("time zones available");
372 assert_eq!(
373 tz_iter
374 .map(|r| { r.expect("time zone is available") })
375 .take(3)
376 .collect::<Vec<String>>(),
377 vec!["AST", "America/Adak", "America/Anchorage"]
378 );
379 }
380
381 #[test]
382 fn test_default_time_zone() {
383 super::set_default_time_zone("America/Adak").expect("time zone set with success");
384 assert_eq!(
385 super::get_default_time_zone().expect("time zone obtained"),
386 "America/Adak",
387 );
388 }
389
390 #[test]
391 fn test_get_tz_data_version() {
392 let re = Regex::new(r"^[0-9][0-9][0-9][0-9][a-z][a-z0-9]*$").expect("valid regex");
393 let tz_version = super::get_tz_data_version().expect("get_tz_data_version works");
394 assert!(re.is_match(&tz_version), "version was: {:?}", &tz_version);
395 }
396
397 #[test]
398 fn test_get_set_millis() -> Result<(), common::Error> {
399 let now = get_now();
400 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
401 assert!((now - cal.get_millis()?).abs() <= 1000f64);
405
406 let arbitrary_delta_ms = 17.0;
407 let date = now + arbitrary_delta_ms;
408 cal.set_millis(date)?;
409 assert_eq!(cal.get_millis()?, date);
410 Ok(())
411 }
412
413 #[test]
414 fn test_set_date() -> Result<(), common::Error> {
415 let time_a = 1588899600000f64;
419 let time_b = 1588640400000f64;
421
422 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
423 cal.set_millis(time_a)?;
424 cal.set_date(2020, UCalendarMonths::UCAL_MAY as i32, 4)?;
425 assert_eq!(cal.get_millis()?, time_b);
426
427 Ok(())
428 }
429
430 #[test]
431 fn test_set_date_time() -> Result<(), common::Error> {
432 let time_a = 1588901215898f64;
436 let time_b = 1588640400898f64;
438
439 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
440 cal.set_millis(time_a)?;
441 cal.set_date_time(2020, UCalendarMonths::UCAL_MAY as i32, 4, 21, 0, 0)?;
442
443 assert_eq!(cal.get_millis()?, time_b);
444
445 Ok(())
446 }
447
448 #[test]
449 fn test_get() -> Result<(), common::Error> {
450 let date_time = 1588901215898f64;
454
455 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
456 cal.set_millis(date_time)?;
457
458 assert_eq!(cal.get(UCalendarDateFields::UCAL_DAY_OF_MONTH)?, 7);
459
460 assert_eq!(cal.get(UCalendarDateFields::UCAL_MILLISECOND)?, 898);
461
462 Ok(())
463 }
464
465 #[test]
466 fn test_offsets_and_daylight_time() -> Result<(), common::Error> {
467 let mut cal = UCalendar::new("America/New_York", "en-US", UCalendarType::UCAL_GREGORIAN)?;
468
469 let expected_zone_offset_ms: i32 = -5 * 60 * 60 * 1000;
471 let expected_dst_offset_ms: i32 = 1 * 60 * 60 * 1000;
473
474 cal.set_date_time(2020, UCalendarMonths::UCAL_MAY as i32, 7, 21, 0, 0)?;
475 assert_eq!(cal.get_zone_offset()?, expected_zone_offset_ms);
476 assert_eq!(cal.get_dst_offset()?, expected_dst_offset_ms);
477 assert!(cal.in_daylight_time()?);
478
479 let expected_zone_offset: i32 = -5 * 60 * 60 * 1000;
481 let expected_dst_offset: i32 = 0;
483
484 cal.set_date_time(2020, UCalendarMonths::UCAL_JANUARY as i32, 15, 12, 0, 0)?;
485 assert_eq!(cal.get_zone_offset()?, expected_zone_offset);
486 assert_eq!(cal.get_dst_offset()?, expected_dst_offset);
487 assert!(!cal.in_daylight_time()?);
488
489 Ok(())
490 }
491}