summaryrefslogtreecommitdiff
path: root/src/time.rs
blob: a9f35023de38fd535f0f078abdf56d6ad084f72f (plain)
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
use log::trace;
use std::time::{Duration, SystemTime};
use time::{
    format_description::well_known::{Iso8601, Rfc2822},
    format_description::FormatItem,
    macros::format_description,
    parsing::Parsable,
    OffsetDateTime, PrimitiveDateTime,
};

#[derive(Debug, thiserror::Error)]
pub enum TimeError {
    #[error("failed to parse date: {0:?}")]
    UnknownTimestamp(String),
}

pub fn parse_timestamp(timestamp: &str) -> Result<SystemTime, TimeError> {
    trace!("parsing timestamp {:?}", timestamp);
    let odt = parse(timestamp)?;
    let unix = odt.unix_timestamp();
    trace!("unix: {}", unix);
    let sys = system_time(unix);
    trace!("system time: {:?}", sys);
    Ok(system_time(unix))
}

fn system_time(unix: i64) -> SystemTime {
    let offset = Duration::from_secs(unix as u64);
    SystemTime::UNIX_EPOCH.checked_add(offset).unwrap()
}

fn parse(timestamp: &str) -> Result<OffsetDateTime, TimeError> {
    const SIMPLIFIED_ISO9601: &[FormatItem<'static>] =
        format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
    const SIMPLIFIED_ISO9601_MIN: &[FormatItem<'static>] =
        format_description!("[year]-[month]-[day] [hour]:[minute]");
    const SIMPLIFIED_ISO9601_TZ: &[FormatItem<'static>] = format_description!(
        "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour][offset_minute]"
    );
    const SIMPLIFIED_ISO9601_MIN_TZ: &[FormatItem<'static>] =
        format_description!("[year]-[month]-[day] [hour]:[minute] [offset_hour][offset_minute]");

    if let Ok(t) = parse_one_time_format(timestamp, "simplified", SIMPLIFIED_ISO9601) {
        Ok(t)
    } else if let Ok(t) = parse_one_time_format(timestamp, "simplified-min", SIMPLIFIED_ISO9601_MIN)
    {
        Ok(t)
    } else if let Ok(t) = parse_one_time_format(timestamp, "simplified-tz", SIMPLIFIED_ISO9601_TZ) {
        Ok(t)
    } else if let Ok(t) =
        parse_one_time_format(timestamp, "simplified-tz", SIMPLIFIED_ISO9601_MIN_TZ)
    {
        Ok(t)
    } else if let Ok(t) = parse_one_time_format(timestamp, "ISO8601", &Iso8601::PARSING) {
        Ok(t)
    } else if let Ok(t) = parse_one_time_format(timestamp, "RFC2822", &Rfc2822) {
        Ok(t)
    } else {
        Err(TimeError::UnknownTimestamp(timestamp.into()))
    }
}

fn parse_one_time_format(
    timestamp: &str,
    what: &str,
    fmt: &(impl Parsable + ?Sized),
) -> Result<OffsetDateTime, time::error::Parse> {
    trace!("trying to parse using: {}", what);
    let r = PrimitiveDateTime::parse(timestamp, fmt);
    if let Ok(t) = r {
        Ok(t.assume_utc())
    } else {
        let e = r.err().unwrap();
        trace!("error: {}", e);
        Err(e)
    }
}

#[cfg(test)]
mod test {
    use super::parse;

    fn unix_time(t: &str) -> i64 {
        parse(t).unwrap().unix_timestamp()
    }

    #[test]
    fn simple() {
        assert_eq!(unix_time("1970-01-01 00:00:00"), 0);
    }

    #[test]
    fn simple_with_minutes_only() {
        assert_eq!(unix_time("1970-01-01 00:00"), 0);
    }

    #[test]
    fn simple_with_tz() {
        assert_eq!(unix_time("1970-01-01 00:00:00 +0000"), 0);
    }

    #[test]
    fn iso9601() {
        assert_eq!(unix_time("1970-01-01T00:00:00+00:00"), 0);
    }

    #[test]
    fn rfc2822() {
        assert_eq!(unix_time("Thu, 01 Jan 1970 00:00:00 +0000"), 0);
    }
}