1 // Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com> 2 // 3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or 4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license 5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your 6 // option. This file may not be copied, modified, or distributed 7 // except according to those terms. 8 9 use std::cmp; 10 use std::fmt; 11 12 use DateTime; 13 14 impl DateTime { 15 /// Get the [`DateTime`](struct.DateTime.html) in UTC to_utc(&self) -> Result<DateTime, glib::BoolError>16 pub fn to_utc(&self) -> Result<DateTime, glib::BoolError> { 17 if !self.has_time() { 18 // No time => no TZ offset 19 return Ok(self.clone()); 20 } 21 22 assert!(self.has_year() && self.has_month() && self.has_day() && self.has_time()); 23 24 // Can instantiate `gst::DateTime` without seconds using `gst::DateTime::new` 25 // with `-1f64` for the `second` argument 26 // however, the resulting instance can't be translated to `glib::DateTime` 27 if self.has_second() { 28 self.to_g_date_time() 29 .expect("DateTime::to_utc: to_g_date_time") 30 .to_utc() 31 .as_ref() 32 .ok_or_else(|| glib_bool_error!("Can't convert datetime to UTC")) 33 .map(DateTime::new_from_g_date_time) 34 } else { 35 // It would be cheaper to build a `glib::DateTime` direcly, unfortunetaly 36 // this would require using `glib::TimeZone::new_offset` which is feature-gated 37 // to `glib/v2_58`. So we need to build a new `gst::DateTime` with `0f64` 38 // and then discard seconds again 39 DateTime::new( 40 self.get_time_zone_offset(), 41 self.get_year(), 42 self.get_month(), 43 self.get_day(), 44 self.get_hour(), 45 self.get_minute(), 46 0f64, 47 ) 48 .to_g_date_time() 49 .expect("DateTime::to_utc: to_g_date_time") 50 .to_utc() 51 .ok_or_else(|| glib_bool_error!("Can't convert datetime to UTC")) 52 .map(|g_date_time_utc| { 53 DateTime::new( 54 0f32, // UTC TZ offset 55 g_date_time_utc.get_year(), 56 g_date_time_utc.get_month(), 57 g_date_time_utc.get_day_of_month(), 58 g_date_time_utc.get_hour(), 59 g_date_time_utc.get_minute(), 60 -1f64, // No second 61 ) 62 }) 63 } 64 } 65 } 66 67 impl cmp::PartialOrd for DateTime { 68 /// *NOTE 1:* When comparing a partially defined [`DateTime`](struct.DateTime.html) `d1` 69 /// such as *"2019/8/20"* with a [`DateTime`](struct.DateTime.html) with a time part `d2` 70 /// such as *"2019/8/20 21:10"*: 71 /// 72 /// - `d1` includes `d2`, 73 /// - neither `d1` < `d2` nor `d1` > `d2`, 74 /// - and `d1` != `d2`, 75 /// 76 /// so we can only return `None`. 77 /// 78 /// This is the reason why [`DateTime`](struct.DateTime.html) neither implements 79 /// [`Ord`](https://doc.rust-lang.org/nightly/std/cmp/trait.Ord.html) 80 /// nor [`Eq`](https://doc.rust-lang.org/nightly/std/cmp/trait.Eq.html). 81 /// 82 /// *NOTE 2:* When comparing a [`DateTime`](struct.DateTime.html) `d1` without a TZ offset 83 /// such as *"2019/8/20"* with a [`DateTime`](struct.DateTime.html) `d2` with a TZ offset 84 /// such as *"2019/8/20 21:10 +02:00"*, we can't tell in which TZ `d1` is expressed and which 85 /// time should be considered for an offset, therefore the two [`DateTime`s](struct.DateTime.html) 86 /// are compared in the same TZ. partial_cmp(&self, other: &Self) -> Option<cmp::Ordering>87 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { 88 #[inline] 89 fn get_cmp(delta: i32) -> Option<cmp::Ordering> { 90 Some(delta.cmp(&0)) 91 } 92 93 if !(self.has_year() && other.has_year()) { 94 // Can't compare anything 95 return None; 96 } 97 98 // Normalize to UTC only if both members have time (see note 2). 99 let (self_norm, other_norm) = if self.has_time() && other.has_time() { 100 (self.to_utc().ok()?, other.to_utc().ok()?) 101 } else { 102 (self.clone(), other.clone()) 103 }; 104 105 let year_delta = self_norm.get_year() - other_norm.get_year(); 106 if year_delta != 0 { 107 return get_cmp(year_delta); 108 } 109 110 // Same year 111 112 if !self.has_month() && !other.has_month() { 113 // Nothing left to compare 114 return get_cmp(year_delta); 115 } 116 117 if !(self.has_month() && other.has_month()) { 118 // One has month, the other doesn't => can't compare (note 1) 119 return None; 120 } 121 122 let month_delta = self_norm.get_month() - other_norm.get_month(); 123 if month_delta != 0 { 124 return get_cmp(month_delta); 125 } 126 127 // Same year, same month 128 129 if !self.has_day() && !other.has_day() { 130 // Nothing left to compare 131 return Some(cmp::Ordering::Equal); 132 } 133 134 if !(self.has_day() && other.has_day()) { 135 // One has day, the other doesn't => can't compare (note 1) 136 return None; 137 } 138 139 let day_delta = self_norm.get_day() - other_norm.get_day(); 140 if day_delta != 0 { 141 return get_cmp(day_delta); 142 } 143 144 // Same year, same month, same day 145 146 if !self.has_time() && !other.has_time() { 147 // Nothing left to compare 148 return Some(cmp::Ordering::Equal); 149 } 150 151 if !(self.has_time() && other.has_time()) { 152 // One has time, the other doesn't => can't compare (note 1) 153 return None; 154 } 155 156 let hour_delta = self_norm.get_hour() - other_norm.get_hour(); 157 if hour_delta != 0 { 158 return get_cmp(hour_delta); 159 } 160 161 let minute_delta = self_norm.get_minute() - other_norm.get_minute(); 162 if minute_delta != 0 { 163 return get_cmp(minute_delta); 164 } 165 166 // Same year, same month, same day, same time 167 168 if !self.has_second() && !other.has_second() { 169 // Nothing left to compare 170 return Some(cmp::Ordering::Equal); 171 } 172 173 if !(self.has_second() && other.has_second()) { 174 // One has second, the other doesn't => can't compare (note 1) 175 return None; 176 } 177 let second_delta = self_norm.get_second() - other_norm.get_second(); 178 if second_delta != 0 { 179 return get_cmp(second_delta); 180 } 181 182 get_cmp(self_norm.get_microsecond() - other_norm.get_microsecond()) 183 } 184 } 185 186 impl cmp::PartialEq for DateTime { 187 /// See the notes for the [`DateTime` `PartialOrd` trait](struct.DateTime.html#impl-PartialOrd%3CDateTime%3E) eq(&self, other: &Self) -> bool188 fn eq(&self, other: &Self) -> bool { 189 self.partial_cmp(other) 190 .map_or_else(|| false, |cmp| cmp == cmp::Ordering::Equal) 191 } 192 } 193 194 impl fmt::Debug for DateTime { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result195 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 196 let mut debug_struct = f.debug_struct("DateTime"); 197 if self.has_year() { 198 debug_struct.field("year", &self.get_year()); 199 } 200 if self.has_month() { 201 debug_struct.field("month", &self.get_month()); 202 } 203 if self.has_day() { 204 debug_struct.field("day", &self.get_day()); 205 } 206 if self.has_time() { 207 debug_struct.field("hour", &self.get_hour()); 208 debug_struct.field("minute", &self.get_minute()); 209 210 if self.has_second() { 211 debug_struct.field("second", &self.get_second()); 212 debug_struct.field("microsecond", &self.get_microsecond()); 213 } 214 215 debug_struct.field("tz_offset", &self.get_time_zone_offset()); 216 } 217 218 debug_struct.finish() 219 } 220 } 221 222 impl fmt::Display for DateTime { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 224 f.write_str( 225 self.to_iso8601_string() 226 .unwrap_or_else(|_| "None".into()) 227 .as_str(), 228 ) 229 } 230 } 231 232 #[cfg(test)] 233 mod tests { 234 use super::*; 235 236 #[allow(clippy::cognitive_complexity)] 237 #[test] test_to_utc()238 fn test_to_utc() { 239 ::init().unwrap(); 240 241 // Hour offset 242 let utc_date_time = DateTime::new(2f32, 2019, 8, 20, 20, 9, 42.123_456f64) 243 .to_utc() 244 .unwrap(); 245 assert_eq!(utc_date_time.get_year(), 2019); 246 assert_eq!(utc_date_time.get_month(), 8); 247 assert_eq!(utc_date_time.get_day(), 20); 248 assert_eq!(utc_date_time.get_hour(), 18); 249 assert_eq!(utc_date_time.get_minute(), 9); 250 assert_eq!(utc_date_time.get_second(), 42); 251 assert_eq!(utc_date_time.get_microsecond(), 123_456); 252 253 // Year, month, day and hour offset 254 let utc_date_time = DateTime::new(2f32, 2019, 1, 1, 0, 0, 42.123_456f64) 255 .to_utc() 256 .unwrap(); 257 assert_eq!(utc_date_time.get_year(), 2018); 258 assert_eq!(utc_date_time.get_month(), 12); 259 assert_eq!(utc_date_time.get_day(), 31); 260 assert_eq!(utc_date_time.get_hour(), 22); 261 assert_eq!(utc_date_time.get_minute(), 0); 262 assert_eq!(utc_date_time.get_second(), 42); 263 assert_eq!(utc_date_time.get_microsecond(), 123_456); 264 265 // Date without an hour (which implies no TZ) 266 let utc_date_time = DateTime::new_ymd(2019, 1, 1).to_utc().unwrap(); 267 assert_eq!(utc_date_time.get_year(), 2019); 268 assert_eq!(utc_date_time.get_month(), 1); 269 assert_eq!(utc_date_time.get_day(), 1); 270 assert!(!utc_date_time.has_time()); 271 assert!(!utc_date_time.has_second()); 272 273 // Date without seconds 274 let utc_date_time = DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64) 275 .to_utc() 276 .unwrap(); 277 assert_eq!(utc_date_time.get_year(), 2018); 278 assert_eq!(utc_date_time.get_month(), 5); 279 assert_eq!(utc_date_time.get_day(), 28); 280 assert_eq!(utc_date_time.get_hour(), 14); 281 assert_eq!(utc_date_time.get_minute(), 6); 282 assert!(!utc_date_time.has_second()); 283 } 284 285 #[test] test_partial_ord()286 fn test_partial_ord() { 287 ::init().unwrap(); 288 289 // Different years 290 assert!( 291 DateTime::new(2f32, 2020, 8, 20, 19, 43, 42.123_456f64) 292 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 293 ); 294 295 // Different months (order intentionally reversed) 296 assert!( 297 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 298 < DateTime::new(2f32, 2019, 9, 19, 19, 43, 42.123_456f64) 299 ); 300 301 // Different days 302 assert!( 303 DateTime::new(2f32, 2019, 8, 21, 19, 43, 42.123_456f64) 304 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 305 ); 306 307 // Different hours 308 assert!( 309 DateTime::new(2f32, 2019, 8, 20, 19, 44, 42.123_456f64) 310 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 311 ); 312 313 // Different minutes 314 assert!( 315 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64) 316 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 317 ); 318 319 // Different seconds 320 assert!( 321 DateTime::new(2f32, 2019, 8, 20, 19, 43, 43.123_456f64) 322 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 323 ); 324 325 // Different micro-seconds 326 assert!( 327 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_457f64) 328 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 329 ); 330 331 // Different TZ offsets 332 assert!( 333 DateTime::new(1f32, 2019, 8, 20, 19, 43, 42.123_456f64) 334 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64) 335 ); 336 337 // TZ offset leading to year, month, day, hour offset 338 assert!( 339 DateTime::new(2f32, 2019, 1, 1, 0, 0, 0f64) 340 < DateTime::new(1f32, 2018, 12, 31, 23, 59, 0f64) 341 ); 342 343 // Partially defined `DateTime` 344 assert!(DateTime::new_ymd(2020, 8, 20) > DateTime::new_ymd(2019, 8, 20)); 345 assert!(DateTime::new_ymd(2019, 9, 20) > DateTime::new_ymd(2019, 8, 20)); 346 assert!(DateTime::new_ymd(2019, 8, 21) > DateTime::new_ymd(2019, 8, 20)); 347 348 assert!(DateTime::new_ym(2020, 8) > DateTime::new_ym(2019, 8)); 349 assert!(DateTime::new_ym(2019, 9) > DateTime::new_ym(2019, 8)); 350 assert!(DateTime::new_ym(2019, 9) > DateTime::new_ymd(2019, 8, 20)); 351 352 assert!(DateTime::new_y(2020) > DateTime::new_y(2019)); 353 assert!(DateTime::new_ym(2020, 1) > DateTime::new_y(2019)); 354 355 assert!( 356 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64) 357 < DateTime::new_ymd(2020, 8, 20) 358 ); 359 360 assert!( 361 DateTime::new_ymd(2020, 8, 20) 362 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64) 363 ); 364 365 // Comparison occurs on the same TZ when the `DateTime` doesn't have time (note 2) 366 assert!(DateTime::new_ymd(2020, 1, 1) > DateTime::new(-2f32, 2019, 12, 31, 23, 59, 0f64)); 367 368 // In the following cases, the partially defined `DateTime` is a range WRT 369 // the fully defined `DateTime` and this range includes the fully defined `DateTime`, 370 // but we can't tell if it's before or after and they are not equal (note 1) 371 assert!(DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64) 372 .partial_cmp(&DateTime::new_ymd(2019, 8, 20)) 373 .is_none()); 374 375 assert!(DateTime::new_ymd(2019, 8, 20) 376 .partial_cmp(&DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)) 377 .is_none()); 378 379 assert!(DateTime::new_ym(2019, 1) 380 .partial_cmp(&DateTime::new_y(2019)) 381 .is_none()); 382 } 383 384 #[test] test_eq()385 fn test_eq() { 386 ::init().unwrap(); 387 388 assert_eq!( 389 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64), 390 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64) 391 ); 392 393 assert_eq!( 394 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64), 395 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64) 396 ); 397 398 assert_eq!( 399 DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64), 400 DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64) 401 ); 402 403 assert_eq!( 404 DateTime::new_ymd(2018, 5, 28), 405 DateTime::new_ymd(2018, 5, 28) 406 ); 407 408 // In the following cases, the partially defined `DateTime` is a range WRT 409 // the fully defined `DateTime` and this range includes the fully defined `DateTime`, 410 // but they are not equal (note 1) 411 assert_ne!( 412 DateTime::new_ymd(2018, 5, 28), 413 DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64) 414 ); 415 416 assert_ne!( 417 DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64), 418 DateTime::new_ym(2018, 5) 419 ); 420 421 assert_ne!( 422 DateTime::new(2f32, 2018, 5, 28, 16, 6, -1f64), 423 DateTime::new_y(2018) 424 ); 425 } 426 } 427