gel_protocol/model/
time.rs

1use crate::model::{OutOfRangeError, ParseDurationError};
2use std::convert::{TryFrom, TryInto};
3use std::fmt::{self, Debug, Display};
4use std::str::FromStr;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7/// A span of time.
8///
9/// Precision: microseconds.
10///
11/// ```
12/// # use gel_protocol::model::Duration;
13/// assert_eq!(&Duration::from_micros(1_000_000).to_string(), "0:00:01");
14/// ```
15///
16/// This type implements [FromStr] and parses either seconds or PostgreSQL
17/// simple duration format:
18///
19/// ```
20/// # use gel_protocol::model::Duration;
21/// # use std::str::FromStr;
22/// assert_eq!(&Duration::from_str("65").unwrap().to_string(), "0:01:05");
23/// assert_eq!(&Duration::from_str("2:12:7.65432").unwrap().to_string(), "2:12:07.65432");
24/// ```
25#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
26#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct Duration {
28    pub(crate) micros: i64,
29}
30
31/// A combination [`LocalDate`] and [`LocalTime`].
32#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
33#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct LocalDatetime {
35    pub(crate) micros: i64,
36}
37
38/// Naive date without a timezone.
39#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
40#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
41pub struct LocalDate {
42    pub(crate) days: i32,
43}
44
45/// Naive time without a timezone.
46///
47/// Can't be more than 24 hours.
48///
49/// Precision: microseconds.
50#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
51#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct LocalTime {
53    pub(crate) micros: u64,
54}
55
56/// A UTC date and time.
57#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
58#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct Datetime {
60    pub(crate) micros: i64,
61}
62
63/// A type that can represent a human-friendly duration like 1 month or two days.
64#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
65#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct RelativeDuration {
67    pub(crate) micros: i64,
68    pub(crate) days: i32,
69    pub(crate) months: i32,
70}
71
72/// A type that can represent a human-friendly date duration like 1 month or two days.
73#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
74#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct DateDuration {
76    pub(crate) days: i32,
77    pub(crate) months: i32,
78}
79
80const SECS_PER_DAY: u64 = 86_400;
81const MICROS_PER_DAY: u64 = SECS_PER_DAY * 1_000_000;
82
83// leap years repeat every 400 years
84const DAYS_IN_400_YEARS: u32 = 400 * 365 + 97;
85
86const MIN_YEAR: i32 = 1;
87const MAX_YEAR: i32 = 9999;
88
89// year -4800 is a multiple of 400 smaller than the minimum supported year
90const BASE_YEAR: i32 = -4800;
91
92#[allow(dead_code)] // only used by specific features
93const DAYS_IN_2000_YEARS: i32 = 5 * DAYS_IN_400_YEARS as i32;
94
95const DAY_TO_MONTH_365: [u32; 13] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
96const DAY_TO_MONTH_366: [u32; 13] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
97
98const MICROS_PER_MS: i64 = 1_000;
99const MICROS_PER_SECOND: i64 = MICROS_PER_MS * 1_000;
100const MICROS_PER_MINUTE: i64 = MICROS_PER_SECOND * 60;
101const MICROS_PER_HOUR: i64 = MICROS_PER_MINUTE * 60;
102
103impl Duration {
104    pub const MIN: Duration = Duration { micros: i64::MIN };
105    pub const MAX: Duration = Duration { micros: i64::MAX };
106
107    pub fn from_micros(micros: i64) -> Duration {
108        Duration { micros }
109    }
110
111    pub fn to_micros(self) -> i64 {
112        self.micros
113    }
114
115    // Returns true if self is positive and false if the duration
116    // is zero or negative.
117    pub fn is_positive(&self) -> bool {
118        self.micros.is_positive()
119    }
120    // Returns true if self is negative and false if the duration
121    // is zero or positive.
122    pub fn is_negative(&self) -> bool {
123        self.micros.is_negative()
124    }
125    // Returns absolute values as stdlib's duration
126    //
127    // Note: `std::time::Duration` can't be negative
128    pub fn abs_duration(&self) -> std::time::Duration {
129        if self.micros.is_negative() {
130            std::time::Duration::from_micros(u64::MAX - self.micros as u64 + 1)
131        } else {
132            std::time::Duration::from_micros(self.micros as u64)
133        }
134    }
135
136    fn try_from_pg_simple_format(input: &str) -> Result<Self, ParseDurationError> {
137        let mut split = input.trim_end().splitn(3, ':');
138        let mut value: i64 = 0;
139        let negative;
140        let mut pos: usize = 0;
141
142        {
143            let hour_str = split.next().filter(|s| !s.is_empty()).ok_or_else(|| {
144                ParseDurationError::new("EOF met, expecting `+`, `-` or int")
145                    .not_final()
146                    .pos(input.len())
147            })?;
148            pos += hour_str.len() - 1;
149            let hour_str = hour_str.trim_start();
150            let hour = hour_str
151                .strip_prefix('-')
152                .unwrap_or(hour_str)
153                .parse::<i32>()
154                .map_err(|e| ParseDurationError::from(e).not_final().pos(pos))?;
155            negative = hour_str.starts_with('-');
156            value += (hour.abs() as i64) * MICROS_PER_HOUR;
157        }
158
159        {
160            pos += 1;
161            let minute_str = split.next().ok_or_else(|| {
162                ParseDurationError::new("EOF met, expecting `:`")
163                    .not_final()
164                    .pos(pos)
165            })?;
166            if !minute_str.is_empty() {
167                pos += minute_str.len();
168                let minute = minute_str
169                    .parse::<u8>()
170                    .map_err(|e| ParseDurationError::from(e).pos(pos))
171                    .and_then(|m| {
172                        if m <= 59 {
173                            Ok(m)
174                        } else {
175                            Err(ParseDurationError::new("minutes value out of range").pos(pos))
176                        }
177                    })?;
178                value += (minute as i64) * MICROS_PER_MINUTE;
179            }
180        }
181
182        if let Some(remaining) = split.last() {
183            pos += 1;
184            let mut sec_split = remaining.splitn(2, '.');
185
186            {
187                let second_str = sec_split.next().unwrap();
188                pos += second_str.len();
189                let second = second_str
190                    .parse::<u8>()
191                    .map_err(|e| ParseDurationError::from(e).pos(pos))
192                    .and_then(|s| {
193                        if s <= 59 {
194                            Ok(s)
195                        } else {
196                            Err(ParseDurationError::new("seconds value out of range").pos(pos))
197                        }
198                    })?;
199                value += (second as i64) * MICROS_PER_SECOND;
200            }
201
202            if let Some(sub_sec_str) = sec_split.last() {
203                pos += 1;
204                for (i, c) in sub_sec_str.char_indices() {
205                    let d = c
206                        .to_digit(10)
207                        .ok_or_else(|| ParseDurationError::new("not a digit").pos(pos + i + 1))?;
208                    if i < 6 {
209                        value += (d * 10_u32.pow((5 - i) as u32)) as i64;
210                    } else {
211                        if d >= 5 {
212                            value += 1;
213                        }
214                        break;
215                    }
216                }
217            }
218        }
219
220        if negative {
221            value = -value;
222        }
223        Ok(Self { micros: value })
224    }
225
226    fn try_from_iso_format(input: &str) -> Result<Self, ParseDurationError> {
227        if let Some(input) = input.strip_prefix("PT") {
228            let mut pos = 2;
229            let mut result = 0;
230            let mut parts = input.split_inclusive(|c: char| c.is_alphabetic());
231            let mut current = parts.next();
232
233            if let Some(part) = current {
234                if let Some(hour_str) = part.strip_suffix('H') {
235                    let hour = hour_str
236                        .parse::<i32>()
237                        .map_err(|e| ParseDurationError::from(e).pos(pos))?;
238                    result += (hour as i64) * MICROS_PER_HOUR;
239                    pos += part.len();
240                    current = parts.next();
241                }
242            }
243
244            if let Some(part) = current {
245                if let Some(minute_str) = part.strip_suffix('M') {
246                    let minute = minute_str
247                        .parse::<i32>()
248                        .map_err(|e| ParseDurationError::from(e).pos(pos))?;
249                    result += (minute as i64) * MICROS_PER_MINUTE;
250                    pos += part.len();
251                    current = parts.next();
252                }
253            }
254
255            if let Some(part) = current {
256                if let Some(second_str) = part.strip_suffix('S') {
257                    let (second_str, subsec_str) = second_str
258                        .split_once('.')
259                        .map(|(sec, sub)| (sec, sub.get(..6).or(Some(sub))))
260                        .unwrap_or_else(|| (second_str, None));
261
262                    let second = second_str
263                        .parse::<i32>()
264                        .map_err(|e| ParseDurationError::from(e).pos(pos))?;
265                    result += (second as i64) * MICROS_PER_SECOND;
266                    pos += second_str.len() + 1;
267
268                    if let Some(subsec_str) = subsec_str {
269                        let subsec = subsec_str
270                            .parse::<i32>()
271                            .map_err(|e| ParseDurationError::from(e).pos(pos))?;
272                        result += (subsec as i64)
273                            * 10_i64.pow((6 - subsec_str.len()) as u32)
274                            * if second < 0 { -1 } else { 1 };
275                        pos += subsec_str.len()
276                    }
277                    current = parts.next();
278                }
279            }
280
281            if current.is_some() {
282                Err(ParseDurationError::new("expecting EOF").pos(pos))
283            } else {
284                Ok(Self { micros: result })
285            }
286        } else {
287            Err(ParseDurationError::new("not ISO format").not_final())
288        }
289    }
290
291    fn get_pg_format_value(
292        input: &str,
293        start: usize,
294        end: usize,
295    ) -> Result<i64, ParseDurationError> {
296        if let Some(val) = input.get(start..end) {
297            match val.parse::<i32>() {
298                Ok(v) => Ok(v as i64),
299                Err(e) => Err(ParseDurationError::from(e).pos(end.saturating_sub(1))),
300            }
301        } else {
302            Err(ParseDurationError::new("expecting value").pos(end))
303        }
304    }
305
306    fn try_from_pg_format(input: &str) -> Result<Self, ParseDurationError> {
307        enum Expect {
308            Numeric { begin: usize },
309            Alphabetic { begin: usize, numeric: i64 },
310            Whitespace { numeric: Option<i64> },
311        }
312        let mut seen = Vec::new();
313        let mut get_unit = |start: usize, end: usize, default: Option<&str>| {
314            input
315                .get(start..end)
316                .or(default)
317                .and_then(|u| match u.to_lowercase().as_str() {
318                    "h" | "hr" | "hrs" | "hour" | "hours" => Some(MICROS_PER_HOUR),
319                    "m" | "min" | "mins" | "minute" | "minutes" => Some(MICROS_PER_MINUTE),
320                    "ms" | "millisecon" | "millisecons" | "millisecond" | "milliseconds" => {
321                        Some(MICROS_PER_MS)
322                    }
323                    "us" | "microsecond" | "microseconds" => Some(1),
324                    "s" | "sec" | "secs" | "second" | "seconds" => Some(MICROS_PER_SECOND),
325                    _ => None,
326                })
327                .ok_or_else(|| ParseDurationError::new("unknown unit").pos(start))
328                .and_then(|u| {
329                    if seen.contains(&u) {
330                        Err(ParseDurationError::new("specified more than once").pos(start))
331                    } else {
332                        seen.push(u);
333                        Ok(u)
334                    }
335                })
336        };
337        let mut state = Expect::Whitespace { numeric: None };
338        let mut result = 0;
339        for (pos, c) in input.char_indices() {
340            let is_whitespace = c.is_whitespace();
341            let is_numeric = c.is_numeric() || c == '+' || c == '-';
342            let is_alphabetic = c.is_alphabetic();
343            if !(is_whitespace || is_numeric || is_alphabetic) {
344                return Err(ParseDurationError::new("unexpected character").pos(pos));
345            }
346            match state {
347                Expect::Numeric { begin } if !is_numeric => {
348                    let numeric = Self::get_pg_format_value(input, begin, pos)?;
349                    if is_alphabetic {
350                        state = Expect::Alphabetic {
351                            begin: pos,
352                            numeric,
353                        };
354                    } else {
355                        state = Expect::Whitespace {
356                            numeric: Some(numeric),
357                        };
358                    }
359                }
360                Expect::Alphabetic { begin, numeric } if !is_alphabetic => {
361                    result += numeric * get_unit(begin, pos, None)?;
362                    if is_numeric {
363                        state = Expect::Numeric { begin: pos };
364                    } else {
365                        state = Expect::Whitespace { numeric: None };
366                    }
367                }
368                Expect::Whitespace { numeric: None } if !is_whitespace => {
369                    if is_numeric {
370                        state = Expect::Numeric { begin: pos };
371                    } else {
372                        return Err(
373                            ParseDurationError::new("expecting whitespace or numeric").pos(pos)
374                        );
375                    }
376                }
377                Expect::Whitespace {
378                    numeric: Some(numeric),
379                } if !is_whitespace => {
380                    if is_alphabetic {
381                        state = Expect::Alphabetic {
382                            begin: pos,
383                            numeric,
384                        };
385                    } else {
386                        return Err(
387                            ParseDurationError::new("expecting whitespace or alphabetic").pos(pos),
388                        );
389                    }
390                }
391                _ => {}
392            }
393        }
394        match state {
395            Expect::Numeric { begin } => {
396                result += Self::get_pg_format_value(input, begin, input.len())? * MICROS_PER_SECOND;
397            }
398            Expect::Alphabetic { begin, numeric } => {
399                result += numeric * get_unit(begin, input.len(), Some("s"))?;
400            }
401            Expect::Whitespace {
402                numeric: Some(numeric),
403            } => {
404                result += numeric * MICROS_PER_SECOND;
405            }
406            _ => {}
407        }
408        Ok(Self { micros: result })
409    }
410}
411
412impl FromStr for Duration {
413    type Err = ParseDurationError;
414
415    fn from_str(input: &str) -> Result<Self, Self::Err> {
416        if let Ok(seconds) = input.trim().parse::<i64>() {
417            seconds
418                .checked_mul(MICROS_PER_SECOND)
419                .map(Self::from_micros)
420                .ok_or_else(|| Self::Err::new("seconds value out of range").pos(input.len() - 1))
421        } else {
422            Self::try_from_pg_simple_format(input)
423                .or_else(|e| {
424                    if e.is_final {
425                        Err(e)
426                    } else {
427                        Self::try_from_iso_format(input)
428                    }
429                })
430                .or_else(|e| {
431                    if e.is_final {
432                        Err(e)
433                    } else {
434                        Self::try_from_pg_format(input)
435                    }
436                })
437        }
438    }
439}
440
441impl LocalDatetime {
442    // 0001-01-01T00:00:00
443    pub const MIN: LocalDatetime = LocalDatetime {
444        micros: -63082281600000000,
445    };
446    // 9999-12-31T23:59:59.999999
447    pub const MAX: LocalDatetime = LocalDatetime {
448        micros: 252455615999999999,
449    };
450
451    pub(crate) fn from_postgres_micros(micros: i64) -> Result<LocalDatetime, OutOfRangeError> {
452        if !(Self::MIN.micros..=Self::MAX.micros).contains(&micros) {
453            return Err(OutOfRangeError);
454        }
455        Ok(LocalDatetime { micros })
456    }
457
458    #[deprecated(
459        since = "0.5.0",
460        note = "use Datetime::try_from_unix_micros(v).into() instead"
461    )]
462    pub fn from_micros(micros: i64) -> LocalDatetime {
463        Self::from_postgres_micros(micros).unwrap_or_else(|_| {
464            panic!(
465                "LocalDatetime::from_micros({}) is outside the valid datetime range",
466                micros
467            )
468        })
469    }
470
471    #[deprecated(since = "0.5.0", note = "use .to_utc().to_unix_micros() instead")]
472    pub fn to_micros(self) -> i64 {
473        self.micros
474    }
475
476    pub fn new(date: LocalDate, time: LocalTime) -> LocalDatetime {
477        let micros = date.to_days() as i64 * MICROS_PER_DAY as i64 + time.to_micros() as i64;
478        LocalDatetime { micros }
479    }
480
481    pub fn date(self) -> LocalDate {
482        LocalDate::from_days(self.micros.wrapping_div_euclid(MICROS_PER_DAY as i64) as i32)
483    }
484
485    pub fn time(self) -> LocalTime {
486        LocalTime::from_micros(self.micros.wrapping_rem_euclid(MICROS_PER_DAY as i64) as u64)
487    }
488
489    pub fn to_utc(self) -> Datetime {
490        Datetime {
491            micros: self.micros,
492        }
493    }
494}
495
496impl From<Datetime> for LocalDatetime {
497    fn from(d: Datetime) -> LocalDatetime {
498        LocalDatetime { micros: d.micros }
499    }
500}
501
502impl Display for LocalDatetime {
503    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
504        write!(f, "{} {}", self.date(), self.time())
505    }
506}
507
508impl Debug for LocalDatetime {
509    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
510        write!(f, "{}T{}", self.date(), self.time())
511    }
512}
513
514impl LocalTime {
515    pub const MIN: LocalTime = LocalTime { micros: 0 };
516    pub const MIDNIGHT: LocalTime = LocalTime { micros: 0 };
517    pub const MAX: LocalTime = LocalTime {
518        micros: MICROS_PER_DAY - 1,
519    };
520
521    pub(crate) fn try_from_micros(micros: u64) -> Result<LocalTime, OutOfRangeError> {
522        if micros < MICROS_PER_DAY {
523            Ok(LocalTime { micros })
524        } else {
525            Err(OutOfRangeError)
526        }
527    }
528
529    pub fn from_micros(micros: u64) -> LocalTime {
530        Self::try_from_micros(micros).expect("LocalTime is out of range")
531    }
532
533    pub fn to_micros(self) -> u64 {
534        self.micros
535    }
536
537    fn to_hmsu(self) -> (u8, u8, u8, u32) {
538        let micros = self.micros;
539
540        let microsecond = (micros % 1_000_000) as u32;
541        let micros = micros / 1_000_000;
542
543        let second = (micros % 60) as u8;
544        let micros = micros / 60;
545
546        let minute = (micros % 60) as u8;
547        let micros = micros / 60;
548
549        let hour = (micros % 24) as u8;
550        let micros = micros / 24;
551        debug_assert_eq!(0, micros);
552
553        (hour, minute, second, microsecond)
554    }
555
556    #[cfg(test)] // currently only used by tests, will be used by parsing later
557    fn from_hmsu(hour: u8, minute: u8, second: u8, microsecond: u32) -> LocalTime {
558        assert!(microsecond < 1_000_000);
559        assert!(second < 60);
560        assert!(minute < 60);
561        assert!(hour < 24);
562
563        let micros = microsecond as u64
564            + 1_000_000 * (second as u64 + 60 * (minute as u64 + 60 * (hour as u64)));
565        LocalTime::from_micros(micros)
566    }
567}
568
569impl Display for LocalTime {
570    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571        Debug::fmt(self, f)
572    }
573}
574
575impl Debug for LocalTime {
576    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
577        let (hour, minute, second, microsecond) = self.to_hmsu();
578        write!(f, "{hour:02}:{minute:02}:{second:02}")?;
579        // like chrono::NaiveTime it outputs either 0, 3 or 6 decimal digits
580        if microsecond != 0 {
581            if microsecond % 1000 == 0 {
582                write!(f, ".{:03}", microsecond / 1000)?;
583            } else {
584                write!(f, ".{microsecond:06}")?;
585            }
586        };
587        Ok(())
588    }
589}
590
591impl LocalDate {
592    pub const MIN: LocalDate = LocalDate { days: -730119 }; // 0001-01-01
593    pub const MAX: LocalDate = LocalDate { days: 2921939 }; // 9999-12-31
594    pub const UNIX_EPOCH: LocalDate = LocalDate {
595        days: -(30 * 365 + 7),
596    }; // 1970-01-01
597
598    fn try_from_days(days: i32) -> Result<LocalDate, OutOfRangeError> {
599        if !(Self::MIN.days..=Self::MAX.days).contains(&days) {
600            return Err(OutOfRangeError);
601        }
602        Ok(LocalDate { days })
603    }
604
605    pub fn from_days(days: i32) -> LocalDate {
606        Self::try_from_days(days).unwrap_or_else(|_| {
607            panic!(
608                "LocalDate::from_days({}) is outside the valid date range",
609                days
610            )
611        })
612    }
613
614    pub fn to_days(self) -> i32 {
615        self.days
616    }
617
618    pub fn from_ymd(year: i32, month: u8, day: u8) -> LocalDate {
619        Self::try_from_ymd(year, month, day)
620            .unwrap_or_else(|_| panic!("invalid date {:04}-{:02}-{:02}", year, month, day))
621    }
622
623    fn try_from_ymd(year: i32, month: u8, day: u8) -> Result<LocalDate, OutOfRangeError> {
624        if !(1..=31).contains(&day) {
625            return Err(OutOfRangeError);
626        }
627        if !(1..=12).contains(&month) {
628            return Err(OutOfRangeError);
629        }
630        if !(MIN_YEAR..=MAX_YEAR).contains(&year) {
631            return Err(OutOfRangeError);
632        }
633
634        let passed_years = (year - BASE_YEAR - 1) as u32;
635        let days_from_year =
636            365 * passed_years + passed_years / 4 - passed_years / 100 + passed_years / 400 + 366;
637
638        let is_leap_year = (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
639        let day_to_month = if is_leap_year {
640            DAY_TO_MONTH_366
641        } else {
642            DAY_TO_MONTH_365
643        };
644
645        let day_in_year = (day - 1) as u32 + day_to_month[month as usize - 1];
646        if day_in_year >= day_to_month[month as usize] {
647            return Err(OutOfRangeError);
648        }
649
650        LocalDate::try_from_days(
651            (days_from_year + day_in_year) as i32
652                - DAYS_IN_400_YEARS as i32 * ((2000 - BASE_YEAR) / 400),
653        )
654    }
655
656    fn to_ymd(self) -> (i32, u8, u8) {
657        const DAYS_IN_100_YEARS: u32 = 100 * 365 + 24;
658        const DAYS_IN_4_YEARS: u32 = 4 * 365 + 1;
659        const DAYS_IN_1_YEAR: u32 = 365;
660        const DAY_TO_MONTH_MARCH: [u32; 12] =
661            [0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337];
662        const MARCH_1: u32 = 31 + 29;
663        const MARCH_1_MINUS_BASE_YEAR_TO_POSTGRES_EPOCH: u32 =
664            (2000 - BASE_YEAR) as u32 / 400 * DAYS_IN_400_YEARS - MARCH_1;
665
666        let days = (self.days as u32).wrapping_add(MARCH_1_MINUS_BASE_YEAR_TO_POSTGRES_EPOCH);
667
668        let years400 = days / DAYS_IN_400_YEARS;
669        let days = days % DAYS_IN_400_YEARS;
670
671        let mut years100 = days / DAYS_IN_100_YEARS;
672        if years100 == 4 {
673            years100 = 3
674        }; // prevent 400 year leap day from overflowing
675        let days = days - DAYS_IN_100_YEARS * years100;
676
677        let years4 = days / DAYS_IN_4_YEARS;
678        let days = days % DAYS_IN_4_YEARS;
679
680        let mut years1 = days / DAYS_IN_1_YEAR;
681        if years1 == 4 {
682            years1 = 3
683        }; // prevent 4 year leap day from overflowing
684        let days = days - DAYS_IN_1_YEAR * years1;
685
686        let years = years1 + years4 * 4 + years100 * 100 + years400 * 400;
687        let month_entry = DAY_TO_MONTH_MARCH
688            .iter()
689            .filter(|d| days >= **d)
690            .enumerate()
691            .last()
692            .unwrap();
693        let months = years * 12 + 2 + month_entry.0 as u32;
694        let year = (months / 12) as i32 + BASE_YEAR;
695        let month = (months % 12 + 1) as u8;
696        let day = (days - month_entry.1 + 1) as u8;
697
698        (year, month, day)
699    }
700}
701
702impl Display for LocalDate {
703    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
704        Debug::fmt(self, f)
705    }
706}
707
708impl Debug for LocalDate {
709    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710        let (year, month, day) = self.to_ymd();
711        if year >= 10_000 {
712            // ISO format requires a + on dates longer than 4 digits
713            write!(f, "+")?;
714        }
715        if year >= 0 {
716            write!(f, "{year:04}-{month:02}-{day:02}")
717        } else {
718            // rust counts the sign as a digit when padding
719            write!(f, "{year:05}-{month:02}-{day:02}")
720        }
721    }
722}
723
724impl Datetime {
725    // -63082281600000000 micros = Jan. 1 year 1
726    pub const MIN: Datetime = Datetime {
727        micros: LocalDatetime::MIN.micros,
728    };
729    // 252455615999999999 micros = Dec. 31 year 9999
730    pub const MAX: Datetime = Datetime {
731        micros: LocalDatetime::MAX.micros,
732    };
733    pub const UNIX_EPOCH: Datetime = Datetime {
734        //micros: 0
735        micros: LocalDate::UNIX_EPOCH.days as i64 * MICROS_PER_DAY as i64,
736    };
737
738    /// Convert microseconds since unix epoch into a datetime
739    pub fn try_from_unix_micros(micros: i64) -> Result<Datetime, OutOfRangeError> {
740        Self::_from_micros(micros).ok_or(OutOfRangeError)
741    }
742
743    #[deprecated(since = "0.5.0", note = "use try_from_unix_micros instead")]
744    pub fn try_from_micros(micros: i64) -> Result<Datetime, OutOfRangeError> {
745        Self::from_postgres_micros(micros)
746    }
747
748    pub(crate) fn from_postgres_micros(micros: i64) -> Result<Datetime, OutOfRangeError> {
749        if !(Self::MIN.micros..=Self::MAX.micros).contains(&micros) {
750            return Err(OutOfRangeError);
751        }
752        Ok(Datetime { micros })
753    }
754
755    fn _from_micros(micros: i64) -> Option<Datetime> {
756        let micros = micros.checked_add(Self::UNIX_EPOCH.micros)?;
757        if !(Self::MIN.micros..=Self::MAX.micros).contains(&micros) {
758            return None;
759        }
760        Some(Datetime { micros })
761    }
762
763    #[deprecated(since = "0.5.0", note = "use from_unix_micros instead")]
764    pub fn from_micros(micros: i64) -> Datetime {
765        Self::from_postgres_micros(micros).unwrap_or_else(|_| {
766            panic!(
767                "Datetime::from_micros({}) is outside the valid datetime range",
768                micros
769            )
770        })
771    }
772
773    /// Convert microseconds since unix epoch into a datetime
774    ///
775    /// # Panics
776    ///
777    /// When value is out of range.
778    pub fn from_unix_micros(micros: i64) -> Datetime {
779        if let Some(result) = Self::_from_micros(micros) {
780            return result;
781        }
782        panic!(
783            "Datetime::from_micros({}) is outside the valid datetime range",
784            micros
785        );
786    }
787
788    #[deprecated(since = "0.5.0", note = "use to_unix_micros instead")]
789    pub fn to_micros(self) -> i64 {
790        self.micros
791    }
792
793    /// Convert datetime to microseconds since Unix Epoch
794    pub fn to_unix_micros(self) -> i64 {
795        // i64 is enough to fit our range with both epochs
796        self.micros - Datetime::UNIX_EPOCH.micros
797    }
798
799    fn postgres_epoch_unix() -> SystemTime {
800        use std::time::Duration;
801        // postgres epoch starts at 2000-01-01
802        UNIX_EPOCH + Duration::from_micros((-Datetime::UNIX_EPOCH.micros) as u64)
803    }
804}
805
806impl Display for Datetime {
807    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
808        write!(
809            f,
810            "{} UTC",
811            LocalDatetime {
812                micros: self.micros
813            }
814        )
815    }
816}
817
818impl Debug for Datetime {
819    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
820        write!(
821            f,
822            "{:?}Z",
823            LocalDatetime {
824                micros: self.micros
825            }
826        )
827    }
828}
829
830impl TryFrom<Datetime> for SystemTime {
831    type Error = OutOfRangeError;
832
833    fn try_from(value: Datetime) -> Result<Self, Self::Error> {
834        use std::time::Duration;
835
836        if value.micros > 0 {
837            Datetime::postgres_epoch_unix().checked_add(Duration::from_micros(value.micros as u64))
838        } else {
839            Datetime::postgres_epoch_unix()
840                .checked_sub(Duration::from_micros((-value.micros) as u64))
841        }
842        .ok_or(OutOfRangeError)
843    }
844}
845
846impl TryFrom<std::time::Duration> for Duration {
847    type Error = OutOfRangeError;
848
849    fn try_from(value: std::time::Duration) -> Result<Self, Self::Error> {
850        TryFrom::try_from(&value)
851    }
852}
853
854impl TryFrom<&std::time::Duration> for Duration {
855    type Error = OutOfRangeError;
856
857    fn try_from(value: &std::time::Duration) -> Result<Self, Self::Error> {
858        let secs = value.as_secs();
859        let subsec_nanos = value.subsec_nanos();
860        let subsec_micros = nanos_to_micros(subsec_nanos.into());
861        let micros = i64::try_from(secs)
862            .ok()
863            .and_then(|x| x.checked_mul(1_000_000))
864            .and_then(|x| x.checked_add(subsec_micros))
865            .ok_or(OutOfRangeError)?;
866        Ok(Duration { micros })
867    }
868}
869
870impl TryFrom<&Duration> for std::time::Duration {
871    type Error = OutOfRangeError;
872
873    fn try_from(value: &Duration) -> Result<std::time::Duration, Self::Error> {
874        let micros = value.micros.try_into().map_err(|_| OutOfRangeError)?;
875        Ok(std::time::Duration::from_micros(micros))
876    }
877}
878impl TryFrom<Duration> for std::time::Duration {
879    type Error = OutOfRangeError;
880
881    fn try_from(value: Duration) -> Result<std::time::Duration, Self::Error> {
882        (&value).try_into()
883    }
884}
885
886impl TryFrom<SystemTime> for Datetime {
887    type Error = OutOfRangeError;
888
889    fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
890        match value.duration_since(UNIX_EPOCH) {
891            Ok(duration) => {
892                let secs = duration.as_secs();
893                let subsec_nanos = duration.subsec_nanos();
894                let subsec_micros = nanos_to_micros(subsec_nanos.into());
895                let micros = i64::try_from(secs)
896                    .ok()
897                    .and_then(|x| x.checked_mul(1_000_000))
898                    .and_then(|x| x.checked_add(subsec_micros))
899                    .and_then(|x| x.checked_add(Datetime::UNIX_EPOCH.micros))
900                    .ok_or(OutOfRangeError)?;
901                if micros > Datetime::MAX.micros {
902                    return Err(OutOfRangeError);
903                }
904                Ok(Datetime { micros })
905            }
906            Err(e) => {
907                let mut secs = e.duration().as_secs();
908                let mut subsec_nanos = e.duration().subsec_nanos();
909                if subsec_nanos > 0 {
910                    secs = secs.checked_add(1).ok_or(OutOfRangeError)?;
911                    subsec_nanos = 1_000_000_000 - subsec_nanos;
912                }
913                let subsec_micros = nanos_to_micros(subsec_nanos.into());
914                let micros = i64::try_from(secs)
915                    .ok()
916                    .and_then(|x| x.checked_mul(1_000_000))
917                    .and_then(|x| Datetime::UNIX_EPOCH.micros.checked_sub(x))
918                    .and_then(|x| x.checked_add(subsec_micros))
919                    .ok_or(OutOfRangeError)?;
920                if micros < Datetime::MIN.micros {
921                    return Err(OutOfRangeError);
922                }
923                Ok(Datetime { micros })
924            }
925        }
926    }
927}
928
929impl std::ops::Add<&'_ std::time::Duration> for Datetime {
930    type Output = Datetime;
931    fn add(self, other: &std::time::Duration) -> Datetime {
932        let Ok(duration) = Duration::try_from(other) else {
933            debug_assert!(false, "duration is out of range");
934            return Datetime::MAX;
935        };
936        if let Some(micros) = self.micros.checked_add(duration.micros) {
937            Datetime { micros }
938        } else {
939            debug_assert!(false, "duration is out of range");
940            Datetime::MAX
941        }
942    }
943}
944
945impl std::ops::Add<std::time::Duration> for Datetime {
946    type Output = Datetime;
947    #[allow(clippy::op_ref)]
948    fn add(self, other: std::time::Duration) -> Datetime {
949        self + &other
950    }
951}
952
953impl Display for Duration {
954    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
955        let abs = if self.micros < 0 {
956            write!(f, "-")?;
957            -self.micros
958        } else {
959            self.micros
960        };
961        let (sec, micros) = (abs / 1_000_000, abs % 1_000_000);
962        if micros != 0 {
963            let mut fract = micros;
964            let mut zeros = 0;
965            while fract % 10 == 0 {
966                zeros += 1;
967                fract /= 10;
968            }
969            write!(
970                f,
971                "{hours}:{minutes:02}:{seconds:02}.{fract:0>fsize$}",
972                hours = sec / 3600,
973                minutes = sec / 60 % 60,
974                seconds = sec % 60,
975                fract = fract,
976                fsize = 6 - zeros,
977            )
978        } else {
979            write!(f, "{}:{:02}:{:02}", sec / 3600, sec / 60 % 60, sec % 60)
980        }
981    }
982}
983
984#[cfg(test)]
985mod test {
986    use super::*;
987
988    #[test]
989    fn micros_conv() {
990        let datetime = Datetime::from_unix_micros(1645681383000002);
991        assert_eq!(datetime.micros, 698996583000002);
992        assert_eq!(to_debug(datetime), "2022-02-24T05:43:03.000002Z");
993    }
994
995    #[test]
996    fn big_duration_abs() {
997        use super::Duration as Src;
998        use std::time::Duration as Trg;
999        assert_eq!(Src { micros: -1 }.abs_duration(), Trg::new(0, 1000));
1000        assert_eq!(Src { micros: -1000 }.abs_duration(), Trg::new(0, 1000000));
1001        assert_eq!(Src { micros: -1000000 }.abs_duration(), Trg::new(1, 0));
1002        assert_eq!(
1003            Src { micros: i64::MIN }.abs_duration(),
1004            Trg::new(9223372036854, 775808000)
1005        );
1006    }
1007
1008    #[test]
1009    fn local_date_from_ymd() {
1010        assert_eq!(0, LocalDate::from_ymd(2000, 1, 1).to_days());
1011        assert_eq!(-365, LocalDate::from_ymd(1999, 1, 1).to_days());
1012        assert_eq!(366, LocalDate::from_ymd(2001, 1, 1).to_days());
1013        assert_eq!(-730119, LocalDate::from_ymd(1, 1, 1).to_days());
1014        assert_eq!(2921575, LocalDate::from_ymd(9999, 1, 1).to_days());
1015
1016        assert_eq!(Err(OutOfRangeError), LocalDate::try_from_ymd(2001, 1, 32));
1017        assert_eq!(Err(OutOfRangeError), LocalDate::try_from_ymd(2001, 2, 29));
1018    }
1019
1020    #[test]
1021    fn local_date_from_ymd_leap_year() {
1022        let days_in_month_leap = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1023        let mut total_days = 0;
1024        let start_of_year = 365 * 4 + 1;
1025        for month in 1..=12 {
1026            let start_of_month = LocalDate::from_ymd(2004, month as u8, 1).to_days();
1027            assert_eq!(total_days, start_of_month - start_of_year);
1028
1029            let days_in_current_month = days_in_month_leap[month - 1];
1030            total_days += days_in_current_month;
1031
1032            let end_of_month =
1033                LocalDate::from_ymd(2004, month as u8, days_in_current_month as u8).to_days();
1034            assert_eq!(total_days - 1, end_of_month - start_of_year);
1035        }
1036        assert_eq!(366, total_days);
1037    }
1038
1039    const DAYS_IN_MONTH_LEAP: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1040
1041    #[test]
1042    fn local_date_from_ymd_normal_year() {
1043        let mut total_days = 0;
1044        let start_of_year = 365 + 1;
1045        for month in 1..=12 {
1046            let start_of_month = LocalDate::from_ymd(2001, month as u8, 1).to_days();
1047            assert_eq!(total_days, start_of_month - start_of_year);
1048
1049            let days_in_current_month = DAYS_IN_MONTH_LEAP[month - 1];
1050            total_days += days_in_current_month as i32;
1051
1052            let end_of_month =
1053                LocalDate::from_ymd(2001, month as u8, days_in_current_month).to_days();
1054            assert_eq!(total_days - 1, end_of_month - start_of_year);
1055        }
1056        assert_eq!(365, total_days);
1057    }
1058
1059    pub const CHRONO_MAX_YEAR: i32 = 262_143;
1060
1061    fn extended_test_dates() -> impl Iterator<Item = (i32, u8, u8)> {
1062        const YEARS: [i32; 36] = [
1063            1,
1064            2,
1065            1000,
1066            1969,
1067            1970, // unix epoch
1068            1971,
1069            1999,
1070            2000, // postgres epoch
1071            2001,
1072            2002,
1073            2003,
1074            2004,
1075            2008,
1076            2009,
1077            2010,
1078            2100,
1079            2200,
1080            2300,
1081            2400,
1082            9000,
1083            9999,
1084            10_000,
1085            10_001,
1086            11_000,
1087            20_000,
1088            100_000,
1089            200_000,
1090            CHRONO_MAX_YEAR - 1,
1091            CHRONO_MAX_YEAR,
1092            CHRONO_MAX_YEAR + 1,
1093            MAX_YEAR - 1000,
1094            MAX_YEAR - 31,
1095            MAX_YEAR - 30, // maximum unix based
1096            MAX_YEAR - 29, // less than 30 years before maximum, so a unix epoch in microseconds overflows
1097            MAX_YEAR - 1,
1098            MAX_YEAR,
1099        ];
1100
1101        const MONTHS: std::ops::RangeInclusive<u8> = 1u8..=12;
1102        const DAYS: [u8; 6] = [1u8, 13, 28, 29, 30, 31];
1103        let dates = MONTHS.flat_map(|month| DAYS.iter().map(move |day| (month, *day)));
1104
1105        YEARS
1106            .iter()
1107            .flat_map(move |year| dates.clone().map(move |date| (*year, date.0, date.1)))
1108    }
1109
1110    pub fn valid_test_dates() -> impl Iterator<Item = (i32, u8, u8)> {
1111        extended_test_dates().filter(|date| LocalDate::try_from_ymd(date.0, date.1, date.2).is_ok())
1112    }
1113
1114    pub fn test_times() -> impl Iterator<Item = u64> {
1115        const TIMES: [u64; 7] = [
1116            0,
1117            10,
1118            10_020,
1119            12345 * 1_000_000,
1120            12345 * 1_001_000,
1121            12345 * 1_001_001,
1122            MICROS_PER_DAY - 1,
1123        ];
1124        TIMES.iter().copied()
1125    }
1126
1127    #[test]
1128    fn check_test_dates() {
1129        assert!(valid_test_dates().count() > 1000);
1130    }
1131
1132    #[test]
1133    fn local_date_ymd_roundtrip() {
1134        for (year, month, day) in valid_test_dates() {
1135            let date = LocalDate::from_ymd(year, month, day);
1136            assert_eq!((year, month, day), date.to_ymd());
1137        }
1138    }
1139
1140    #[test]
1141    fn local_time_parts_roundtrip() {
1142        for time in test_times() {
1143            let expected_time = LocalTime::from_micros(time);
1144            let (hour, minute, second, microsecond) = expected_time.to_hmsu();
1145            let actual_time = LocalTime::from_hmsu(hour, minute, second, microsecond);
1146            assert_eq!(expected_time, actual_time);
1147        }
1148    }
1149
1150    #[test]
1151    fn format_local_date() {
1152        assert_eq!("2000-01-01", LocalDate::from_days(0).to_string());
1153        assert_eq!("0001-01-01", LocalDate::MIN.to_string());
1154        assert_eq!("9999-12-31", LocalDate::MAX.to_string());
1155    }
1156
1157    #[test]
1158    fn format_local_time() {
1159        assert_eq!("00:00:00", LocalTime::MIDNIGHT.to_string());
1160        assert_eq!("00:00:00.010", LocalTime::from_micros(10_000).to_string());
1161        assert_eq!(
1162            "00:00:00.010020",
1163            LocalTime::from_micros(10_020).to_string()
1164        );
1165        assert_eq!("23:59:59.999999", LocalTime::MAX.to_string());
1166    }
1167
1168    pub fn to_debug<T: Debug>(x: T) -> String {
1169        format!("{x:?}")
1170    }
1171
1172    #[test]
1173    #[allow(deprecated)]
1174    fn format_local_datetime() {
1175        assert_eq!(
1176            "2039-02-13 23:31:30.123456",
1177            LocalDatetime::from_micros(1_234_567_890_123_456).to_string()
1178        );
1179        assert_eq!(
1180            "2039-02-13T23:31:30.123456",
1181            to_debug(LocalDatetime::from_micros(1_234_567_890_123_456))
1182        );
1183
1184        assert_eq!("0001-01-01 00:00:00", LocalDatetime::MIN.to_string());
1185        assert_eq!("0001-01-01T00:00:00", to_debug(LocalDatetime::MIN));
1186
1187        assert_eq!("9999-12-31 23:59:59.999999", LocalDatetime::MAX.to_string());
1188        assert_eq!("9999-12-31T23:59:59.999999", to_debug(LocalDatetime::MAX));
1189    }
1190
1191    #[test]
1192    #[allow(deprecated)]
1193    fn format_datetime() {
1194        assert_eq!(
1195            "2039-02-13 23:31:30.123456 UTC",
1196            Datetime::from_micros(1_234_567_890_123_456).to_string()
1197        );
1198        assert_eq!(
1199            "2039-02-13T23:31:30.123456Z",
1200            to_debug(Datetime::from_micros(1_234_567_890_123_456))
1201        );
1202
1203        assert_eq!("0001-01-01 00:00:00 UTC", Datetime::MIN.to_string());
1204        assert_eq!("0001-01-01T00:00:00Z", to_debug(Datetime::MIN));
1205
1206        assert_eq!("9999-12-31 23:59:59.999999 UTC", Datetime::MAX.to_string());
1207        assert_eq!("9999-12-31T23:59:59.999999Z", to_debug(Datetime::MAX));
1208    }
1209
1210    #[test]
1211    fn format_duration() {
1212        fn dur_str(msec: i64) -> String {
1213            Duration::from_micros(msec).to_string()
1214        }
1215        assert_eq!(dur_str(1_000_000), "0:00:01");
1216        assert_eq!(dur_str(1), "0:00:00.000001");
1217        assert_eq!(dur_str(7_015_000), "0:00:07.015");
1218        assert_eq!(dur_str(10_000_000_015_000), "2777:46:40.015");
1219        assert_eq!(dur_str(12_345_678_000_000), "3429:21:18");
1220    }
1221
1222    #[test]
1223    fn parse_duration_str() {
1224        fn micros(input: &str) -> i64 {
1225            Duration::from_str(input).unwrap().micros
1226        }
1227        assert_eq!(micros(" 100   "), 100_000_000);
1228        assert_eq!(micros("123"), 123_000_000);
1229        assert_eq!(micros("-123"), -123_000_000);
1230        assert_eq!(micros("  20 mins 1hr "), 4_800_000_000);
1231        assert_eq!(micros("  20 mins -1hr "), -2_400_000_000);
1232        assert_eq!(micros("  20us  1h    20   "), 3_620_000_020);
1233        assert_eq!(micros("  -20us  1h    20   "), 3_619_999_980);
1234        assert_eq!(micros("  -20US  1H    20   "), 3_619_999_980);
1235        assert_eq!(
1236            micros("1 hour 20 minutes 30 seconds 40 milliseconds 50 microseconds"),
1237            4_830_040_050
1238        );
1239        assert_eq!(
1240            micros("1 hour 20 minutes +30seconds 40 milliseconds -50microseconds"),
1241            4_830_039_950
1242        );
1243        assert_eq!(
1244            micros("1 houR  20 minutes 30SECOND 40 milliseconds 50 us"),
1245            4_830_040_050
1246        );
1247        assert_eq!(micros("  20 us 1H 20 minutes "), 4_800_000_020);
1248        assert_eq!(micros("-1h"), -3_600_000_000);
1249        assert_eq!(micros("100h"), 360_000_000_000);
1250        let h12 = 12 * 3_600_000_000_i64;
1251        let m12 = 12 * 60_000_000_i64;
1252        assert_eq!(micros("   12:12:12.2131   "), h12 + m12 + 12_213_100);
1253        assert_eq!(micros("-12:12:12.21313"), -(h12 + m12 + 12_213_130));
1254        assert_eq!(micros("-12:12:12.213134"), -(h12 + m12 + 12_213_134));
1255        assert_eq!(micros("-12:12:12.2131341"), -(h12 + m12 + 12_213_134));
1256        assert_eq!(micros("-12:12:12.2131341111111"), -(h12 + m12 + 12_213_134));
1257        assert_eq!(micros("-12:12:12.2131315111111"), -(h12 + m12 + 12_213_132));
1258        assert_eq!(micros("-12:12:12.2131316111111"), -(h12 + m12 + 12_213_132));
1259        assert_eq!(micros("-12:12:12.2131314511111"), -(h12 + m12 + 12_213_131));
1260        assert_eq!(micros("-0:12:12.2131"), -(m12 + 12_213_100));
1261        assert_eq!(micros("12:12"), h12 + m12);
1262        assert_eq!(micros("-12:12"), -(h12 + m12));
1263        assert_eq!(micros("-12:1:1"), -(h12 + 61_000_000));
1264        assert_eq!(micros("+12:1:1"), h12 + 61_000_000);
1265        assert_eq!(micros("-12:1:1.1234"), -(h12 + 61_123_400));
1266        assert_eq!(micros("1211:59:59.9999"), h12 * 100 + h12 - 100);
1267        assert_eq!(micros("-12:"), -h12);
1268        assert_eq!(micros("0"), 0);
1269        assert_eq!(micros("00:00:00"), 0);
1270        assert_eq!(micros("00:00:10.9"), 10_900_000);
1271        assert_eq!(micros("00:00:10.09"), 10_090_000);
1272        assert_eq!(micros("00:00:10.009"), 10_009_000);
1273        assert_eq!(micros("00:00:10.0009"), 10_000_900);
1274        assert_eq!(micros("00:00:00.5"), 500_000);
1275        assert_eq!(micros("  +00005"), 5_000_000);
1276        assert_eq!(micros("  -00005"), -5_000_000);
1277        assert_eq!(micros("PT"), 0);
1278        assert_eq!(micros("PT1H1M1S"), 3_661_000_000);
1279        assert_eq!(micros("PT1M1S"), 61_000_000);
1280        assert_eq!(micros("PT1S"), 1_000_000);
1281        assert_eq!(micros("PT1H1S"), 3_601_000_000);
1282        assert_eq!(micros("PT1H1M1.1S"), 3_661_100_000);
1283        assert_eq!(micros("PT1H1M1.01S"), 3_661_010_000);
1284        assert_eq!(micros("PT1H1M1.10S"), 3_661_100_000);
1285        assert_eq!(micros("PT1H1M1.1234567S"), 3_661_123_456);
1286        assert_eq!(micros("PT1H1M1.1234564S"), 3_661_123_456);
1287        assert_eq!(micros("PT-1H1M1.1S"), -3_538_900_000);
1288        assert_eq!(micros("PT+1H-1M1.1S"), 3_541_100_000);
1289        assert_eq!(micros("PT1H+1M-1.1S"), 3_658_900_000);
1290
1291        fn assert_error(input: &str, expected_pos: usize, pat: &str) {
1292            let ParseDurationError {
1293                pos,
1294                message,
1295                is_final: _,
1296            } = Duration::from_str(input).unwrap_err();
1297            assert_eq!(pos, expected_pos);
1298            assert!(
1299                message.contains(pat),
1300                "`{}` not found in `{}`",
1301                pat,
1302                message,
1303            );
1304        }
1305        assert_error("blah", 0, "numeric");
1306        assert_error("!", 0, "unexpected");
1307        assert_error("-", 0, "invalid digit");
1308        assert_error("+", 0, "invalid digit");
1309        assert_error("  20 us 1H 20 30 minutes ", 14, "alphabetic");
1310        assert_error("   12:12:121.2131   ", 11, "seconds");
1311        assert_error("   12:60:21.2131   ", 7, "minutes");
1312        assert_error("  20us 20   1h       ", 12, "alphabetic");
1313        assert_error("  20us $ 20   1h       ", 7, "unexpected");
1314        assert_error(
1315            "1 houR  20 minutes 30SECOND 40 milliseconds 50 uss",
1316            47,
1317            "unit",
1318        );
1319        assert_error("PT1M1H", 4, "EOF");
1320        assert_error("PT1S1M", 4, "EOF");
1321    }
1322
1323    #[test]
1324    fn add_duration_rounding() {
1325        // round down
1326        assert_eq!(
1327            Datetime::UNIX_EPOCH + std::time::Duration::new(17, 500),
1328            Datetime::UNIX_EPOCH + std::time::Duration::new(17, 0),
1329        );
1330        // round up
1331        assert_eq!(
1332            Datetime::UNIX_EPOCH + std::time::Duration::new(12345, 1500),
1333            Datetime::UNIX_EPOCH + std::time::Duration::new(12345, 2000),
1334        );
1335    }
1336
1337    #[test]
1338    #[allow(deprecated)]
1339    fn to_and_from_unix_micros_roundtrip() {
1340        let zero_micros = 0;
1341        let datetime = Datetime::from_unix_micros(0);
1342        // Unix micros should equal 0
1343        assert_eq!(zero_micros, datetime.to_unix_micros());
1344        // Datetime (Postgres epoch-based) micros should be negative
1345        // Micros = negative micros to go from 2000 to 1970
1346        assert_eq!(datetime.micros, datetime.to_micros());
1347        assert_eq!(datetime.micros, Datetime::UNIX_EPOCH.micros);
1348        assert_eq!(datetime.micros, -946684800000000);
1349    }
1350}
1351
1352impl RelativeDuration {
1353    pub fn try_from_years(years: i32) -> Result<RelativeDuration, OutOfRangeError> {
1354        Ok(RelativeDuration {
1355            months: years.checked_mul(12).ok_or(OutOfRangeError)?,
1356            days: 0,
1357            micros: 0,
1358        })
1359    }
1360    pub fn from_years(years: i32) -> RelativeDuration {
1361        RelativeDuration::try_from_years(years).unwrap()
1362    }
1363    pub fn try_from_months(months: i32) -> Result<RelativeDuration, OutOfRangeError> {
1364        Ok(RelativeDuration {
1365            months,
1366            days: 0,
1367            micros: 0,
1368        })
1369    }
1370    pub fn from_months(months: i32) -> RelativeDuration {
1371        RelativeDuration::try_from_months(months).unwrap()
1372    }
1373    pub fn try_from_days(days: i32) -> Result<RelativeDuration, OutOfRangeError> {
1374        Ok(RelativeDuration {
1375            months: 0,
1376            days,
1377            micros: 0,
1378        })
1379    }
1380    pub fn from_days(days: i32) -> RelativeDuration {
1381        RelativeDuration::try_from_days(days).unwrap()
1382    }
1383    pub fn try_from_hours(hours: i64) -> Result<RelativeDuration, OutOfRangeError> {
1384        Ok(RelativeDuration {
1385            months: 0,
1386            days: 0,
1387            micros: hours.checked_mul(3_600_000_000).ok_or(OutOfRangeError)?,
1388        })
1389    }
1390    pub fn from_hours(hours: i64) -> RelativeDuration {
1391        RelativeDuration::try_from_hours(hours).unwrap()
1392    }
1393    pub fn try_from_minutes(minutes: i64) -> Result<RelativeDuration, OutOfRangeError> {
1394        Ok(RelativeDuration {
1395            months: 0,
1396            days: 0,
1397            micros: minutes.checked_mul(60_000_000).ok_or(OutOfRangeError)?,
1398        })
1399    }
1400    pub fn from_minutes(minutes: i64) -> RelativeDuration {
1401        RelativeDuration::try_from_minutes(minutes).unwrap()
1402    }
1403    pub fn try_from_secs(secs: i64) -> Result<RelativeDuration, OutOfRangeError> {
1404        Ok(RelativeDuration {
1405            months: 0,
1406            days: 0,
1407            micros: secs.checked_mul(1_000_000).ok_or(OutOfRangeError)?,
1408        })
1409    }
1410    pub fn from_secs(secs: i64) -> RelativeDuration {
1411        RelativeDuration::try_from_secs(secs).unwrap()
1412    }
1413    pub fn try_from_millis(millis: i64) -> Result<RelativeDuration, OutOfRangeError> {
1414        Ok(RelativeDuration {
1415            months: 0,
1416            days: 0,
1417            micros: millis.checked_mul(1_000).ok_or(OutOfRangeError)?,
1418        })
1419    }
1420    pub fn from_millis(millis: i64) -> RelativeDuration {
1421        RelativeDuration::try_from_millis(millis).unwrap()
1422    }
1423    pub fn try_from_micros(micros: i64) -> Result<RelativeDuration, OutOfRangeError> {
1424        Ok(RelativeDuration {
1425            months: 0,
1426            days: 0,
1427            micros,
1428        })
1429    }
1430    pub fn from_micros(micros: i64) -> RelativeDuration {
1431        RelativeDuration::try_from_micros(micros).unwrap()
1432    }
1433    pub fn checked_add(self, other: Self) -> Option<Self> {
1434        Some(RelativeDuration {
1435            months: self.months.checked_add(other.months)?,
1436            days: self.days.checked_add(other.days)?,
1437            micros: self.micros.checked_add(other.micros)?,
1438        })
1439    }
1440    pub fn checked_sub(self, other: Self) -> Option<Self> {
1441        Some(RelativeDuration {
1442            months: self.months.checked_sub(other.months)?,
1443            days: self.days.checked_sub(other.days)?,
1444            micros: self.micros.checked_sub(other.micros)?,
1445        })
1446    }
1447}
1448
1449impl std::ops::Add for RelativeDuration {
1450    type Output = Self;
1451    fn add(self, other: Self) -> Self {
1452        RelativeDuration {
1453            months: self.months + other.months,
1454            days: self.days + other.days,
1455            micros: self.micros + other.micros,
1456        }
1457    }
1458}
1459
1460impl std::ops::Sub for RelativeDuration {
1461    type Output = Self;
1462    fn sub(self, other: Self) -> Self {
1463        RelativeDuration {
1464            months: self.months - other.months,
1465            days: self.days - other.days,
1466            micros: self.micros - other.micros,
1467        }
1468    }
1469}
1470
1471impl Display for RelativeDuration {
1472    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1473        if self.months == 0 && self.days == 0 && self.micros == 0 {
1474            return write!(f, "PT0S");
1475        }
1476        write!(f, "P")?;
1477        if self.months.abs() >= 12 {
1478            write!(f, "{}Y", self.months / 12)?;
1479        }
1480        if (self.months % 12).abs() > 0 {
1481            write!(f, "{}M", self.months % 12)?;
1482        }
1483        if self.days.abs() > 0 {
1484            write!(f, "{}D", self.days)?;
1485        }
1486        if self.micros.abs() > 0 {
1487            write!(f, "T")?;
1488            if self.micros.abs() >= 3_600_000_000 {
1489                write!(f, "{}H", self.micros / 3_600_000_000)?;
1490            }
1491            let minutes = self.micros % 3_600_000_000;
1492            if minutes.abs() >= 60_000_000 {
1493                write!(f, "{}M", minutes / 60_000_000)?;
1494            }
1495            let seconds = minutes % 60_000_000;
1496            if seconds.abs() >= 1_000_000 {
1497                write!(f, "{}", seconds / 1_000_000)?;
1498            }
1499            let micros = seconds % 1_000_000;
1500            if micros.abs() > 0 {
1501                let mut buf = [0u8; 6];
1502                let text = {
1503                    use std::io::{Cursor, Write};
1504
1505                    let mut cur = Cursor::new(&mut buf[..]);
1506                    write!(cur, "{:06}", micros.abs()).unwrap();
1507                    let mut len = buf.len();
1508                    while buf[len - 1] == b'0' {
1509                        len -= 1;
1510                    }
1511                    std::str::from_utf8(&buf[..len]).unwrap()
1512                };
1513                write!(f, ".{text}")?;
1514            }
1515            if seconds.abs() > 0 {
1516                write!(f, "S")?;
1517            }
1518        }
1519        Ok(())
1520    }
1521}
1522
1523#[test]
1524fn relative_duration_display() {
1525    let dur = RelativeDuration::from_years(2)
1526        + RelativeDuration::from_months(56)
1527        + RelativeDuration::from_days(-16)
1528        + RelativeDuration::from_hours(48)
1529        + RelativeDuration::from_minutes(245)
1530        + RelativeDuration::from_secs(7)
1531        + RelativeDuration::from_millis(600);
1532    assert_eq!(dur.to_string(), "P6Y8M-16DT52H5M7.6S");
1533
1534    let dur = RelativeDuration::from_years(2)
1535        + RelativeDuration::from_months(-56)
1536        + RelativeDuration::from_days(-16)
1537        + RelativeDuration::from_minutes(-245)
1538        + RelativeDuration::from_secs(7)
1539        + RelativeDuration::from_millis(600);
1540    assert_eq!(dur.to_string(), "P-2Y-8M-16DT-4H-4M-52.4S");
1541
1542    let dur = RelativeDuration::from_years(1);
1543    assert_eq!(dur.to_string(), "P1Y");
1544    let dur = RelativeDuration::from_months(1);
1545    assert_eq!(dur.to_string(), "P1M");
1546    let dur = RelativeDuration::from_hours(1);
1547    assert_eq!(dur.to_string(), "PT1H");
1548    let dur = RelativeDuration::from_minutes(1);
1549    assert_eq!(dur.to_string(), "PT1M");
1550    let dur = RelativeDuration::from_secs(1);
1551    assert_eq!(dur.to_string(), "PT1S");
1552}
1553
1554impl DateDuration {
1555    pub fn try_from_years(years: i32) -> Result<DateDuration, OutOfRangeError> {
1556        Ok(DateDuration {
1557            months: years.checked_mul(12).ok_or(OutOfRangeError)?,
1558            days: 0,
1559        })
1560    }
1561    pub fn from_years(years: i32) -> DateDuration {
1562        DateDuration::try_from_years(years).unwrap()
1563    }
1564    pub fn try_from_months(months: i32) -> Result<DateDuration, OutOfRangeError> {
1565        Ok(DateDuration { months, days: 0 })
1566    }
1567    pub fn from_months(months: i32) -> DateDuration {
1568        DateDuration::try_from_months(months).unwrap()
1569    }
1570    pub fn try_from_days(days: i32) -> Result<DateDuration, OutOfRangeError> {
1571        Ok(DateDuration { months: 0, days })
1572    }
1573    pub fn from_days(days: i32) -> DateDuration {
1574        DateDuration::try_from_days(days).unwrap()
1575    }
1576    pub fn checked_add(self, other: Self) -> Option<Self> {
1577        Some(DateDuration {
1578            months: self.months.checked_add(other.months)?,
1579            days: self.days.checked_add(other.days)?,
1580        })
1581    }
1582    pub fn checked_sub(self, other: Self) -> Option<Self> {
1583        Some(DateDuration {
1584            months: self.months.checked_sub(other.months)?,
1585            days: self.days.checked_sub(other.days)?,
1586        })
1587    }
1588}
1589
1590impl std::ops::Add for DateDuration {
1591    type Output = Self;
1592    fn add(self, other: Self) -> Self {
1593        DateDuration {
1594            months: self.months + other.months,
1595            days: self.days + other.days,
1596        }
1597    }
1598}
1599
1600impl std::ops::Sub for DateDuration {
1601    type Output = Self;
1602    fn sub(self, other: Self) -> Self {
1603        DateDuration {
1604            months: self.months - other.months,
1605            days: self.days - other.days,
1606        }
1607    }
1608}
1609
1610impl Display for DateDuration {
1611    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1612        if self.months == 0 && self.days == 0 {
1613            return write!(f, "PT0D"); // XXX
1614        }
1615        write!(f, "P")?;
1616        if self.months.abs() >= 12 {
1617            write!(f, "{}Y", self.months / 12)?;
1618        }
1619        if (self.months % 12).abs() > 0 {
1620            write!(f, "{}M", self.months % 12)?;
1621        }
1622        if self.days.abs() > 0 {
1623            write!(f, "{}D", self.days)?;
1624        }
1625        Ok(())
1626    }
1627}
1628
1629fn nanos_to_micros(nanos: i64) -> i64 {
1630    // round to the nearest even
1631    let mut micros = nanos / 1000;
1632    let remainder = nanos % 1000;
1633    if remainder == 500 && micros % 2 == 1 || remainder > 500 {
1634        micros += 1;
1635    }
1636    micros
1637}
1638
1639#[cfg(feature = "chrono")]
1640mod chrono_interop {
1641    use super::*;
1642    use chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime};
1643    use chrono::DateTime;
1644
1645    type ChronoDatetime = chrono::DateTime<chrono::Utc>;
1646
1647    impl From<&LocalDatetime> for NaiveDateTime {
1648        fn from(value: &LocalDatetime) -> NaiveDateTime {
1649            let timestamp_seconds = value.micros.wrapping_div_euclid(1_000_000)
1650                - (Datetime::UNIX_EPOCH.micros / 1_000_000);
1651            let timestamp_nanos = (value.micros.wrapping_rem_euclid(1_000_000) * 1000) as u32;
1652            DateTime::from_timestamp(timestamp_seconds, timestamp_nanos)
1653                .expect("NaiveDateTime range is bigger than LocalDatetime")
1654                .naive_utc()
1655        }
1656    }
1657
1658    impl TryFrom<&NaiveDateTime> for LocalDatetime {
1659        type Error = OutOfRangeError;
1660        fn try_from(d: &NaiveDateTime) -> Result<LocalDatetime, Self::Error> {
1661            let secs = d.and_utc().timestamp();
1662            let subsec_nanos = d.and_utc().timestamp_subsec_nanos();
1663            let subsec_micros = nanos_to_micros(subsec_nanos.into());
1664            let micros = secs
1665                .checked_mul(1_000_000)
1666                .and_then(|x| x.checked_add(subsec_micros))
1667                .and_then(|x| x.checked_add(Datetime::UNIX_EPOCH.micros))
1668                .ok_or(OutOfRangeError)?;
1669            if !(LocalDatetime::MIN.micros..=LocalDatetime::MAX.micros).contains(&micros) {
1670                return Err(OutOfRangeError);
1671            }
1672            Ok(LocalDatetime { micros })
1673        }
1674    }
1675
1676    impl From<&Datetime> for ChronoDatetime {
1677        fn from(value: &Datetime) -> ChronoDatetime {
1678            use chrono::TimeZone;
1679
1680            let pg_epoch = chrono::Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
1681            let duration = chrono::Duration::microseconds(value.micros);
1682            pg_epoch
1683                .checked_add_signed(duration)
1684                .expect("Gel datetime range is smaller than Chrono's")
1685        }
1686    }
1687
1688    impl From<Datetime> for ChronoDatetime {
1689        fn from(value: Datetime) -> ChronoDatetime {
1690            (&value).into()
1691        }
1692    }
1693
1694    impl TryFrom<&ChronoDatetime> for Datetime {
1695        type Error = OutOfRangeError;
1696
1697        fn try_from(value: &ChronoDatetime) -> Result<Datetime, Self::Error> {
1698            let min = ChronoDatetime::from(Datetime::MIN);
1699            let duration = value
1700                .signed_duration_since(min)
1701                .to_std()
1702                .map_err(|_| OutOfRangeError)?;
1703            let secs = duration.as_secs();
1704            let subsec_micros = nanos_to_micros(duration.subsec_nanos().into());
1705            let micros = i64::try_from(secs)
1706                .ok()
1707                .and_then(|x| x.checked_mul(1_000_000))
1708                .and_then(|x| x.checked_add(subsec_micros))
1709                .and_then(|x| x.checked_add(Datetime::MIN.micros))
1710                .ok_or(OutOfRangeError)?;
1711            if micros > Datetime::MAX.micros {
1712                return Err(OutOfRangeError);
1713            }
1714            Ok(Datetime { micros })
1715        }
1716    }
1717
1718    impl TryFrom<&NaiveDate> for LocalDate {
1719        type Error = OutOfRangeError;
1720        fn try_from(d: &NaiveDate) -> Result<LocalDate, Self::Error> {
1721            let days = chrono::Datelike::num_days_from_ce(d);
1722            Ok(LocalDate {
1723                days: days
1724                    .checked_sub(DAYS_IN_2000_YEARS - 365)
1725                    .ok_or(OutOfRangeError)?,
1726            })
1727        }
1728    }
1729
1730    impl From<&LocalDate> for NaiveDate {
1731        fn from(value: &LocalDate) -> NaiveDate {
1732            value
1733                .days
1734                .checked_add(DAYS_IN_2000_YEARS - 365)
1735                .and_then(NaiveDate::from_num_days_from_ce_opt)
1736                .expect("NaiveDate range is bigger than LocalDate")
1737        }
1738    }
1739
1740    impl From<&LocalTime> for NaiveTime {
1741        fn from(value: &LocalTime) -> NaiveTime {
1742            NaiveTime::from_num_seconds_from_midnight_opt(
1743                (value.micros / 1_000_000) as u32,
1744                ((value.micros % 1_000_000) * 1000) as u32,
1745            )
1746            .expect("localtime and native time have equal range")
1747        }
1748    }
1749
1750    impl From<&NaiveTime> for LocalTime {
1751        fn from(time: &NaiveTime) -> LocalTime {
1752            let sec = chrono::Timelike::num_seconds_from_midnight(time);
1753            let nanos = nanos_to_micros(chrono::Timelike::nanosecond(time) as i64) as u64;
1754            let mut micros = sec as u64 * 1_000_000 + nanos;
1755
1756            if micros >= 86_400_000_000 {
1757                // this is only possible due to rounding:
1758                // >= 23:59:59.999999500
1759                micros -= 86_400_000_000;
1760            }
1761
1762            LocalTime { micros }
1763        }
1764    }
1765
1766    impl From<LocalDatetime> for NaiveDateTime {
1767        fn from(value: LocalDatetime) -> NaiveDateTime {
1768            (&value).into()
1769        }
1770    }
1771
1772    impl From<LocalDate> for NaiveDate {
1773        fn from(value: LocalDate) -> NaiveDate {
1774            (&value).into()
1775        }
1776    }
1777
1778    impl TryFrom<NaiveDate> for LocalDate {
1779        type Error = OutOfRangeError;
1780        fn try_from(d: NaiveDate) -> Result<LocalDate, Self::Error> {
1781            std::convert::TryFrom::try_from(&d)
1782        }
1783    }
1784
1785    impl From<LocalTime> for NaiveTime {
1786        fn from(value: LocalTime) -> NaiveTime {
1787            (&value).into()
1788        }
1789    }
1790
1791    impl TryFrom<NaiveDateTime> for LocalDatetime {
1792        type Error = OutOfRangeError;
1793        fn try_from(d: NaiveDateTime) -> Result<LocalDatetime, Self::Error> {
1794            std::convert::TryFrom::try_from(&d)
1795        }
1796    }
1797
1798    impl TryFrom<ChronoDatetime> for Datetime {
1799        type Error = OutOfRangeError;
1800        fn try_from(d: ChronoDatetime) -> Result<Datetime, Self::Error> {
1801            std::convert::TryFrom::try_from(&d)
1802        }
1803    }
1804
1805    impl From<NaiveTime> for LocalTime {
1806        fn from(time: NaiveTime) -> LocalTime {
1807            From::from(&time)
1808        }
1809    }
1810
1811    #[cfg(test)]
1812    mod test {
1813        use super::*;
1814        use crate::model::time::test::{test_times, to_debug, valid_test_dates, CHRONO_MAX_YEAR};
1815
1816        #[test]
1817        fn chrono_roundtrips() -> Result<(), Box<dyn std::error::Error>> {
1818            let naive = NaiveDateTime::from_str("2019-12-27T01:02:03.123456")?;
1819            assert_eq!(
1820                naive,
1821                Into::<NaiveDateTime>::into(LocalDatetime::try_from(naive)?)
1822            );
1823            let naive = NaiveDate::from_str("2019-12-27")?;
1824            assert_eq!(naive, Into::<NaiveDate>::into(LocalDate::try_from(naive)?));
1825            let naive = NaiveTime::from_str("01:02:03.123456")?;
1826            assert_eq!(naive, Into::<NaiveTime>::into(LocalTime::from(naive)));
1827            Ok(())
1828        }
1829
1830        fn check_display<E: Display, A: Display>(expected_value: E, actual_value: A) {
1831            let expected_display = expected_value.to_string();
1832            let actual_display = actual_value.to_string();
1833            assert_eq!(expected_display, actual_display);
1834        }
1835
1836        fn check_debug<E: Debug, A: Debug>(expected_value: E, actual_value: A) {
1837            let expected_debug = to_debug(expected_value);
1838            let actual_debug = to_debug(actual_value);
1839            assert_eq!(expected_debug, actual_debug);
1840        }
1841
1842        #[test]
1843        fn format_local_time() {
1844            for time in test_times() {
1845                let actual_value = LocalTime::from_micros(time);
1846                let expected_value = NaiveTime::from(actual_value);
1847
1848                check_display(expected_value, actual_value);
1849                check_debug(expected_value, actual_value);
1850            }
1851        }
1852
1853        #[test]
1854        fn format_local_date() {
1855            let dates = valid_test_dates().filter(|d| d.0 <= CHRONO_MAX_YEAR);
1856            for (y, m, d) in dates {
1857                let actual_value = LocalDate::from_ymd(y, m, d);
1858                let expected = NaiveDate::from_ymd_opt(y, m as u32, d as u32).unwrap();
1859
1860                check_display(expected, actual_value);
1861                check_debug(expected, actual_value);
1862            }
1863        }
1864
1865        #[test]
1866        fn format_local_datetime() {
1867            let dates = valid_test_dates().filter(|d| d.0 <= CHRONO_MAX_YEAR);
1868            for date in dates {
1869                for time in test_times() {
1870                    let actual_date = LocalDate::from_ymd(date.0, date.1, date.2);
1871                    let actual_time = LocalTime::from_micros(time);
1872                    let actual_value = LocalDatetime::new(actual_date, actual_time);
1873                    let expected_value = NaiveDateTime::from(actual_value);
1874
1875                    check_display(expected_value, actual_value);
1876                    check_debug(expected_value, actual_value);
1877                }
1878            }
1879        }
1880
1881        #[test]
1882        fn format_datetime() {
1883            let dates = valid_test_dates().filter(|d| d.0 <= CHRONO_MAX_YEAR);
1884            for date in dates {
1885                for time in test_times() {
1886                    let actual_date = LocalDate::from_ymd(date.0, date.1, date.2);
1887                    let actual_time = LocalTime::from_micros(time);
1888                    let local_datetime = LocalDatetime::new(actual_date, actual_time);
1889                    let actual_value = local_datetime.to_utc();
1890                    let expected_value = ChronoDatetime::from(actual_value);
1891
1892                    check_display(expected_value, actual_value);
1893                    check_debug(expected_value, actual_value);
1894                }
1895            }
1896        }
1897
1898        #[test]
1899        fn date_duration() -> Result<(), Box<dyn std::error::Error>> {
1900            assert_eq!(DateDuration::from_years(1).to_string(), "P1Y");
1901            assert_eq!(DateDuration::from_months(1).to_string(), "P1M");
1902            assert_eq!(DateDuration::from_days(1).to_string(), "P1D");
1903            assert_eq!(DateDuration::from_months(10).to_string(), "P10M");
1904            assert_eq!(DateDuration::from_months(20).to_string(), "P1Y8M");
1905            assert_eq!(DateDuration::from_days(131).to_string(), "P131D");
1906            assert_eq!(
1907                (DateDuration::from_months(7) + DateDuration::from_days(131)).to_string(),
1908                "P7M131D"
1909            );
1910            Ok(())
1911        }
1912    }
1913}