iso8601/
duration.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use core::str::FromStr;

use alloc::string::String;

use crate::parsers;

/// A time duration.
/// Durations:
/// <https://www.rfc-editor.org/rfc/rfc3339#page-13>
///    dur-second        = 1*DIGIT "S"
///    dur-minute        = 1*DIGIT "M" [dur-second]
///    dur-hour          = 1*DIGIT "H" [dur-minute]
///    dur-time          = "T" (dur-hour / dur-minute / dur-second)
///    dur-day           = 1*DIGIT "D"
///    dur-week          = 1*DIGIT "W"
///    dur-month         = 1*DIGIT "M" [dur-day]
///    dur-year          = 1*DIGIT "Y" [dur-month]
///    dur-date          = (dur-day / dur-month / dur-year) [dur-time]
///    duration          = "P" (dur-date / dur-time / dur-week)
/// ```
///# use std::str::FromStr;
/// assert_eq!(iso8601::Duration::from_str("P2021Y11M16DT23H26M59.123S"), Ok(iso8601::Duration::YMDHMS{ year: 2021, month: 11, day: 16, hour: 23, minute: 26, second: 59, millisecond: 123 }))
/// ```
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum Duration {
    /// A duration specified by year, month, day, hour, minute and second units
    YMDHMS {
        /// Number of calendar years
        year: u32,
        /// Number of months
        month: u32,
        /// Number of days
        day: u32,
        /// Number of hours
        hour: u32,
        /// Number of minutes
        minute: u32,
        /// Number of seconds
        second: u32,
        /// Number of milliseconds
        millisecond: u32,
    },
    /// consists of week units
    Weeks(u32),
}

impl Duration {
    /// Whether this duration represents a zero duration.
    pub fn is_zero(&self) -> bool {
        *self
            == Duration::YMDHMS {
                year: 0,
                month: 0,
                day: 0,
                hour: 0,
                minute: 0,
                second: 0,
                millisecond: 0,
            }
            || *self == Duration::Weeks(0)
    }
}

impl Default for Duration {
    fn default() -> Duration {
        Duration::YMDHMS {
            year: 0,
            month: 0,
            day: 0,
            hour: 0,
            minute: 0,
            second: 0,
            millisecond: 0,
        }
    }
}

impl FromStr for Duration {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        duration(s)
    }
}

impl From<Duration> for ::core::time::Duration {
    fn from(duration: Duration) -> Self {
        match duration {
            Duration::YMDHMS {
                year,
                month,
                day,
                hour,
                minute,
                second,
                millisecond,
            } => {
                let secs = u64::from(year) * 365 * 86_400
                    + u64::from(month) * 30 * 86_400
                    + u64::from(day) * 86_400
                    + u64::from(hour) * 3600
                    + u64::from(minute) * 60
                    + u64::from(second);
                let nanos = millisecond * 1_000_000;
                Self::new(secs, nanos)
            }
            Duration::Weeks(week) => {
                let secs = u64::from(week) * 7 * 86_400;
                Self::from_secs(secs)
            }
        }
    }
}

/// Parses a duration string.
///
/// A string starts with `P` and can have one of the following formats:
///
/// * Fully-specified duration: `P1Y2M3DT4H5M6S`
/// * Duration in weekly intervals: `P1W`
/// * Fully-specified duration in [`DateTime`](`crate::DateTime`) format: `P<datetime>`
///
/// Both fully-specified formats get parsed into the YMDHMS Duration variant.
/// The weekly interval format gets parsed into the Weeks Duration variant.
///
/// The ranges for each of the individual units are not expected to exceed
/// the next largest unit.
///
/// These ranges (inclusive) are as follows:
///
/// * Year (any valid u32)
/// * Month 0 - 12
/// * Week 0 - 52
/// * Day 0 - 31
/// * Hour 0 - 24
/// * Minute 0 - 60
/// * Second 0 - 60
///
/// ## Examples
///
/// ```rust
/// let duration = iso8601::duration("P1Y2M3DT4H5M6S").unwrap();
/// let duration = iso8601::duration("P1W").unwrap();
/// let duration = iso8601::duration("P2015-11-03T21:56").unwrap();
/// ```
pub fn duration(string: &str) -> Result<Duration, String> {
    if let Ok((_left_overs, parsed)) = parsers::parse_duration(string.as_bytes()) {
        Ok(parsed)
    } else {
        Err(format!("Failed to parse duration: {}", string))
    }
}