use super::IcalDuration; use super::IcalTimeZone; use super::TZ_MUTEX; use crate::ical; use crate::utils::dateutil; use chrono::{Date, DateTime, Local, TimeZone, Utc}; use std::ffi::{CStr, CString}; use std::fmt::{Display, Error, Formatter}; use std::ops::{Add, Deref}; use std::str::FromStr; /// Time type /// /// A type representing "time" #[derive(Clone, Debug)] pub struct IcalTime { time: ical::icaltimetype, } impl IcalTime { /// Get an IcalTime object that represents UTC now pub fn utc() -> Self { dateutil::now().into() } /// Get an IcalTime object that represents the current time in the local timezone. pub fn local() -> Self { dateutil::now().with_timezone(&Local).into() } /// Get an IcalTime object that represents a specific day. pub fn floating_ymd(year: i32, month: i32, day: i32) -> Self { let time = ical::icaltimetype { year, month, day, hour: 0, minute: 0, second: 0, is_date: 1, is_daylight: 0, zone: ::std::ptr::null(), }; let time = unsafe { ical::icaltime_normalize(time) }; IcalTime { time } } /// Get an IcalTime object that is the same as the object this function is called on, but with /// hour, minute and second set pub fn and_hms(&self, hour: i32, minute: i32, second: i32) -> Self { let mut time = self.time; time.hour = hour; time.minute = minute; time.second = second; time.is_date = 0; let time = unsafe { ical::icaltime_normalize(time) }; IcalTime { time } } /// Get an IcalTime object based on a timestamp pub fn from_timestamp(timestamp: i64) -> Self { let _lock = TZ_MUTEX.lock(); let utc = IcalTimeZone::utc(); let is_date = 0; let time = unsafe { ical::icaltime_from_timet_with_zone(timestamp, is_date, *utc) }; IcalTime { time } } /// Get the timestamp representation of the IcalTime object pub fn timestamp(&self) -> i64 { let _lock = TZ_MUTEX.lock(); unsafe { ical::icaltime_as_timet_with_zone(self.time, self.time.zone) } } /// Get whether the IcalTime object is a date object pub fn is_date(&self) -> bool { self.time.is_date != 0 } /// Get the IcalTime object as a date object pub fn as_date(&self) -> IcalTime { let mut time = self.time; time.is_date = 1; IcalTime { time } } /// Get the timezone for the IcalTime object pub fn get_timezone(&self) -> Option { if self.time.zone.is_null() { return None; } let tz_ptr = unsafe { ical::icaltime_get_timezone(self.time) }; Some(IcalTimeZone::from_ptr_copy(tz_ptr)) } /// Get a new IcalTime object with a different timezone pub fn with_timezone(&self, timezone: &IcalTimeZone) -> IcalTime { let _lock = TZ_MUTEX.lock(); let mut time = unsafe { ical::icaltime_convert_to_zone(self.time, **timezone) }; //icaltime_convert_to_zone does nothing if is_date == 1 time.zone = **timezone; IcalTime { time } } /// Get a new IcalTime object with the day before the day of the current object pub fn pred(&self) -> IcalTime { let mut time = self.time; time.day -= 1; let time = unsafe { ical::icaltime_normalize(time) }; IcalTime { time } } /// Get a new IcalTime object with the day after the day of the current object pub fn succ(&self) -> IcalTime { let mut time = self.time; time.day += 1; let time = unsafe { ical::icaltime_normalize(time) }; IcalTime { time } } } impl Deref for IcalTime { type Target = ical::icaltimetype; fn deref(&self) -> &ical::icaltimetype { &self.time } } impl Display for IcalTime { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { let cstr = unsafe { CStr::from_ptr(ical::icaltime_as_ical_string(self.time)) }; let string = cstr.to_string_lossy(); write!(f, "{}", string) } } impl FromStr for IcalTime { type Err = String; fn from_str(s: &str) -> Result { unsafe { let c_str = CString::new(s).unwrap(); let time = ical::icaltime_from_string(c_str.as_ptr()); if ical::icaltime_is_null_time(time) == 0 { Ok(IcalTime { time }) } else { Err(format!("Could not parse time {}", s)) } } } } impl PartialEq for IcalTime { fn eq(&self, rhs: &IcalTime) -> bool { let _lock = TZ_MUTEX.lock(); let cmp = unsafe { ical::icaltime_compare(self.time, rhs.time) }; cmp == 0 } } impl Eq for IcalTime {} impl From for IcalTime { fn from(time: ical::icaltimetype) -> IcalTime { IcalTime { time } } } impl Add for IcalTime { type Output = IcalTime; fn add(self, other: IcalDuration) -> IcalTime { let time = unsafe { ical::icaltime_add(self.time, *other) }; IcalTime { time } } } impl + Clone> From<&T> for IcalTime { fn from(time: &T) -> IcalTime { time.clone().into() } } impl From> for IcalTime { fn from(time: DateTime) -> IcalTime { let timestamp = time.timestamp(); let local = IcalTimeZone::local(); IcalTime::from_timestamp(timestamp).with_timezone(&local) } } impl From> for IcalTime { fn from(time: DateTime) -> IcalTime { let timestamp = time.timestamp(); IcalTime::from_timestamp(timestamp) } } impl From> for IcalTime { fn from(date: Date) -> IcalTime { let timestamp = date.with_timezone(&Utc).and_hms(0, 0, 0).timestamp(); let timezone = IcalTimeZone::local(); IcalTime::from_timestamp(timestamp) .with_timezone(&timezone) .as_date() } } impl From> for IcalTime { fn from(date: Date) -> IcalTime { let timestamp = date.and_hms(0, 0, 0).timestamp(); IcalTime::from_timestamp(timestamp).as_date() } } impl From for Date { fn from(time: IcalTime) -> Date { Local.timestamp(time.timestamp(), 0).date() } } impl From for DateTime { fn from(time: IcalTime) -> DateTime { Local.timestamp(time.timestamp(), 0) } } impl From for Date { fn from(time: IcalTime) -> Date { Utc.timestamp(time.timestamp(), 0).date() } } impl From for DateTime { fn from(time: IcalTime) -> DateTime { Utc.timestamp(time.timestamp(), 0) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse() { let time = "20130101T010203Z".parse::().unwrap(); assert_eq!("20130101T010203Z", time.to_string()); } #[test] fn test_parse_negative() { let time = "201XXX01T010203Z".parse::(); assert!(time.is_err()); } #[test] fn test_with_timezone() { let utc = IcalTime::utc(); let tz = IcalTimeZone::from_name("US/Eastern").unwrap(); let time = utc.with_timezone(&tz); assert_eq!("US/Eastern", time.get_timezone().unwrap().get_name()); assert_eq!("20121231T200203", time.to_string()); assert_eq!(1357002123, time.timestamp()); } #[test] fn test_get_timezone_negative() { let time = IcalTime::floating_ymd(2018, 02, 03); assert!(time.get_timezone().is_none()); } #[test] fn test_from_utc_date() { let utc_date = Utc.ymd(2014, 01, 01); let time = IcalTime::from(utc_date); assert_eq!("UTC", time.get_timezone().unwrap().get_name()); assert_eq!("20140101", time.to_string()); } #[test] fn test_into_utc_date() { let time = IcalTimeZone::utc().ymd(2014, 02, 02); let date: Date = time.into(); assert_eq!(Utc.ymd(2014, 02, 02), date); } #[test] fn test_into_utc_datetime() { let time = IcalTimeZone::utc().ymd(2014, 02, 02).and_hms(13, 37, 00); let datetime: DateTime = time.into(); assert_eq!(Utc.ymd(2014, 02, 02).and_hms(13, 37, 00), datetime); } #[test] fn test_add() { let now = IcalTime::utc(); let duration = IcalDuration::from_seconds(123); let sum = now + duration; assert_eq!(1357002123 + 123, sum.timestamp()); } #[test] fn test_pred() { let time = IcalTime::utc(); assert_eq!("20121231T010203Z", time.pred().to_string()); } #[test] fn test_succ() { let time = IcalTime::utc(); assert_eq!("20130102T010203Z", time.succ().to_string()); } #[test] fn test_invalid_month() { let time = IcalTime::floating_ymd(2000, 13, 1); assert_eq!("20010101", time.to_string()); } #[test] fn test_invalid_day() { let time = IcalTime::floating_ymd(2000, 12, 32); assert_eq!("20010101", time.to_string()); } #[test] fn test_invalid_hour() { let time = IcalTime::floating_ymd(2000, 12, 31).and_hms(25, 0, 0); assert_eq!("20010101T010000", time.to_string()); } #[test] fn test_invalid_minute() { let time = IcalTime::floating_ymd(2000, 12, 31).and_hms(24, 61, 0); assert_eq!("20010101T010100", time.to_string()); } #[test] fn test_invalid_second() { let time = IcalTime::floating_ymd(2000, 12, 31).and_hms(24, 60, 61); assert_eq!("20010101T010101", time.to_string()); } }