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")]
146 pub fn new(
147 tzoffset: impl Into<Option<f32>>,
148 year: impl Into<i32>,
149 month: impl Into<Option<i32>>,
150 day: impl Into<Option<i32>>,
151 hour: impl Into<Option<i32>>,
152 minute: impl Into<Option<i32>>,
153 seconds: impl Into<Option<f64>>,
154 ) -> Result<DateTime, glib::BoolError> {
155 assert_initialized_main_thread!();
156
157 let tzoffset = tzoffset.into();
158 let year = year.into();
159 let month = month.into();
160 let day = day.into();
161 let hour = hour.into();
162 let minute = minute.into();
163 let seconds = seconds.into();
164
165 validate(tzoffset, year, month, day, hour, minute, seconds)?;
166
167 unsafe {
168 Option::<_>::from_glib_full(ffi::gst_date_time_new(
169 tzoffset.unwrap_or(0.0),
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_local_time")]
182 pub fn from_local_time(
183 year: impl Into<i32>,
184 month: impl Into<Option<i32>>,
185 day: impl Into<Option<i32>>,
186 hour: impl Into<Option<i32>>,
187 minute: impl Into<Option<i32>>,
188 seconds: impl Into<Option<f64>>,
189 ) -> Result<DateTime, glib::BoolError> {
190 assert_initialized_main_thread!();
191
192 let year = year.into();
193 let month = month.into();
194 let day = day.into();
195 let hour = hour.into();
196 let minute = minute.into();
197 let seconds = seconds.into();
198
199 validate(None, year, month, day, hour, minute, seconds)?;
200
201 unsafe {
202 Option::<_>::from_glib_full(ffi::gst_date_time_new_local_time(
203 year,
204 month.unwrap_or(-1),
205 day.unwrap_or(-1),
206 hour.unwrap_or(-1),
207 minute.unwrap_or(-1),
208 seconds.unwrap_or(-1.0),
209 ))
210 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
211 }
212 }
213
214 #[doc(alias = "gst_date_time_new_y")]
215 pub fn from_y(year: i32) -> Result<DateTime, glib::BoolError> {
216 assert_initialized_main_thread!();
217
218 validate(None, year, None, None, None, None, None)?;
219
220 unsafe {
221 Option::<_>::from_glib_full(ffi::gst_date_time_new_y(year))
222 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
223 }
224 }
225
226 #[doc(alias = "gst_date_time_new_ym")]
227 pub fn from_ym(year: i32, month: i32) -> Result<DateTime, glib::BoolError> {
228 assert_initialized_main_thread!();
229
230 validate(None, year, Some(month), None, None, None, None)?;
231
232 unsafe {
233 Option::<_>::from_glib_full(ffi::gst_date_time_new_ym(year, month))
234 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
235 }
236 }
237
238 #[doc(alias = "gst_date_time_new_ymd")]
239 pub fn from_ymd(year: i32, month: i32, day: i32) -> Result<DateTime, glib::BoolError> {
240 assert_initialized_main_thread!();
241
242 validate(None, year, Some(month), Some(day), None, None, None)?;
243
244 unsafe {
245 Option::<_>::from_glib_full(ffi::gst_date_time_new_ymd(year, month, day))
246 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
247 }
248 }
249
250 #[doc(alias = "get_day")]
256 #[doc(alias = "gst_date_time_get_day")]
257 pub fn day(&self) -> Option<i32> {
258 if !self.has_day() {
259 return None;
260 }
261
262 unsafe { Some(ffi::gst_date_time_get_day(self.to_glib_none().0)) }
263 }
264
265 #[doc(alias = "get_hour")]
272 #[doc(alias = "gst_date_time_get_hour")]
273 pub fn hour(&self) -> Option<i32> {
274 if !self.has_time() {
275 return None;
276 }
277
278 unsafe { Some(ffi::gst_date_time_get_hour(self.to_glib_none().0)) }
279 }
280
281 #[doc(alias = "get_microsecond")]
288 #[doc(alias = "gst_date_time_get_microsecond")]
289 pub fn microsecond(&self) -> Option<i32> {
290 if !self.has_second() {
291 return None;
292 }
293
294 unsafe { Some(ffi::gst_date_time_get_microsecond(self.to_glib_none().0)) }
295 }
296
297 #[doc(alias = "get_minute")]
304 #[doc(alias = "gst_date_time_get_minute")]
305 pub fn minute(&self) -> Option<i32> {
306 if !self.has_time() {
307 return None;
308 }
309
310 unsafe { Some(ffi::gst_date_time_get_minute(self.to_glib_none().0)) }
311 }
312
313 #[doc(alias = "get_month")]
319 #[doc(alias = "gst_date_time_get_month")]
320 pub fn month(&self) -> Option<i32> {
321 if !self.has_month() {
322 return None;
323 }
324
325 unsafe { Some(ffi::gst_date_time_get_month(self.to_glib_none().0)) }
326 }
327
328 #[doc(alias = "get_second")]
335 #[doc(alias = "gst_date_time_get_second")]
336 pub fn second(&self) -> Option<i32> {
337 if !self.has_second() {
338 return None;
339 }
340
341 unsafe { Some(ffi::gst_date_time_get_second(self.to_glib_none().0)) }
342 }
343
344 #[doc(alias = "get_time_zone_offset")]
353 #[doc(alias = "gst_date_time_get_time_zone_offset")]
354 pub fn time_zone_offset(&self) -> Option<f32> {
355 if !self.has_time() {
356 return None;
357 }
358
359 unsafe {
360 Some(ffi::gst_date_time_get_time_zone_offset(
361 self.to_glib_none().0,
362 ))
363 }
364 }
365
366 pub fn to_utc(&self) -> Result<DateTime, glib::BoolError> {
367 if !self.has_time() {
368 return Ok(self.clone());
370 }
371
372 assert!(self.has_year() && self.has_month() && self.has_day() && self.has_time());
373
374 if self.has_second() {
378 self.to_g_date_time()
379 .and_then(|d| d.to_utc())
380 .map(|d| d.into())
381 } else {
382 DateTime::new(
387 self.time_zone_offset(),
388 self.year(),
389 self.month(),
390 self.day(),
391 self.hour(),
392 self.minute(),
393 Some(0.0),
394 )
395 .and_then(|d| d.to_g_date_time())
396 .and_then(|d| d.to_utc())
397 .and_then(|d| {
398 DateTime::new(
399 None, d.year(),
401 Some(d.month()),
402 Some(d.day_of_month()),
403 Some(d.hour()),
404 Some(d.minute()),
405 None, )
407 })
408 }
409 }
410}
411
412impl cmp::PartialOrd for DateTime {
413 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
433 #[inline]
434 #[allow(clippy::unnecessary_wraps)]
435 #[doc(alias = "get_cmp")]
436 fn cmp(delta: i32) -> Option<cmp::Ordering> {
437 skip_assert_initialized!();
438 Some(delta.cmp(&0))
439 }
440
441 if !(self.has_year() && other.has_year()) {
442 return None;
444 }
445
446 let (self_norm, other_norm) = if self.has_time() && other.has_time() {
448 (self.to_utc().ok()?, other.to_utc().ok()?)
449 } else {
450 (self.clone(), other.clone())
451 };
452
453 let year_delta = self_norm.year() - other_norm.year();
454 if year_delta != 0 {
455 return cmp(year_delta);
456 }
457
458 if !self.has_month() && !other.has_month() {
461 return cmp(year_delta);
463 }
464
465 if !(self.has_month() && other.has_month()) {
466 return None;
468 }
469
470 let month_delta = self_norm.month().unwrap() - other_norm.month().unwrap();
471 if month_delta != 0 {
472 return cmp(month_delta);
473 }
474
475 if !self.has_day() && !other.has_day() {
478 return Some(cmp::Ordering::Equal);
480 }
481
482 if !(self.has_day() && other.has_day()) {
483 return None;
485 }
486
487 let day_delta = self_norm.day().unwrap() - other_norm.day().unwrap();
488 if day_delta != 0 {
489 return cmp(day_delta);
490 }
491
492 if !self.has_time() && !other.has_time() {
495 return Some(cmp::Ordering::Equal);
497 }
498
499 if !(self.has_time() && other.has_time()) {
500 return None;
502 }
503
504 let hour_delta = self_norm.hour().unwrap() - other_norm.hour().unwrap();
505 if hour_delta != 0 {
506 return cmp(hour_delta);
507 }
508
509 let minute_delta = self_norm.minute().unwrap() - other_norm.minute().unwrap();
510 if minute_delta != 0 {
511 return cmp(minute_delta);
512 }
513
514 if !self.has_second() && !other.has_second() {
517 return Some(cmp::Ordering::Equal);
519 }
520
521 if !(self.has_second() && other.has_second()) {
522 return None;
524 }
525 let second_delta = self_norm.second().unwrap() - other_norm.second().unwrap();
526 if second_delta != 0 {
527 return cmp(second_delta);
528 }
529
530 cmp(self_norm.microsecond().unwrap() - other_norm.microsecond().unwrap())
531 }
532}
533
534impl cmp::PartialEq for DateTime {
535 fn eq(&self, other: &Self) -> bool {
536 self.partial_cmp(other)
537 .map_or_else(|| false, |cmp| cmp == cmp::Ordering::Equal)
538 }
539}
540
541impl fmt::Debug for DateTime {
542 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
543 let mut debug_struct = f.debug_struct("DateTime");
544 if self.has_year() {
545 debug_struct.field("year", &self.year());
546 }
547 if self.has_month() {
548 debug_struct.field("month", &self.month());
549 }
550 if self.has_day() {
551 debug_struct.field("day", &self.day());
552 }
553 if self.has_time() {
554 debug_struct.field("hour", &self.hour());
555 debug_struct.field("minute", &self.minute());
556
557 if self.has_second() {
558 debug_struct.field("second", &self.second());
559 debug_struct.field("microsecond", &self.microsecond());
560 }
561
562 debug_struct.field("tz_offset", &self.time_zone_offset());
563 }
564
565 debug_struct.finish()
566 }
567}
568
569impl fmt::Display for DateTime {
570 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
571 f.write_str(
572 self.to_iso8601_string()
573 .unwrap_or_else(|_| "None".into())
574 .as_str(),
575 )
576 }
577}
578
579impl<'a> From<&'a glib::DateTime> for DateTime {
580 fn from(v: &'a glib::DateTime) -> DateTime {
581 skip_assert_initialized!();
582 DateTime::from_g_date_time(v.clone())
583 }
584}
585
586impl From<glib::DateTime> for DateTime {
587 fn from(v: glib::DateTime) -> DateTime {
588 skip_assert_initialized!();
589 DateTime::from_g_date_time(v)
590 }
591}
592
593impl<'a> TryFrom<&'a DateTime> for glib::DateTime {
594 type Error = glib::BoolError;
595
596 fn try_from(v: &'a DateTime) -> Result<glib::DateTime, glib::BoolError> {
597 skip_assert_initialized!();
598 v.to_g_date_time()
599 }
600}
601
602impl TryFrom<DateTime> for glib::DateTime {
603 type Error = glib::BoolError;
604
605 fn try_from(v: DateTime) -> Result<glib::DateTime, glib::BoolError> {
606 skip_assert_initialized!();
607 v.to_g_date_time()
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614
615 #[allow(clippy::cognitive_complexity)]
616 #[test]
617 fn test_to_utc() {
618 crate::init().unwrap();
619
620 let utc_date_time = DateTime::new(2f32, 2019, 8, 20, 20, 9, 42.123_456f64)
622 .unwrap()
623 .to_utc()
624 .unwrap();
625 assert_eq!(utc_date_time.year(), 2019);
626 assert_eq!(utc_date_time.month().unwrap(), 8);
627 assert_eq!(utc_date_time.day().unwrap(), 20);
628 assert_eq!(utc_date_time.hour().unwrap(), 18);
629 assert_eq!(utc_date_time.minute().unwrap(), 9);
630 assert_eq!(utc_date_time.second().unwrap(), 42);
631 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
632
633 let utc_date_time = DateTime::new(2f32, 2019, 1, 1, 0, 0, 42.123_456f64)
635 .unwrap()
636 .to_utc()
637 .unwrap();
638 assert_eq!(utc_date_time.year(), 2018);
639 assert_eq!(utc_date_time.month().unwrap(), 12);
640 assert_eq!(utc_date_time.day().unwrap(), 31);
641 assert_eq!(utc_date_time.hour().unwrap(), 22);
642 assert_eq!(utc_date_time.minute().unwrap(), 0);
643 assert_eq!(utc_date_time.second().unwrap(), 42);
644 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
645
646 let utc_date_time = DateTime::from_ymd(2019, 1, 1).unwrap().to_utc().unwrap();
648 assert_eq!(utc_date_time.year(), 2019);
649 assert_eq!(utc_date_time.month().unwrap(), 1);
650 assert_eq!(utc_date_time.day().unwrap(), 1);
651 assert!(!utc_date_time.has_time());
652 assert!(!utc_date_time.has_second());
653
654 let utc_date_time = DateTime::new(2f32, 2018, 5, 28, 16, 6, None)
656 .unwrap()
657 .to_utc()
658 .unwrap();
659 assert_eq!(utc_date_time.year(), 2018);
660 assert_eq!(utc_date_time.month().unwrap(), 5);
661 assert_eq!(utc_date_time.day().unwrap(), 28);
662 assert_eq!(utc_date_time.hour().unwrap(), 14);
663 assert_eq!(utc_date_time.minute().unwrap(), 6);
664 assert!(!utc_date_time.has_second());
665 }
666
667 #[test]
668 fn test_partial_ord() {
669 crate::init().unwrap();
670
671 assert!(
673 DateTime::new(2f32, 2020, 8, 20, 19, 43, 42.123_456f64).unwrap()
674 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
675 );
676
677 assert!(
679 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
680 < DateTime::new(2f32, 2019, 9, 19, 19, 43, 42.123_456f64).unwrap()
681 );
682
683 assert!(
685 DateTime::new(2f32, 2019, 8, 21, 19, 43, 42.123_456f64).unwrap()
686 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
687 );
688
689 assert!(
691 DateTime::new(2f32, 2019, 8, 20, 19, 44, 42.123_456f64).unwrap()
692 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
693 );
694
695 assert!(
697 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
698 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
699 );
700
701 assert!(
703 DateTime::new(2f32, 2019, 8, 20, 19, 43, 43.123_456f64).unwrap()
704 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
705 );
706
707 assert!(
709 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_457f64).unwrap()
710 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
711 );
712
713 assert!(
715 DateTime::new(1f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
716 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
717 );
718
719 assert!(
721 DateTime::new(2f32, 2019, 1, 1, 0, 0, 0f64).unwrap()
722 < DateTime::new(1f32, 2018, 12, 31, 23, 59, 0f64).unwrap()
723 );
724
725 assert!(
727 DateTime::from_ymd(2020, 8, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
728 );
729 assert!(
730 DateTime::from_ymd(2019, 9, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
731 );
732 assert!(
733 DateTime::from_ymd(2019, 8, 21).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
734 );
735
736 assert!(DateTime::from_ym(2020, 8).unwrap() > DateTime::from_ym(2019, 8).unwrap());
737 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ym(2019, 8).unwrap());
738 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap());
739
740 assert!(DateTime::from_y(2020).unwrap() > DateTime::from_y(2019).unwrap());
741 assert!(DateTime::from_ym(2020, 1).unwrap() > DateTime::from_y(2019).unwrap());
742
743 assert!(
744 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
745 < DateTime::from_ymd(2020, 8, 20).unwrap()
746 );
747
748 assert!(
749 DateTime::from_ymd(2020, 8, 20).unwrap()
750 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
751 );
752
753 assert!(
755 DateTime::from_ymd(2020, 1, 1).unwrap()
756 > DateTime::new(-2f32, 2019, 12, 31, 23, 59, 0f64).unwrap()
757 );
758
759 assert!(
763 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
764 .unwrap()
765 .partial_cmp(&DateTime::from_ymd(2019, 8, 20).unwrap())
766 .is_none()
767 );
768
769 assert!(
770 DateTime::from_ymd(2019, 8, 20)
771 .unwrap()
772 .partial_cmp(&DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap())
773 .is_none()
774 );
775
776 assert!(
777 DateTime::from_ym(2019, 1)
778 .unwrap()
779 .partial_cmp(&DateTime::from_y(2019).unwrap())
780 .is_none()
781 );
782 }
783
784 #[test]
785 fn test_eq() {
786 crate::init().unwrap();
787
788 assert_eq!(
789 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap(),
790 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap()
791 );
792
793 assert_eq!(
794 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap(),
795 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap()
796 );
797
798 assert_eq!(
799 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
800 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
801 );
802
803 assert_eq!(
804 DateTime::from_ymd(2018, 5, 28).unwrap(),
805 DateTime::from_ymd(2018, 5, 28).unwrap()
806 );
807
808 assert_ne!(
812 DateTime::from_ymd(2018, 5, 28).unwrap(),
813 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
814 );
815
816 assert_ne!(
817 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
818 DateTime::from_ym(2018, 5).unwrap()
819 );
820
821 assert_ne!(
822 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
823 DateTime::from_y(2018).unwrap()
824 );
825 }
826}