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")]
111 pub fn new(
112 tzoffset: impl Into<Option<f32>>,
113 year: impl Into<i32>,
114 month: impl Into<Option<i32>>,
115 day: impl Into<Option<i32>>,
116 hour: impl Into<Option<i32>>,
117 minute: impl Into<Option<i32>>,
118 seconds: impl Into<Option<f64>>,
119 ) -> Result<DateTime, glib::BoolError> {
120 assert_initialized_main_thread!();
121
122 let tzoffset = tzoffset.into();
123 let year = year.into();
124 let month = month.into();
125 let day = day.into();
126 let hour = hour.into();
127 let minute = minute.into();
128 let seconds = seconds.into();
129
130 validate(tzoffset, year, month, day, hour, minute, seconds)?;
131
132 unsafe {
133 Option::<_>::from_glib_full(ffi::gst_date_time_new(
134 tzoffset.unwrap_or(0.0),
135 year,
136 month.unwrap_or(-1),
137 day.unwrap_or(-1),
138 hour.unwrap_or(-1),
139 minute.unwrap_or(-1),
140 seconds.unwrap_or(-1.0),
141 ))
142 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
143 }
144 }
145
146 #[doc(alias = "gst_date_time_new_local_time")]
147 pub fn from_local_time(
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 year = year.into();
158 let month = month.into();
159 let day = day.into();
160 let hour = hour.into();
161 let minute = minute.into();
162 let seconds = seconds.into();
163
164 validate(None, year, month, day, hour, minute, seconds)?;
165
166 unsafe {
167 Option::<_>::from_glib_full(ffi::gst_date_time_new_local_time(
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_y")]
180 pub fn from_y(year: i32) -> Result<DateTime, glib::BoolError> {
181 assert_initialized_main_thread!();
182
183 validate(None, year, None, None, None, None, None)?;
184
185 unsafe {
186 Option::<_>::from_glib_full(ffi::gst_date_time_new_y(year))
187 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
188 }
189 }
190
191 #[doc(alias = "gst_date_time_new_ym")]
192 pub fn from_ym(year: i32, month: i32) -> Result<DateTime, glib::BoolError> {
193 assert_initialized_main_thread!();
194
195 validate(None, year, Some(month), None, None, None, None)?;
196
197 unsafe {
198 Option::<_>::from_glib_full(ffi::gst_date_time_new_ym(year, month))
199 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
200 }
201 }
202
203 #[doc(alias = "gst_date_time_new_ymd")]
204 pub fn from_ymd(year: i32, month: i32, day: i32) -> Result<DateTime, glib::BoolError> {
205 assert_initialized_main_thread!();
206
207 validate(None, year, Some(month), Some(day), None, None, None)?;
208
209 unsafe {
210 Option::<_>::from_glib_full(ffi::gst_date_time_new_ymd(year, month, day))
211 .ok_or_else(|| glib::bool_error!("Can't create DateTime"))
212 }
213 }
214
215 #[doc(alias = "get_day")]
216 #[doc(alias = "gst_date_time_get_day")]
217 pub fn day(&self) -> Option<i32> {
218 if !self.has_day() {
219 return None;
220 }
221
222 unsafe { Some(ffi::gst_date_time_get_day(self.to_glib_none().0)) }
223 }
224
225 #[doc(alias = "get_hour")]
226 #[doc(alias = "gst_date_time_get_hour")]
227 pub fn hour(&self) -> Option<i32> {
228 if !self.has_time() {
229 return None;
230 }
231
232 unsafe { Some(ffi::gst_date_time_get_hour(self.to_glib_none().0)) }
233 }
234
235 #[doc(alias = "get_microsecond")]
236 #[doc(alias = "gst_date_time_get_microsecond")]
237 pub fn microsecond(&self) -> Option<i32> {
238 if !self.has_second() {
239 return None;
240 }
241
242 unsafe { Some(ffi::gst_date_time_get_microsecond(self.to_glib_none().0)) }
243 }
244
245 #[doc(alias = "get_minute")]
246 #[doc(alias = "gst_date_time_get_minute")]
247 pub fn minute(&self) -> Option<i32> {
248 if !self.has_time() {
249 return None;
250 }
251
252 unsafe { Some(ffi::gst_date_time_get_minute(self.to_glib_none().0)) }
253 }
254
255 #[doc(alias = "get_month")]
256 #[doc(alias = "gst_date_time_get_month")]
257 pub fn month(&self) -> Option<i32> {
258 if !self.has_month() {
259 return None;
260 }
261
262 unsafe { Some(ffi::gst_date_time_get_month(self.to_glib_none().0)) }
263 }
264
265 #[doc(alias = "get_second")]
266 #[doc(alias = "gst_date_time_get_second")]
267 pub fn second(&self) -> Option<i32> {
268 if !self.has_second() {
269 return None;
270 }
271
272 unsafe { Some(ffi::gst_date_time_get_second(self.to_glib_none().0)) }
273 }
274
275 #[doc(alias = "get_time_zone_offset")]
276 #[doc(alias = "gst_date_time_get_time_zone_offset")]
277 pub fn time_zone_offset(&self) -> Option<f32> {
278 if !self.has_time() {
279 return None;
280 }
281
282 unsafe {
283 Some(ffi::gst_date_time_get_time_zone_offset(
284 self.to_glib_none().0,
285 ))
286 }
287 }
288
289 pub fn to_utc(&self) -> Result<DateTime, glib::BoolError> {
290 if !self.has_time() {
291 return Ok(self.clone());
293 }
294
295 assert!(self.has_year() && self.has_month() && self.has_day() && self.has_time());
296
297 if self.has_second() {
301 self.to_g_date_time()
302 .and_then(|d| d.to_utc())
303 .map(|d| d.into())
304 } else {
305 DateTime::new(
310 self.time_zone_offset(),
311 self.year(),
312 self.month(),
313 self.day(),
314 self.hour(),
315 self.minute(),
316 Some(0.0),
317 )
318 .and_then(|d| d.to_g_date_time())
319 .and_then(|d| d.to_utc())
320 .and_then(|d| {
321 DateTime::new(
322 None, d.year(),
324 Some(d.month()),
325 Some(d.day_of_month()),
326 Some(d.hour()),
327 Some(d.minute()),
328 None, )
330 })
331 }
332 }
333}
334
335impl cmp::PartialOrd for DateTime {
336 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
356 #[inline]
357 #[allow(clippy::unnecessary_wraps)]
358 #[doc(alias = "get_cmp")]
359 fn cmp(delta: i32) -> Option<cmp::Ordering> {
360 skip_assert_initialized!();
361 Some(delta.cmp(&0))
362 }
363
364 if !(self.has_year() && other.has_year()) {
365 return None;
367 }
368
369 let (self_norm, other_norm) = if self.has_time() && other.has_time() {
371 (self.to_utc().ok()?, other.to_utc().ok()?)
372 } else {
373 (self.clone(), other.clone())
374 };
375
376 let year_delta = self_norm.year() - other_norm.year();
377 if year_delta != 0 {
378 return cmp(year_delta);
379 }
380
381 if !self.has_month() && !other.has_month() {
384 return cmp(year_delta);
386 }
387
388 if !(self.has_month() && other.has_month()) {
389 return None;
391 }
392
393 let month_delta = self_norm.month().unwrap() - other_norm.month().unwrap();
394 if month_delta != 0 {
395 return cmp(month_delta);
396 }
397
398 if !self.has_day() && !other.has_day() {
401 return Some(cmp::Ordering::Equal);
403 }
404
405 if !(self.has_day() && other.has_day()) {
406 return None;
408 }
409
410 let day_delta = self_norm.day().unwrap() - other_norm.day().unwrap();
411 if day_delta != 0 {
412 return cmp(day_delta);
413 }
414
415 if !self.has_time() && !other.has_time() {
418 return Some(cmp::Ordering::Equal);
420 }
421
422 if !(self.has_time() && other.has_time()) {
423 return None;
425 }
426
427 let hour_delta = self_norm.hour().unwrap() - other_norm.hour().unwrap();
428 if hour_delta != 0 {
429 return cmp(hour_delta);
430 }
431
432 let minute_delta = self_norm.minute().unwrap() - other_norm.minute().unwrap();
433 if minute_delta != 0 {
434 return cmp(minute_delta);
435 }
436
437 if !self.has_second() && !other.has_second() {
440 return Some(cmp::Ordering::Equal);
442 }
443
444 if !(self.has_second() && other.has_second()) {
445 return None;
447 }
448 let second_delta = self_norm.second().unwrap() - other_norm.second().unwrap();
449 if second_delta != 0 {
450 return cmp(second_delta);
451 }
452
453 cmp(self_norm.microsecond().unwrap() - other_norm.microsecond().unwrap())
454 }
455}
456
457impl cmp::PartialEq for DateTime {
458 fn eq(&self, other: &Self) -> bool {
459 self.partial_cmp(other)
460 .map_or_else(|| false, |cmp| cmp == cmp::Ordering::Equal)
461 }
462}
463
464impl fmt::Debug for DateTime {
465 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466 let mut debug_struct = f.debug_struct("DateTime");
467 if self.has_year() {
468 debug_struct.field("year", &self.year());
469 }
470 if self.has_month() {
471 debug_struct.field("month", &self.month());
472 }
473 if self.has_day() {
474 debug_struct.field("day", &self.day());
475 }
476 if self.has_time() {
477 debug_struct.field("hour", &self.hour());
478 debug_struct.field("minute", &self.minute());
479
480 if self.has_second() {
481 debug_struct.field("second", &self.second());
482 debug_struct.field("microsecond", &self.microsecond());
483 }
484
485 debug_struct.field("tz_offset", &self.time_zone_offset());
486 }
487
488 debug_struct.finish()
489 }
490}
491
492impl fmt::Display for DateTime {
493 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
494 f.write_str(
495 self.to_iso8601_string()
496 .unwrap_or_else(|_| "None".into())
497 .as_str(),
498 )
499 }
500}
501
502impl<'a> From<&'a glib::DateTime> for DateTime {
503 fn from(v: &'a glib::DateTime) -> DateTime {
504 skip_assert_initialized!();
505 DateTime::from_g_date_time(v.clone())
506 }
507}
508
509impl From<glib::DateTime> for DateTime {
510 fn from(v: glib::DateTime) -> DateTime {
511 skip_assert_initialized!();
512 DateTime::from_g_date_time(v)
513 }
514}
515
516impl<'a> TryFrom<&'a DateTime> for glib::DateTime {
517 type Error = glib::BoolError;
518
519 fn try_from(v: &'a DateTime) -> Result<glib::DateTime, glib::BoolError> {
520 skip_assert_initialized!();
521 v.to_g_date_time()
522 }
523}
524
525impl TryFrom<DateTime> for glib::DateTime {
526 type Error = glib::BoolError;
527
528 fn try_from(v: DateTime) -> Result<glib::DateTime, glib::BoolError> {
529 skip_assert_initialized!();
530 v.to_g_date_time()
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537
538 #[allow(clippy::cognitive_complexity)]
539 #[test]
540 fn test_to_utc() {
541 crate::init().unwrap();
542
543 let utc_date_time = DateTime::new(2f32, 2019, 8, 20, 20, 9, 42.123_456f64)
545 .unwrap()
546 .to_utc()
547 .unwrap();
548 assert_eq!(utc_date_time.year(), 2019);
549 assert_eq!(utc_date_time.month().unwrap(), 8);
550 assert_eq!(utc_date_time.day().unwrap(), 20);
551 assert_eq!(utc_date_time.hour().unwrap(), 18);
552 assert_eq!(utc_date_time.minute().unwrap(), 9);
553 assert_eq!(utc_date_time.second().unwrap(), 42);
554 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
555
556 let utc_date_time = DateTime::new(2f32, 2019, 1, 1, 0, 0, 42.123_456f64)
558 .unwrap()
559 .to_utc()
560 .unwrap();
561 assert_eq!(utc_date_time.year(), 2018);
562 assert_eq!(utc_date_time.month().unwrap(), 12);
563 assert_eq!(utc_date_time.day().unwrap(), 31);
564 assert_eq!(utc_date_time.hour().unwrap(), 22);
565 assert_eq!(utc_date_time.minute().unwrap(), 0);
566 assert_eq!(utc_date_time.second().unwrap(), 42);
567 assert_eq!(utc_date_time.microsecond().unwrap(), 123_456);
568
569 let utc_date_time = DateTime::from_ymd(2019, 1, 1).unwrap().to_utc().unwrap();
571 assert_eq!(utc_date_time.year(), 2019);
572 assert_eq!(utc_date_time.month().unwrap(), 1);
573 assert_eq!(utc_date_time.day().unwrap(), 1);
574 assert!(!utc_date_time.has_time());
575 assert!(!utc_date_time.has_second());
576
577 let utc_date_time = DateTime::new(2f32, 2018, 5, 28, 16, 6, None)
579 .unwrap()
580 .to_utc()
581 .unwrap();
582 assert_eq!(utc_date_time.year(), 2018);
583 assert_eq!(utc_date_time.month().unwrap(), 5);
584 assert_eq!(utc_date_time.day().unwrap(), 28);
585 assert_eq!(utc_date_time.hour().unwrap(), 14);
586 assert_eq!(utc_date_time.minute().unwrap(), 6);
587 assert!(!utc_date_time.has_second());
588 }
589
590 #[test]
591 fn test_partial_ord() {
592 crate::init().unwrap();
593
594 assert!(
596 DateTime::new(2f32, 2020, 8, 20, 19, 43, 42.123_456f64).unwrap()
597 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
598 );
599
600 assert!(
602 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
603 < DateTime::new(2f32, 2019, 9, 19, 19, 43, 42.123_456f64).unwrap()
604 );
605
606 assert!(
608 DateTime::new(2f32, 2019, 8, 21, 19, 43, 42.123_456f64).unwrap()
609 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
610 );
611
612 assert!(
614 DateTime::new(2f32, 2019, 8, 20, 19, 44, 42.123_456f64).unwrap()
615 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
616 );
617
618 assert!(
620 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
621 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
622 );
623
624 assert!(
626 DateTime::new(2f32, 2019, 8, 20, 19, 43, 43.123_456f64).unwrap()
627 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
628 );
629
630 assert!(
632 DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_457f64).unwrap()
633 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
634 );
635
636 assert!(
638 DateTime::new(1f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
639 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 42.123_456f64).unwrap()
640 );
641
642 assert!(
644 DateTime::new(2f32, 2019, 1, 1, 0, 0, 0f64).unwrap()
645 < DateTime::new(1f32, 2018, 12, 31, 23, 59, 0f64).unwrap()
646 );
647
648 assert!(
650 DateTime::from_ymd(2020, 8, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
651 );
652 assert!(
653 DateTime::from_ymd(2019, 9, 20).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
654 );
655 assert!(
656 DateTime::from_ymd(2019, 8, 21).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap()
657 );
658
659 assert!(DateTime::from_ym(2020, 8).unwrap() > DateTime::from_ym(2019, 8).unwrap());
660 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ym(2019, 8).unwrap());
661 assert!(DateTime::from_ym(2019, 9).unwrap() > DateTime::from_ymd(2019, 8, 20).unwrap());
662
663 assert!(DateTime::from_y(2020).unwrap() > DateTime::from_y(2019).unwrap());
664 assert!(DateTime::from_ym(2020, 1).unwrap() > DateTime::from_y(2019).unwrap());
665
666 assert!(
667 DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
668 < DateTime::from_ymd(2020, 8, 20).unwrap()
669 );
670
671 assert!(
672 DateTime::from_ymd(2020, 8, 20).unwrap()
673 > DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap()
674 );
675
676 assert!(
678 DateTime::from_ymd(2020, 1, 1).unwrap()
679 > DateTime::new(-2f32, 2019, 12, 31, 23, 59, 0f64).unwrap()
680 );
681
682 assert!(DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64)
686 .unwrap()
687 .partial_cmp(&DateTime::from_ymd(2019, 8, 20).unwrap())
688 .is_none());
689
690 assert!(DateTime::from_ymd(2019, 8, 20)
691 .unwrap()
692 .partial_cmp(&DateTime::new(2f32, 2019, 8, 20, 19, 43, 44.123_456f64).unwrap())
693 .is_none());
694
695 assert!(DateTime::from_ym(2019, 1)
696 .unwrap()
697 .partial_cmp(&DateTime::from_y(2019).unwrap())
698 .is_none());
699 }
700
701 #[test]
702 fn test_eq() {
703 crate::init().unwrap();
704
705 assert_eq!(
706 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap(),
707 DateTime::new(2f32, 2018, 5, 28, 16, 6, 42.123_456f64).unwrap()
708 );
709
710 assert_eq!(
711 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap(),
712 DateTime::new(2f32, 2018, 5, 28, 16, 6, 0f64).unwrap()
713 );
714
715 assert_eq!(
716 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
717 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
718 );
719
720 assert_eq!(
721 DateTime::from_ymd(2018, 5, 28).unwrap(),
722 DateTime::from_ymd(2018, 5, 28).unwrap()
723 );
724
725 assert_ne!(
729 DateTime::from_ymd(2018, 5, 28).unwrap(),
730 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap()
731 );
732
733 assert_ne!(
734 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
735 DateTime::from_ym(2018, 5).unwrap()
736 );
737
738 assert_ne!(
739 DateTime::new(2f32, 2018, 5, 28, 16, 6, None).unwrap(),
740 DateTime::from_y(2018).unwrap()
741 );
742 }
743}