1use std::{cmp, fmt};
4
5use glib::translate::*;
6
7use crate::{ffi, DateTime};
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 if month <= 0 || month > 12 {
30 return Err(glib::bool_error!(
31 "Can't create DateTime: Month out of range"
32 ));
33 }
34 }
35
36 if let Some(day) = day {
37 if day <= 0 || day > 31 {
38 return Err(glib::bool_error!("Can't create DateTime: Day out of range"));
39 }
40 }
41
42 if let Some(hour) = hour {
43 if hour < 0 || hour >= 24 {
44 return Err(glib::bool_error!(
45 "Can't create DateTime: Hour out of range"
46 ));
47 }
48 }
49
50 if let Some(minute) = minute {
51 if minute < 0 || minute >= 60 {
52 return Err(glib::bool_error!(
53 "Can't create DateTime: Minute out of range"
54 ));
55 }
56 }
57
58 if let Some(seconds) = seconds {
59 if seconds < 0.0 || seconds >= 60.0 {
60 return Err(glib::bool_error!(
61 "Can't create DateTime: Seconds out of range"
62 ));
63 }
64 }
65
66 if let Some(tzoffset) = tzoffset {
67 if tzoffset < -12.0 || tzoffset > 12.0 {
68 return Err(glib::bool_error!(
69 "Can't create DateTime: Timezone offset out of range"
70 ));
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!("Can't create DateTime: Need to provide hour and minute if providing seconds or timezone offset"));
104 }
105
106 Ok(())
107}
108
109impl DateTime {
110 #[doc(alias = "gst_date_time_new")]
144 pub fn new(
145 tzoffset: impl Into<Option<f32>>,
146 year: impl Into<i32>,
147 month: impl Into<Option<i32>>,
148 day: impl Into<Option<i32>>,
149 hour: impl Into<Option<i32>>,
150 minute: impl Into<Option<i32>>,
151 seconds: impl Into<Option<f64>>,
152 ) -> Result<DateTime, glib::BoolError> {
153 assert_initialized_main_thread!();
154
155 let tzoffset = tzoffset.into();
156 let year = year.into();
157 let month = month.into();
158 let day = day.into();
159 let hour = hour.into();
160 let minute = minute.into();
161 let seconds = seconds.into();
162
163 validate(tzoffset, year, month, day, hour, minute, seconds)?;
164
165 unsafe {
166 Option::<_>::from_glib_full(ffi::gst_date_time_new(
167 tzoffset.unwrap_or(0.0),
168 year,
169 month.unwrap_or(-1),
170 day.unwrap_or(-1),
171 hour.unwrap_or(-1),
172 minute.unwrap_or(-1),
173 seconds.unwrap_or(-1.0),
174 ))
175 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
176 }
177 }
178
179 #[doc(alias = "gst_date_time_new_local_time")]
180 pub fn from_local_time(
181 year: impl Into<i32>,
182 month: impl Into<Option<i32>>,
183 day: impl Into<Option<i32>>,
184 hour: impl Into<Option<i32>>,
185 minute: impl Into<Option<i32>>,
186 seconds: impl Into<Option<f64>>,
187 ) -> Result<DateTime, glib::BoolError> {
188 assert_initialized_main_thread!();
189
190 let year = year.into();
191 let month = month.into();
192 let day = day.into();
193 let hour = hour.into();
194 let minute = minute.into();
195 let seconds = seconds.into();
196
197 validate(None, year, month, day, hour, minute, seconds)?;
198
199 unsafe {
200 Option::<_>::from_glib_full(ffi::gst_date_time_new_local_time(
201 year,
202 month.unwrap_or(-1),
203 day.unwrap_or(-1),
204 hour.unwrap_or(-1),
205 minute.unwrap_or(-1),
206 seconds.unwrap_or(-1.0),
207 ))
208 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
209 }
210 }
211
212 #[doc(alias = "gst_date_time_new_y")]
213 pub fn from_y(year: i32) -> Result<DateTime, glib::BoolError> {
214 assert_initialized_main_thread!();
215
216 validate(None, year, None, None, None, None, None)?;
217
218 unsafe {
219 Option::<_>::from_glib_full(ffi::gst_date_time_new_y(year))
220 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
221 }
222 }
223
224 #[doc(alias = "gst_date_time_new_ym")]
225 pub fn from_ym(year: i32, month: i32) -> Result<DateTime, glib::BoolError> {
226 assert_initialized_main_thread!();
227
228 validate(None, year, Some(month), None, None, None, None)?;
229
230 unsafe {
231 Option::<_>::from_glib_full(ffi::gst_date_time_new_ym(year, month))
232 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
233 }
234 }
235
236 #[doc(alias = "gst_date_time_new_ymd")]
237 pub fn from_ymd(year: i32, month: i32, day: i32) -> Result<DateTime, glib::BoolError> {
238 assert_initialized_main_thread!();
239
240 validate(None, year, Some(month), Some(day), None, None, None)?;
241
242 unsafe {
243 Option::<_>::from_glib_full(ffi::gst_date_time_new_ymd(year, month, day))
244 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
245 }
246 }
247
248 #[doc(alias = "get_day")]
254 #[doc(alias = "gst_date_time_get_day")]
255 pub fn day(&self) -> Option<i32> {
256 if !self.has_day() {
257 return None;
258 }
259
260 unsafe { Some(ffi::gst_date_time_get_day(self.to_glib_none().0)) }
261 }
262
263 #[doc(alias = "get_hour")]
270 #[doc(alias = "gst_date_time_get_hour")]
271 pub fn hour(&self) -> Option<i32> {
272 if !self.has_time() {
273 return None;
274 }
275
276 unsafe { Some(ffi::gst_date_time_get_hour(self.to_glib_none().0)) }
277 }
278
279 #[doc(alias = "get_microsecond")]
286 #[doc(alias = "gst_date_time_get_microsecond")]
287 pub fn microsecond(&self) -> Option<i32> {
288 if !self.has_second() {
289 return None;
290 }
291
292 unsafe { Some(ffi::gst_date_time_get_microsecond(self.to_glib_none().0)) }
293 }
294
295 #[doc(alias = "get_minute")]
302 #[doc(alias = "gst_date_time_get_minute")]
303 pub fn minute(&self) -> Option<i32> {
304 if !self.has_time() {
305 return None;
306 }
307
308 unsafe { Some(ffi::gst_date_time_get_minute(self.to_glib_none().0)) }
309 }
310
311 #[doc(alias = "get_month")]
317 #[doc(alias = "gst_date_time_get_month")]
318 pub fn month(&self) -> Option<i32> {
319 if !self.has_month() {
320 return None;
321 }
322
323 unsafe { Some(ffi::gst_date_time_get_month(self.to_glib_none().0)) }
324 }
325
326 #[doc(alias = "get_second")]
333 #[doc(alias = "gst_date_time_get_second")]
334 pub fn second(&self) -> Option<i32> {
335 if !self.has_second() {
336 return None;
337 }
338
339 unsafe { Some(ffi::gst_date_time_get_second(self.to_glib_none().0)) }
340 }
341
342 #[doc(alias = "get_time_zone_offset")]
351 #[doc(alias = "gst_date_time_get_time_zone_offset")]
352 pub fn time_zone_offset(&self) -> Option<f32> {
353 if !self.has_time() {
354 return None;
355 }
356
357 unsafe {
358 Some(ffi::gst_date_time_get_time_zone_offset(
359 self.to_glib_none().0,
360 ))
361 }
362 }
363
364 pub fn to_utc(&self) -> Result<DateTime, glib::BoolError> {
365 if !self.has_time() {
366 return Ok(self.clone());
368 }
369
370 assert!(self.has_year() && self.has_month() && self.has_day() && self.has_time());
371
372 if self.has_second() {
376 self.to_g_date_time()
377 .and_then(|d| d.to_utc())
378 .map(|d| d.into())
379 } else {
380 DateTime::new(
385 self.time_zone_offset(),
386 self.year(),
387 self.month(),
388 self.day(),
389 self.hour(),
390 self.minute(),
391 Some(0.0),
392 )
393 .and_then(|d| d.to_g_date_time())
394 .and_then(|d| d.to_utc())
395 .and_then(|d| {
396 DateTime::new(
397 None, d.year(),
399 Some(d.month()),
400 Some(d.day_of_month()),
401 Some(d.hour()),
402 Some(d.minute()),
403 None, )
405 })
406 }
407 }
408}
409
410impl cmp::PartialOrd for DateTime {
411 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
431 #[inline]
432 #[allow(clippy::unnecessary_wraps)]
433 #[doc(alias = "get_cmp")]
434 fn cmp(delta: i32) -> Option<cmp::Ordering> {
435 skip_assert_initialized!();
436 Some(delta.cmp(&0))
437 }
438
439 if !(self.has_year() && other.has_year()) {
440 return None;
442 }
443
444 let (self_norm, other_norm) = if self.has_time() && other.has_time() {
446 (self.to_utc().ok()?, other.to_utc().ok()?)
447 } else {
448 (self.clone(), other.clone())
449 };
450
451 let year_delta = self_norm.year() - other_norm.year();
452 if year_delta != 0 {
453 return cmp(year_delta);
454 }
455
456 if !self.has_month() && !other.has_month() {
459 return cmp(year_delta);
461 }
462
463 if !(self.has_month() && other.has_month()) {
464 return None;
466 }
467
468 let month_delta = self_norm.month().unwrap() - other_norm.month().unwrap();
469 if month_delta != 0 {
470 return cmp(month_delta);
471 }
472
473 if !self.has_day() && !other.has_day() {
476 return Some(cmp::Ordering::Equal);
478 }
479
480 if !(self.has_day() && other.has_day()) {
481 return None;
483 }
484
485 let day_delta = self_norm.day().unwrap() - other_norm.day().unwrap();
486 if day_delta != 0 {
487 return cmp(day_delta);
488 }
489
490 if !self.has_time() && !other.has_time() {
493 return Some(cmp::Ordering::Equal);
495 }
496
497 if !(self.has_time() && other.has_time()) {
498 return None;
500 }
501
502 let hour_delta = self_norm.hour().unwrap() - other_norm.hour().unwrap();
503 if hour_delta != 0 {
504 return cmp(hour_delta);
505 }
506
507 let minute_delta = self_norm.minute().unwrap() - other_norm.minute().unwrap();
508 if minute_delta != 0 {
509 return cmp(minute_delta);
510 }
511
512 if !self.has_second() && !other.has_second() {
515 return Some(cmp::Ordering::Equal);
517 }
518
519 if !(self.has_second() && other.has_second()) {
520 return None;
522 }
523 let second_delta = self_norm.second().unwrap() - other_norm.second().unwrap();
524 if second_delta != 0 {
525 return cmp(second_delta);
526 }
527
528 cmp(self_norm.microsecond().unwrap() - other_norm.microsecond().unwrap())
529 }
530}
531
532impl cmp::PartialEq for DateTime {
533 fn eq(&self, other: &Self) -> bool {
534 self.partial_cmp(other)
535 .map_or_else(|| false, |cmp| cmp == cmp::Ordering::Equal)
536 }
537}
538
539impl fmt::Debug for DateTime {
540 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
541 let mut debug_struct = f.debug_struct("DateTime");
542 if self.has_year() {
543 debug_struct.field("year", &self.year());
544 }
545 if self.has_month() {
546 debug_struct.field("month", &self.month());
547 }
548 if self.has_day() {
549 debug_struct.field("day", &self.day());
550 }
551 if self.has_time() {
552 debug_struct.field("hour", &self.hour());
553 debug_struct.field("minute", &self.minute());
554
555 if self.has_second() {
556 debug_struct.field("second", &self.second());
557 debug_struct.field("microsecond", &self.microsecond());
558 }
559
560 debug_struct.field("tz_offset", &self.time_zone_offset());
561 }
562
563 debug_struct.finish()
564 }
565}
566
567impl fmt::Display for DateTime {
568 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
569 f.write_str(
570 self.to_iso8601_string()
571 .unwrap_or_else(|_| "None".into())
572 .as_str(),
573 )
574 }
575}
576
577impl<'a> From<&'a glib::DateTime> for DateTime {
578 fn from(v: &'a glib::DateTime) -> DateTime {
579 skip_assert_initialized!();
580 DateTime::from_g_date_time(v.clone())
581 }
582}
583
584impl From<glib::DateTime> for DateTime {
585 fn from(v: glib::DateTime) -> DateTime {
586 skip_assert_initialized!();
587 DateTime::from_g_date_time(v)
588 }
589}
590
591impl<'a> TryFrom<&'a DateTime> for glib::DateTime {
592 type Error = glib::BoolError;
593
594 fn try_from(v: &'a DateTime) -> Result<glib::DateTime, glib::BoolError> {
595 skip_assert_initialized!();
596 v.to_g_date_time()
597 }
598}
599
600impl TryFrom<DateTime> for glib::DateTime {
601 type Error = glib::BoolError;
602
603 fn try_from(v: DateTime) -> Result<glib::DateTime, glib::BoolError> {
604 skip_assert_initialized!();
605 v.to_g_date_time()
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612
613 #[allow(clippy::cognitive_complexity)]
614 #[test]
615 fn test_to_utc() {
616 crate::init().unwrap();
617
618 let utc_date_time = DateTime::new(2f32, 2019, 8, 20, 20, 9, 42.123_456f64)
620 .unwrap()
621 .to_utc()
622 .unwrap();
623 assert_eq!(utc_date_time.year(), 2019);
624 assert_eq!(utc_date_time.month().unwrap(), 8);
625 assert_eq!(utc_date_time.day().unwrap(), 20);
626 assert_eq!(utc_date_time.hour().unwrap(), 18);
627 assert_eq!(utc_date_time.minute().unwrap(), 9);
628 assert_eq!(utc_date_time.second().unwrap(), 42);
629 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
630
631 let utc_date_time = DateTime::new(2f32, 2019, 1, 1, 0, 0, 42.123_456f64)
633 .unwrap()
634 .to_utc()
635 .unwrap();
636 assert_eq!(utc_date_time.year(), 2018);
637 assert_eq!(utc_date_time.month().unwrap(), 12);
638 assert_eq!(utc_date_time.day().unwrap(), 31);
639 assert_eq!(utc_date_time.hour().unwrap(), 22);
640 assert_eq!(utc_date_time.minute().unwrap(), 0);
641 assert_eq!(utc_date_time.second().unwrap(), 42);
642 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
643
644 let utc_date_time = DateTime::from_ymd(2019, 1, 1).unwrap().to_utc().unwrap();
646 assert_eq!(utc_date_time.year(), 2019);
647 assert_eq!(utc_date_time.month().unwrap(), 1);
648 assert_eq!(utc_date_time.day().unwrap(), 1);
649 assert!(!utc_date_time.has_time());
650 assert!(!utc_date_time.has_second());
651
652 let utc_date_time = DateTime::new(2f32, 2018, 5, 28, 16, 6, None)
654 .unwrap()
655 .to_utc()
656 .unwrap();
657 assert_eq!(utc_date_time.year(), 2018);
658 assert_eq!(utc_date_time.month().unwrap(), 5);
659 assert_eq!(utc_date_time.day().unwrap(), 28);
660 assert_eq!(utc_date_time.hour().unwrap(), 14);
661 assert_eq!(utc_date_time.minute().unwrap(), 6);
662 assert!(!utc_date_time.has_second());
663 }
664
665 #[test]
666 fn test_partial_ord() {
667 crate::init().unwrap();
668
669 assert!(
671 DateTime::new(2f32, 2020, 8, 20, 19, 43, 42.123_456f64).unwrap()
672 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
673 );
674
675 assert!(
677 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
678 < DateTime::new(2f32, 2019, 9, 19, 19, 43, 42.123_456f64).unwrap()
679 );
680
681 assert!(
683 DateTime::new(2f32, 2019, 8, 21, 19, 43, 42.123_456f64).unwrap()
684 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
685 );
686
687 assert!(
689 DateTime::new(2f32, 2019, 8, 20, 19, 44, 42.123_456f64).unwrap()
690 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
691 );
692
693 assert!(
695 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
696 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
697 );
698
699 assert!(
701 DateTime::new(2f32, 2019, 8, 20, 19, 43, 43.123_456f64).unwrap()
702 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
703 );
704
705 assert!(
707 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_457f64).unwrap()
708 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
709 );
710
711 assert!(
713 DateTime::new(1f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
714 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
715 );
716
717 assert!(
719 DateTime::new(2f32, 2019, 1, 1, 0, 0, 0f64).unwrap()
720 < DateTime::new(1f32, 2018, 12, 31, 23, 59, 0f64).unwrap()
721 );
722
723 assert!(
725 DateTime::from_ymd(2020, 8, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
726 );
727 assert!(
728 DateTime::from_ymd(2019, 9, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
729 );
730 assert!(
731 DateTime::from_ymd(2019, 8, 21).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
732 );
733
734 assert!(DateTime::from_ym(2020, 8).unwrap() > DateTime::from_ym(2019, 8).unwrap());
735 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ym(2019, 8).unwrap());
736 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap());
737
738 assert!(DateTime::from_y(2020).unwrap() > DateTime::from_y(2019).unwrap());
739 assert!(DateTime::from_ym(2020, 1).unwrap() > DateTime::from_y(2019).unwrap());
740
741 assert!(
742 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
743 < DateTime::from_ymd(2020, 8, 20).unwrap()
744 );
745
746 assert!(
747 DateTime::from_ymd(2020, 8, 20).unwrap()
748 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
749 );
750
751 assert!(
753 DateTime::from_ymd(2020, 1, 1).unwrap()
754 > DateTime::new(-2f32, 2019, 12, 31, 23, 59, 0f64).unwrap()
755 );
756
757 assert!(DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
761 .unwrap()
762 .partial_cmp(&DateTime::from_ymd(2019, 8, 20).unwrap())
763 .is_none());
764
765 assert!(DateTime::from_ymd(2019, 8, 20)
766 .unwrap()
767 .partial_cmp(&DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap())
768 .is_none());
769
770 assert!(DateTime::from_ym(2019, 1)
771 .unwrap()
772 .partial_cmp(&DateTime::from_y(2019).unwrap())
773 .is_none());
774 }
775
776 #[test]
777 fn test_eq() {
778 crate::init().unwrap();
779
780 assert_eq!(
781 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap(),
782 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap()
783 );
784
785 assert_eq!(
786 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap(),
787 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap()
788 );
789
790 assert_eq!(
791 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
792 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
793 );
794
795 assert_eq!(
796 DateTime::from_ymd(2018, 5, 28).unwrap(),
797 DateTime::from_ymd(2018, 5, 28).unwrap()
798 );
799
800 assert_ne!(
804 DateTime::from_ymd(2018, 5, 28).unwrap(),
805 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
806 );
807
808 assert_ne!(
809 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
810 DateTime::from_ym(2018, 5).unwrap()
811 );
812
813 assert_ne!(
814 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
815 DateTime::from_y(2018).unwrap()
816 );
817 }
818}