1use std::{cmp, fmt};
4
5use glib::translate::*;
6
7use crate::{DateTime, ffi};
8
9fn validate(
11 tzoffset: Option<f32>,
12 year: i32,
13 month: Option<i32>,
14 day: Option<i32>,
15 hour: Option<i32>,
16 minute: Option<i32>,
17 seconds: Option<f64>,
18) -> Result<(), glib::BoolError> {
19 skip_assert_initialized!();
20
21 if year <= 0 || year > 9999 {
23 return Err(glib::bool_error!(
24 "Can't create DateTime: Year out of range"
25 ));
26 }
27
28 if let Some(month) = month
29 && (month <= 0 || month > 12)
30 {
31 return Err(glib::bool_error!(
32 "Can't create DateTime: Month out of range"
33 ));
34 }
35
36 if let Some(day) = day
37 && (day <= 0 || day > 31)
38 {
39 return Err(glib::bool_error!("Can't create DateTime: Day out of range"));
40 }
41
42 if let Some(hour) = hour
43 && (hour < 0 || hour >= 24)
44 {
45 return Err(glib::bool_error!(
46 "Can't create DateTime: Hour out of range"
47 ));
48 }
49
50 if let Some(minute) = minute
51 && (minute < 0 || minute >= 60)
52 {
53 return Err(glib::bool_error!(
54 "Can't create DateTime: Minute out of range"
55 ));
56 }
57
58 if let Some(seconds) = seconds
59 && (seconds < 0.0 || seconds >= 60.0)
60 {
61 return Err(glib::bool_error!(
62 "Can't create DateTime: Seconds out of range"
63 ));
64 }
65
66 if let Some(tzoffset) = tzoffset
67 && (tzoffset < -12.0 || tzoffset > 12.0)
68 {
69 return Err(glib::bool_error!(
70 "Can't create DateTime: Timezone offset out of range"
71 ));
72 }
73
74 if day.is_some() && month.is_none() {
76 return Err(glib::bool_error!(
77 "Can't create DateTime: Need to provide month if providing day"
78 ));
79 }
80
81 if hour.is_some() && day.is_none() {
83 return Err(glib::bool_error!(
84 "Can't create DateTime: Need to provide day if providing hour"
85 ));
86 }
87
88 if hour.is_none() && minute.is_some() {
90 return Err(glib::bool_error!(
91 "Can't create DateTime: Need to provide both hour and minute or neither"
92 ));
93 }
94
95 if minute.is_some() && hour.is_none() {
96 return Err(glib::bool_error!(
97 "Can't create DateTime: Need to provide both hour and minute or neither"
98 ));
99 }
100
101 if (seconds.is_some() || tzoffset.is_some()) && (hour.is_none() || minute.is_none()) {
103 return Err(glib::bool_error!(
104 "Can't create DateTime: Need to provide hour and minute if providing seconds or timezone offset"
105 ));
106 }
107
108 Ok(())
109}
110
111impl DateTime {
112 #[doc(alias = "gst_date_time_new")]
113 pub fn new(
114 tzoffset: impl Into<Option<f32>>,
115 year: impl Into<i32>,
116 month: impl Into<Option<i32>>,
117 day: impl Into<Option<i32>>,
118 hour: impl Into<Option<i32>>,
119 minute: impl Into<Option<i32>>,
120 seconds: impl Into<Option<f64>>,
121 ) -> Result<DateTime, glib::BoolError> {
122 assert_initialized_main_thread!();
123
124 let tzoffset = tzoffset.into();
125 let year = year.into();
126 let month = month.into();
127 let day = day.into();
128 let hour = hour.into();
129 let minute = minute.into();
130 let seconds = seconds.into();
131
132 validate(tzoffset, year, month, day, hour, minute, seconds)?;
133
134 unsafe {
135 Option::<_>::from_glib_full(ffi::gst_date_time_new(
136 tzoffset.unwrap_or(0.0),
137 year,
138 month.unwrap_or(-1),
139 day.unwrap_or(-1),
140 hour.unwrap_or(-1),
141 minute.unwrap_or(-1),
142 seconds.unwrap_or(-1.0),
143 ))
144 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
145 }
146 }
147
148 #[doc(alias = "gst_date_time_new_local_time")]
149 pub fn from_local_time(
150 year: impl Into<i32>,
151 month: impl Into<Option<i32>>,
152 day: impl Into<Option<i32>>,
153 hour: impl Into<Option<i32>>,
154 minute: impl Into<Option<i32>>,
155 seconds: impl Into<Option<f64>>,
156 ) -> Result<DateTime, glib::BoolError> {
157 assert_initialized_main_thread!();
158
159 let year = year.into();
160 let month = month.into();
161 let day = day.into();
162 let hour = hour.into();
163 let minute = minute.into();
164 let seconds = seconds.into();
165
166 validate(None, year, month, day, hour, minute, seconds)?;
167
168 unsafe {
169 Option::<_>::from_glib_full(ffi::gst_date_time_new_local_time(
170 year,
171 month.unwrap_or(-1),
172 day.unwrap_or(-1),
173 hour.unwrap_or(-1),
174 minute.unwrap_or(-1),
175 seconds.unwrap_or(-1.0),
176 ))
177 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
178 }
179 }
180
181 #[doc(alias = "gst_date_time_new_y")]
182 pub fn from_y(year: i32) -> Result<DateTime, glib::BoolError> {
183 assert_initialized_main_thread!();
184
185 validate(None, year, None, None, None, None, None)?;
186
187 unsafe {
188 Option::<_>::from_glib_full(ffi::gst_date_time_new_y(year))
189 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
190 }
191 }
192
193 #[doc(alias = "gst_date_time_new_ym")]
194 pub fn from_ym(year: i32, month: i32) -> Result<DateTime, glib::BoolError> {
195 assert_initialized_main_thread!();
196
197 validate(None, year, Some(month), None, None, None, None)?;
198
199 unsafe {
200 Option::<_>::from_glib_full(ffi::gst_date_time_new_ym(year, month))
201 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
202 }
203 }
204
205 #[doc(alias = "gst_date_time_new_ymd")]
206 pub fn from_ymd(year: i32, month: i32, day: i32) -> Result<DateTime, glib::BoolError> {
207 assert_initialized_main_thread!();
208
209 validate(None, year, Some(month), Some(day), None, None, None)?;
210
211 unsafe {
212 Option::<_>::from_glib_full(ffi::gst_date_time_new_ymd(year, month, day))
213 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
214 }
215 }
216
217 #[doc(alias = "get_day")]
218 #[doc(alias = "gst_date_time_get_day")]
219 pub fn day(&self) -> Option<i32> {
220 if !self.has_day() {
221 return None;
222 }
223
224 unsafe { Some(ffi::gst_date_time_get_day(self.to_glib_none().0)) }
225 }
226
227 #[doc(alias = "get_hour")]
228 #[doc(alias = "gst_date_time_get_hour")]
229 pub fn hour(&self) -> Option<i32> {
230 if !self.has_time() {
231 return None;
232 }
233
234 unsafe { Some(ffi::gst_date_time_get_hour(self.to_glib_none().0)) }
235 }
236
237 #[doc(alias = "get_microsecond")]
238 #[doc(alias = "gst_date_time_get_microsecond")]
239 pub fn microsecond(&self) -> Option<i32> {
240 if !self.has_second() {
241 return None;
242 }
243
244 unsafe { Some(ffi::gst_date_time_get_microsecond(self.to_glib_none().0)) }
245 }
246
247 #[doc(alias = "get_minute")]
248 #[doc(alias = "gst_date_time_get_minute")]
249 pub fn minute(&self) -> Option<i32> {
250 if !self.has_time() {
251 return None;
252 }
253
254 unsafe { Some(ffi::gst_date_time_get_minute(self.to_glib_none().0)) }
255 }
256
257 #[doc(alias = "get_month")]
258 #[doc(alias = "gst_date_time_get_month")]
259 pub fn month(&self) -> Option<i32> {
260 if !self.has_month() {
261 return None;
262 }
263
264 unsafe { Some(ffi::gst_date_time_get_month(self.to_glib_none().0)) }
265 }
266
267 #[doc(alias = "get_second")]
268 #[doc(alias = "gst_date_time_get_second")]
269 pub fn second(&self) -> Option<i32> {
270 if !self.has_second() {
271 return None;
272 }
273
274 unsafe { Some(ffi::gst_date_time_get_second(self.to_glib_none().0)) }
275 }
276
277 #[doc(alias = "get_time_zone_offset")]
278 #[doc(alias = "gst_date_time_get_time_zone_offset")]
279 pub fn time_zone_offset(&self) -> Option<f32> {
280 if !self.has_time() {
281 return None;
282 }
283
284 unsafe {
285 Some(ffi::gst_date_time_get_time_zone_offset(
286 self.to_glib_none().0,
287 ))
288 }
289 }
290
291 pub fn to_utc(&self) -> Result<DateTime, glib::BoolError> {
292 if !self.has_time() {
293 return Ok(self.clone());
295 }
296
297 assert!(self.has_year() && self.has_month() && self.has_day() && self.has_time());
298
299 if self.has_second() {
303 self.to_g_date_time()
304 .and_then(|d| d.to_utc())
305 .map(|d| d.into())
306 } else {
307 DateTime::new(
312 self.time_zone_offset(),
313 self.year(),
314 self.month(),
315 self.day(),
316 self.hour(),
317 self.minute(),
318 Some(0.0),
319 )
320 .and_then(|d| d.to_g_date_time())
321 .and_then(|d| d.to_utc())
322 .and_then(|d| {
323 DateTime::new(
324 None, d.year(),
326 Some(d.month()),
327 Some(d.day_of_month()),
328 Some(d.hour()),
329 Some(d.minute()),
330 None, )
332 })
333 }
334 }
335}
336
337impl cmp::PartialOrd for DateTime {
338 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
358 #[inline]
359 #[allow(clippy::unnecessary_wraps)]
360 #[doc(alias = "get_cmp")]
361 fn cmp(delta: i32) -> Option<cmp::Ordering> {
362 skip_assert_initialized!();
363 Some(delta.cmp(&0))
364 }
365
366 if !(self.has_year() && other.has_year()) {
367 return None;
369 }
370
371 let (self_norm, other_norm) = if self.has_time() && other.has_time() {
373 (self.to_utc().ok()?, other.to_utc().ok()?)
374 } else {
375 (self.clone(), other.clone())
376 };
377
378 let year_delta = self_norm.year() - other_norm.year();
379 if year_delta != 0 {
380 return cmp(year_delta);
381 }
382
383 if !self.has_month() && !other.has_month() {
386 return cmp(year_delta);
388 }
389
390 if !(self.has_month() && other.has_month()) {
391 return None;
393 }
394
395 let month_delta = self_norm.month().unwrap() - other_norm.month().unwrap();
396 if month_delta != 0 {
397 return cmp(month_delta);
398 }
399
400 if !self.has_day() && !other.has_day() {
403 return Some(cmp::Ordering::Equal);
405 }
406
407 if !(self.has_day() && other.has_day()) {
408 return None;
410 }
411
412 let day_delta = self_norm.day().unwrap() - other_norm.day().unwrap();
413 if day_delta != 0 {
414 return cmp(day_delta);
415 }
416
417 if !self.has_time() && !other.has_time() {
420 return Some(cmp::Ordering::Equal);
422 }
423
424 if !(self.has_time() && other.has_time()) {
425 return None;
427 }
428
429 let hour_delta = self_norm.hour().unwrap() - other_norm.hour().unwrap();
430 if hour_delta != 0 {
431 return cmp(hour_delta);
432 }
433
434 let minute_delta = self_norm.minute().unwrap() - other_norm.minute().unwrap();
435 if minute_delta != 0 {
436 return cmp(minute_delta);
437 }
438
439 if !self.has_second() && !other.has_second() {
442 return Some(cmp::Ordering::Equal);
444 }
445
446 if !(self.has_second() && other.has_second()) {
447 return None;
449 }
450 let second_delta = self_norm.second().unwrap() - other_norm.second().unwrap();
451 if second_delta != 0 {
452 return cmp(second_delta);
453 }
454
455 cmp(self_norm.microsecond().unwrap() - other_norm.microsecond().unwrap())
456 }
457}
458
459impl cmp::PartialEq for DateTime {
460 fn eq(&self, other: &Self) -> bool {
461 self.partial_cmp(other)
462 .map_or_else(|| false, |cmp| cmp == cmp::Ordering::Equal)
463 }
464}
465
466impl fmt::Debug for DateTime {
467 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
468 let mut debug_struct = f.debug_struct("DateTime");
469 if self.has_year() {
470 debug_struct.field("year", &self.year());
471 }
472 if self.has_month() {
473 debug_struct.field("month", &self.month());
474 }
475 if self.has_day() {
476 debug_struct.field("day", &self.day());
477 }
478 if self.has_time() {
479 debug_struct.field("hour", &self.hour());
480 debug_struct.field("minute", &self.minute());
481
482 if self.has_second() {
483 debug_struct.field("second", &self.second());
484 debug_struct.field("microsecond", &self.microsecond());
485 }
486
487 debug_struct.field("tz_offset", &self.time_zone_offset());
488 }
489
490 debug_struct.finish()
491 }
492}
493
494impl fmt::Display for DateTime {
495 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
496 f.write_str(
497 self.to_iso8601_string()
498 .unwrap_or_else(|_| "None".into())
499 .as_str(),
500 )
501 }
502}
503
504impl<'a> From<&'a glib::DateTime> for DateTime {
505 fn from(v: &'a glib::DateTime) -> DateTime {
506 skip_assert_initialized!();
507 DateTime::from_g_date_time(v.clone())
508 }
509}
510
511impl From<glib::DateTime> for DateTime {
512 fn from(v: glib::DateTime) -> DateTime {
513 skip_assert_initialized!();
514 DateTime::from_g_date_time(v)
515 }
516}
517
518impl<'a> TryFrom<&'a DateTime> for glib::DateTime {
519 type Error = glib::BoolError;
520
521 fn try_from(v: &'a DateTime) -> Result<glib::DateTime, glib::BoolError> {
522 skip_assert_initialized!();
523 v.to_g_date_time()
524 }
525}
526
527impl TryFrom<DateTime> for glib::DateTime {
528 type Error = glib::BoolError;
529
530 fn try_from(v: DateTime) -> Result<glib::DateTime, glib::BoolError> {
531 skip_assert_initialized!();
532 v.to_g_date_time()
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[allow(clippy::cognitive_complexity)]
541 #[test]
542 fn test_to_utc() {
543 crate::init().unwrap();
544
545 let utc_date_time = DateTime::new(2f32, 2019, 8, 20, 20, 9, 42.123_456f64)
547 .unwrap()
548 .to_utc()
549 .unwrap();
550 assert_eq!(utc_date_time.year(), 2019);
551 assert_eq!(utc_date_time.month().unwrap(), 8);
552 assert_eq!(utc_date_time.day().unwrap(), 20);
553 assert_eq!(utc_date_time.hour().unwrap(), 18);
554 assert_eq!(utc_date_time.minute().unwrap(), 9);
555 assert_eq!(utc_date_time.second().unwrap(), 42);
556 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
557
558 let utc_date_time = DateTime::new(2f32, 2019, 1, 1, 0, 0, 42.123_456f64)
560 .unwrap()
561 .to_utc()
562 .unwrap();
563 assert_eq!(utc_date_time.year(), 2018);
564 assert_eq!(utc_date_time.month().unwrap(), 12);
565 assert_eq!(utc_date_time.day().unwrap(), 31);
566 assert_eq!(utc_date_time.hour().unwrap(), 22);
567 assert_eq!(utc_date_time.minute().unwrap(), 0);
568 assert_eq!(utc_date_time.second().unwrap(), 42);
569 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
570
571 let utc_date_time = DateTime::from_ymd(2019, 1, 1).unwrap().to_utc().unwrap();
573 assert_eq!(utc_date_time.year(), 2019);
574 assert_eq!(utc_date_time.month().unwrap(), 1);
575 assert_eq!(utc_date_time.day().unwrap(), 1);
576 assert!(!utc_date_time.has_time());
577 assert!(!utc_date_time.has_second());
578
579 let utc_date_time = DateTime::new(2f32, 2018, 5, 28, 16, 6, None)
581 .unwrap()
582 .to_utc()
583 .unwrap();
584 assert_eq!(utc_date_time.year(), 2018);
585 assert_eq!(utc_date_time.month().unwrap(), 5);
586 assert_eq!(utc_date_time.day().unwrap(), 28);
587 assert_eq!(utc_date_time.hour().unwrap(), 14);
588 assert_eq!(utc_date_time.minute().unwrap(), 6);
589 assert!(!utc_date_time.has_second());
590 }
591
592 #[test]
593 fn test_partial_ord() {
594 crate::init().unwrap();
595
596 assert!(
598 DateTime::new(2f32, 2020, 8, 20, 19, 43, 42.123_456f64).unwrap()
599 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
600 );
601
602 assert!(
604 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
605 < DateTime::new(2f32, 2019, 9, 19, 19, 43, 42.123_456f64).unwrap()
606 );
607
608 assert!(
610 DateTime::new(2f32, 2019, 8, 21, 19, 43, 42.123_456f64).unwrap()
611 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
612 );
613
614 assert!(
616 DateTime::new(2f32, 2019, 8, 20, 19, 44, 42.123_456f64).unwrap()
617 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
618 );
619
620 assert!(
622 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
623 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
624 );
625
626 assert!(
628 DateTime::new(2f32, 2019, 8, 20, 19, 43, 43.123_456f64).unwrap()
629 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
630 );
631
632 assert!(
634 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_457f64).unwrap()
635 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
636 );
637
638 assert!(
640 DateTime::new(1f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
641 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
642 );
643
644 assert!(
646 DateTime::new(2f32, 2019, 1, 1, 0, 0, 0f64).unwrap()
647 < DateTime::new(1f32, 2018, 12, 31, 23, 59, 0f64).unwrap()
648 );
649
650 assert!(
652 DateTime::from_ymd(2020, 8, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
653 );
654 assert!(
655 DateTime::from_ymd(2019, 9, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
656 );
657 assert!(
658 DateTime::from_ymd(2019, 8, 21).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
659 );
660
661 assert!(DateTime::from_ym(2020, 8).unwrap() > DateTime::from_ym(2019, 8).unwrap());
662 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ym(2019, 8).unwrap());
663 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap());
664
665 assert!(DateTime::from_y(2020).unwrap() > DateTime::from_y(2019).unwrap());
666 assert!(DateTime::from_ym(2020, 1).unwrap() > DateTime::from_y(2019).unwrap());
667
668 assert!(
669 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
670 < DateTime::from_ymd(2020, 8, 20).unwrap()
671 );
672
673 assert!(
674 DateTime::from_ymd(2020, 8, 20).unwrap()
675 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
676 );
677
678 assert!(
680 DateTime::from_ymd(2020, 1, 1).unwrap()
681 > DateTime::new(-2f32, 2019, 12, 31, 23, 59, 0f64).unwrap()
682 );
683
684 assert!(
688 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
689 .unwrap()
690 .partial_cmp(&DateTime::from_ymd(2019, 8, 20).unwrap())
691 .is_none()
692 );
693
694 assert!(
695 DateTime::from_ymd(2019, 8, 20)
696 .unwrap()
697 .partial_cmp(&DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap())
698 .is_none()
699 );
700
701 assert!(
702 DateTime::from_ym(2019, 1)
703 .unwrap()
704 .partial_cmp(&DateTime::from_y(2019).unwrap())
705 .is_none()
706 );
707 }
708
709 #[test]
710 fn test_eq() {
711 crate::init().unwrap();
712
713 assert_eq!(
714 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap(),
715 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap()
716 );
717
718 assert_eq!(
719 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap(),
720 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap()
721 );
722
723 assert_eq!(
724 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
725 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
726 );
727
728 assert_eq!(
729 DateTime::from_ymd(2018, 5, 28).unwrap(),
730 DateTime::from_ymd(2018, 5, 28).unwrap()
731 );
732
733 assert_ne!(
737 DateTime::from_ymd(2018, 5, 28).unwrap(),
738 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
739 );
740
741 assert_ne!(
742 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
743 DateTime::from_ym(2018, 5).unwrap()
744 );
745
746 assert_ne!(
747 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
748 DateTime::from_y(2018).unwrap()
749 );
750 }
751}