humanize_rs/time/
mod.rs

1//! This module is used to parse [`RFC3339`] datetime string
2//!
3//! # Example
4//! ```
5//! use humanize_rs::time::{Time, TimeZone};
6//!
7//! assert_eq!(
8//!     "2018-09-21T16:56:44.234867232+08:00".parse::<Time>(),
9//!     Ok(Time::from_timetuple(
10//!         2018,
11//!         9,
12//!         21,
13//!         16,
14//!         56,
15//!         44,
16//!         234867232,
17//!         TimeZone::new(8).unwrap(),
18//!     ).unwrap())
19//! );
20//! ```
21//!
22//! [`RFC3339`]: https://tools.ietf.org/html/rfc3339
23
24mod timezone;
25
26pub use self::timezone::*;
27
28use std::cmp::Ordering;
29use std::str::{from_utf8, FromStr};
30use std::time::{Duration, SystemTime};
31use ParseError;
32
33const MAX_SECONDS: u64 = 315569433600;
34const UNIX_EPOCH: Time = Time {
35    sec: 62167132800,
36    nano: 0,
37};
38
39const SECS_PER_MINUTE: u64 = 60;
40const SECS_PER_HOUR: u64 = 60 * SECS_PER_MINUTE;
41const SECS_PER_DAY: u64 = 24 * SECS_PER_HOUR;
42const DAYS_PER_400_YEARS: u32 = 365 * 400 + 97;
43const DAYS_PER_100_YEARS: u32 = 365 * 100 + 24;
44const DAYS_PER_4_YEARS: u32 = 365 * 4 + 1;
45const DAYS_BEFORE: [u32; 13] = [
46    0,
47    31,
48    31 + 28,
49    31 + 28 + 31,
50    31 + 28 + 31 + 30,
51    31 + 28 + 31 + 30 + 31,
52    31 + 28 + 31 + 30 + 31 + 30,
53    31 + 28 + 31 + 30 + 31 + 30 + 31,
54    31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
55    31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
56    31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
57    31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
58    31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
59];
60
61const DATE_TIME_FORMAT_MIN_LENGTH: usize = 10; // "2006-01-02"
62const DATE_TIME_FORMAT_WITH_TIME: usize = 19; // "2006-01-02T15:04:05"
63const DATE_TIME_FORMAT_MAX_LENGTH: usize = 35; // "2006-01-02T15:04:05.999999999Z07:00"
64
65/// Represents a time in range [0000-01-01T00:00:00Z, 10000-01-01T00:00:00Z)
66#[derive(Debug, Eq, PartialEq)]
67pub struct Time {
68    sec: u64,
69    nano: u32,
70}
71
72impl Time {
73    /// Represents `1970-01-01 00:00:00Z`
74    pub const UNIX_EPOCH: Time = UNIX_EPOCH;
75
76    /// Returns a Time with the given time tuple
77    pub fn from_timetuple(
78        year: u32,
79        month: u32,
80        day: u32,
81        hour: u32,
82        minute: u32,
83        second: u32,
84        nano: u32,
85        timezone: TimeZone,
86    ) -> Option<Time> {
87        if !in_range(year, 0, 10000)
88            || !in_range(month, 1, 12)
89            || !in_range(day, 1, 31)
90            || !in_range(hour, 0, 23)
91            || !in_range(minute, 0, 59)
92            || !in_range(second, 0, 59)
93            || !in_range(nano, 0, 1_000_000_000 - 1)
94        {
95            return None;
96        }
97
98        let is_leap = is_leap_year(year);
99
100        if !is_day_validate(is_leap, month, day) {
101            return None;
102        }
103
104        let mut d: u32 = 0;
105
106        let mut y = year;
107
108        let mut n: u32 = y / 400;
109        y -= 400 * n;
110        d += DAYS_PER_400_YEARS * n;
111
112        n = y / 100;
113        y -= n * 100;
114        d += DAYS_PER_100_YEARS * n;
115
116        n = y / 4;
117        y -= n * 4;
118        d += DAYS_PER_4_YEARS * n;
119
120        n = y;
121        d += 365 * n;
122
123        d += DAYS_BEFORE[(month - 1) as usize];
124        // already calculated in DAYS_PER_XX_YEARS
125        if year > 0 && is_leap && month <= 2 {
126            d -= 1;
127        }
128
129        d += day - 1;
130
131        let mut sec: u64 = d as u64 * SECS_PER_DAY
132            + hour as u64 * SECS_PER_HOUR
133            + minute as u64 * SECS_PER_MINUTE
134            + second as u64;
135
136        let offset = timezone.offset();
137        if offset >= 0 {
138            let minus = offset as u64;
139            if minus > sec {
140                return None;
141            }
142
143            sec -= minus;
144        } else {
145            sec += (-offset) as u64;
146        }
147
148        if sec >= MAX_SECONDS {
149            return None;
150        }
151
152        Some(Time {
153            sec: sec,
154            nano: nano,
155        })
156    }
157
158    /// Convert the time to SystemTime, returns None if the time is before unix epoch
159    pub fn to_system_time(&self) -> Option<SystemTime> {
160        if let Some(d) = self.since(&UNIX_EPOCH) {
161            return Some(SystemTime::UNIX_EPOCH + d);
162        }
163
164        None
165    }
166
167    /// Returns the duration since an earlier time, and None if earlier is not before self.
168    pub fn since(&self, earlier: &Time) -> Option<Duration> {
169        if self < earlier {
170            return None;
171        }
172
173        let mut sec = self.sec - earlier.sec;
174        let mut nano = self.nano;
175        if nano < earlier.nano {
176            sec -= 1;
177            nano += 1_000_000_000;
178        }
179        nano -= earlier.nano;
180
181        Some(Duration::new(sec, nano))
182    }
183}
184
185fn is_leap_year(y: u32) -> bool {
186    return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
187}
188
189fn in_range(n: u32, min: u32, max: u32) -> bool {
190    return min <= n && n <= max;
191}
192
193fn is_day_validate(is_leap: bool, m: u32, d: u32) -> bool {
194    match m {
195        2 if is_leap => d <= 29,
196        2 => d <= 28,
197        4 | 6 | 9 | 11 => d <= 30,
198        _ => d <= 31,
199    }
200}
201
202impl FromStr for Time {
203    type Err = ParseError;
204
205    fn from_str(s: &str) -> Result<Self, Self::Err> {
206        parse_rfc3339(s)
207    }
208}
209
210impl PartialOrd for Time {
211    fn partial_cmp(&self, other: &Time) -> Option<Ordering> {
212        Some(self.cmp(other))
213    }
214}
215
216impl Ord for Time {
217    fn cmp(&self, other: &Time) -> Ordering {
218        let ord = self.sec.cmp(&other.sec);
219        match ord {
220            Ordering::Equal => self.nano.cmp(&other.nano),
221            _ => ord,
222        }
223    }
224}
225
226/// Parses a [`RFC3339`] datetime string
227///
228/// [`RFC3339`]: https://tools.ietf.org/html/rfc3339
229pub fn parse_rfc3339(s: &str) -> Result<Time, ParseError> {
230    let bs = s.trim().as_bytes();
231    let size = bs.len();
232    if size == 0 {
233        return Err(ParseError::EmptyInput);
234    }
235
236    if size < DATE_TIME_FORMAT_MIN_LENGTH
237        || (size > DATE_TIME_FORMAT_MIN_LENGTH && size < DATE_TIME_FORMAT_WITH_TIME)
238    {
239        return Err(ParseError::TooShort);
240    }
241
242    if size > DATE_TIME_FORMAT_MAX_LENGTH {
243        return Err(ParseError::TooLong);
244    }
245
246    if !check_pattern(bs) {
247        return Err(ParseError::Malformed);
248    }
249
250    let year = read_u32(&bs[0..4])?;
251    let month = read_u32(&bs[5..7])?;
252    let day = read_u32(&bs[8..10])?;
253
254    let hour: u32;
255    let minute: u32;
256    let second: u32;
257    if size > DATE_TIME_FORMAT_MIN_LENGTH {
258        hour = read_u32(&bs[DATE_TIME_FORMAT_MIN_LENGTH + 1..DATE_TIME_FORMAT_MIN_LENGTH + 3])?;
259        minute = read_u32(&bs[DATE_TIME_FORMAT_MIN_LENGTH + 4..DATE_TIME_FORMAT_MIN_LENGTH + 6])?;
260        second = read_u32(&bs[DATE_TIME_FORMAT_MIN_LENGTH + 7..DATE_TIME_FORMAT_MIN_LENGTH + 9])?;
261    } else {
262        hour = 0;
263        minute = 0;
264        second = 0;
265    }
266
267    let nano: u32;
268    let tzstr: &str;
269    if size > DATE_TIME_FORMAT_WITH_TIME {
270        let tz_start: usize;
271        if bs[DATE_TIME_FORMAT_WITH_TIME] == b'.' {
272            let (v, read) = read_nano(&bs[DATE_TIME_FORMAT_WITH_TIME + 1..]);
273            if read == 0 {
274                return Err(ParseError::MissingValue);
275            }
276            nano = v;
277            tz_start = DATE_TIME_FORMAT_WITH_TIME + 1 + read;
278        } else {
279            nano = 0;
280            tz_start = DATE_TIME_FORMAT_WITH_TIME;
281        }
282
283        tzstr = from_utf8(&bs[tz_start..]).or(Err(ParseError::InvalidTimezone))?;
284    } else {
285        nano = 0;
286        tzstr = "";
287    }
288
289    let tz = tzstr.parse::<TimeZone>()?;
290
291    Time::from_timetuple(year, month, day, hour, minute, second, nano, tz)
292        .ok_or(ParseError::Overflow)
293}
294
295fn check_pattern(bs: &[u8]) -> bool {
296    if bs[4] != b'-' || bs[7] != b'-' {
297        return false;
298    }
299
300    if bs.len() > DATE_TIME_FORMAT_MIN_LENGTH {
301        if (bs[DATE_TIME_FORMAT_MIN_LENGTH] != b'T' && bs[DATE_TIME_FORMAT_MIN_LENGTH] != b' ')
302            || bs[DATE_TIME_FORMAT_MIN_LENGTH + 3] != b':'
303            || bs[DATE_TIME_FORMAT_MIN_LENGTH + 6] != b':'
304        {
305            return false;
306        }
307    }
308
309    if bs.len() > DATE_TIME_FORMAT_WITH_TIME {
310        if bs[DATE_TIME_FORMAT_WITH_TIME] != b'.'
311            && bs[DATE_TIME_FORMAT_WITH_TIME] != b'Z'
312            && bs[DATE_TIME_FORMAT_WITH_TIME] != b'+'
313            && bs[DATE_TIME_FORMAT_WITH_TIME] != b'-'
314        {
315            return false;
316        }
317    }
318
319    true
320}
321
322fn read_u32(bs: &[u8]) -> Result<u32, ParseError> {
323    let mut read: usize = 0;
324    let mut n: u32 = 0;
325
326    while read < bs.len() {
327        let c = bs[read];
328        if c < b'0' || c > b'9' {
329            return Err(ParseError::InvalidValue);
330        }
331
332        n = n * 10;
333        n += (c - b'0') as u32;
334
335        read += 1;
336    }
337
338    Ok(n)
339}
340
341fn read_nano(bs: &[u8]) -> (u32, usize) {
342    let mut read: usize = 0;
343    let mut n: u32 = 0;
344
345    while read < bs.len() && read <= 9 {
346        let c = bs[read];
347        if c < b'0' || c > b'9' {
348            break;
349        }
350
351        n = n * 10;
352        n += (c - b'0') as u32;
353
354        read += 1;
355    }
356
357    if read < 9 {
358        n = n * 10_u32.pow((9 - read) as u32);
359    }
360
361    (n, read)
362}
363
364#[cfg(test)]
365mod tests;