• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

baoyachi / shadow-rs / 24725141756

21 Apr 2026 01:29PM UTC coverage: 76.989% (+0.1%) from 76.89%
24725141756

Pull #259

github

web-flow
Merge 2fd74c8ba into 544adb473
Pull Request #259: refactor: use jiff as the datetime lib

34 of 41 new or added lines in 2 files covered. (82.93%)

5 existing lines in 1 file now uncovered.

542 of 704 relevant lines covered (76.99%)

1.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.17
/src/date_time.rs
1
use crate::{Format, SdResult, ShadowError};
2

3
pub struct DateTime(jiff::Zoned);
4

5
pub(crate) const DEFINE_SOURCE_DATE_EPOCH: &str = "SOURCE_DATE_EPOCH";
6

7
pub fn now_date_time() -> DateTime {
1✔
8
    // Enable reproducibility for uses of `now_date_time` by respecting the
9
    // `SOURCE_DATE_EPOCH` env variable.
10
    //
11
    // https://reproducible-builds.org/docs/source-date-epoch/
12
    match std::env::var_os(DEFINE_SOURCE_DATE_EPOCH) {
1✔
13
        None => DateTime::now(),
×
14
        Some(timestamp) => {
1✔
15
            let epoch = timestamp
3✔
16
                .into_string()
17
                .unwrap_or_else(|_| panic!("Input {DEFINE_SOURCE_DATE_EPOCH} could not be parsed"))
1✔
18
                .parse::<i64>()
19
                .unwrap_or_else(|_| {
1✔
20
                    panic!("Input {DEFINE_SOURCE_DATE_EPOCH} could not be cast to a number")
×
21
                });
22
            DateTime(
23
                jiff::Timestamp::from_second(epoch)
1✔
24
                    .unwrap()
1✔
25
                    .to_zoned(jiff::tz::TimeZone::UTC),
1✔
26
            )
27
        }
28
    }
29
}
30

31
impl Default for DateTime {
32
    fn default() -> Self {
×
33
        Self::now()
×
34
    }
35
}
36

37
impl DateTime {
38
    pub fn new(zoned: jiff::Zoned) -> Self {
1✔
39
        Self(zoned)
1✔
40
    }
41

NEW
42
    pub fn now() -> Self {
×
NEW
43
        Self(jiff::Zoned::now())
×
44
    }
45

46
    pub fn timestamp_2_utc(time_stamp: i64) -> SdResult<Self> {
1✔
47
        let utc_time = jiff::Timestamp::from_second(time_stamp).map_err(ShadowError::new)?;
1✔
48
        let zoned = utc_time.to_zoned(jiff::tz::TimeZone::UTC);
1✔
49
        Ok(DateTime::new(zoned))
1✔
50
    }
51

52
    pub fn from_iso8601_string(iso_string: &str) -> SdResult<Self> {
1✔
53
        let pieces = jiff::fmt::temporal::Pieces::parse(iso_string).map_err(ShadowError::new)?;
1✔
54

55
        let time = match pieces.time() {
2✔
56
            Some(time) => time,
1✔
57
            None => {
NEW
58
                return Err(ShadowError::from(
×
NEW
59
                    "iso string has no time, and thus cannot be parsed into a datetime".to_string(),
×
60
                ));
61
            }
62
        };
63
        let dt = pieces.date().to_datetime(time);
2✔
64
        let offset = match pieces.to_numeric_offset() {
1✔
65
            Some(offset) => offset,
1✔
66
            None => {
NEW
67
                return Err(ShadowError::from(
×
68
                    "iso string has no offset, and thus cannot be parsed into a datetime"
NEW
69
                        .to_string(),
×
70
                ));
71
            }
72
        };
73
        let zoned = jiff::tz::TimeZone::fixed(offset)
3✔
74
            .to_zoned(dt)
1✔
75
            .map_err(ShadowError::new)?;
1✔
76

77
        Ok(DateTime::new(zoned))
1✔
78
    }
79

80
    pub fn to_rfc2822(&self) -> String {
1✔
81
        jiff::fmt::rfc2822::to_string(&self.0).unwrap_or_default()
1✔
82
    }
83

84
    pub fn to_rfc3339(&self) -> String {
1✔
85
        let ts = self.0.timestamp();
1✔
86
        let offset = self.0.offset();
1✔
87
        if self.0.time_zone() == &jiff::tz::TimeZone::UTC {
2✔
88
            ts.to_string()
1✔
89
        } else {
90
            ts.display_with_offset(offset).to_string()
1✔
91
        }
92
    }
93

94
    pub fn timestamp(&self) -> i64 {
1✔
95
        self.0.timestamp().as_second()
1✔
96
    }
97
}
98

99
impl Format for DateTime {
100
    fn human_format(&self) -> String {
1✔
101
        self.0.strftime("%Y-%m-%d %H:%M:%S %:z").to_string()
1✔
102
    }
103
}
104

105
#[cfg(test)]
106
mod tests {
107
    use super::*;
108

109
    mod human_format_validate {
110
        use std::num::{NonZeroU32, NonZeroU8};
111
        use winnow::ascii::{digit1, space1};
112
        use winnow::error::{ContextError, ParseError};
113
        use winnow::token::{literal, take};
114
        use winnow::{ModalResult, Parser};
115

116
        fn u8_len2(input: &mut &str) -> ModalResult<u8> {
117
            take(2_usize).try_map(str::parse).parse_next(input)
118
        }
119

120
        fn non_zero_u8_len2<const LIMIT: u8>(input: &mut &str) -> ModalResult<NonZeroU8> {
121
            take(2_usize)
122
                .try_map(str::parse)
123
                .verify(|x| *x <= unsafe { NonZeroU8::new_unchecked(LIMIT) })
124
                .parse_next(input)
125
        }
126

127
        //
128
        fn non_zero_u32(input: &mut &str) -> ModalResult<NonZeroU32> {
129
            digit1.try_map(str::parse).parse_next(input)
130
        }
131

132
        // 2022-07-14 00:40:05 +08:00
133
        pub(crate) fn parse_human_format(
134
            input: &str,
135
        ) -> Result<(), ParseError<&str, ContextError>> {
136
            (
137
                non_zero_u32,
138
                literal('-'),
139
                non_zero_u8_len2::<12>,
140
                literal('-'),
141
                non_zero_u8_len2::<31>,
142
                space1,
143
                u8_len2,
144
                literal(':'),
145
                u8_len2,
146
                literal(':'),
147
                u8_len2,
148
                space1,
149
                literal('+'),
150
                u8_len2,
151
                literal(':'),
152
                u8_len2,
153
            )
154
                .parse(input)?;
155
            Ok(())
156
        }
157

158
        #[test]
159
        fn test_parse() {
160
            assert!(parse_human_format("2022-07-14 00:40:05 +08:00").is_ok());
161
            assert!(parse_human_format("2022-12-14 00:40:05 +08:00").is_ok());
162
            assert!(parse_human_format("2022-13-14 00:40:05 +08:00").is_err());
163
            assert!(parse_human_format("2022-12-31 00:40:05 +08:00").is_ok());
164
            assert!(parse_human_format("2022-12-32 00:40:05 +08:00").is_err());
165
            assert!(parse_human_format("2022-07-14 00:40:05 +08:0").is_err());
166
            assert!(parse_human_format("2022-07-14 00:40:05 -08:0").is_err());
167
            assert!(parse_human_format("2022-07-00 00:40:05 +08:00").is_err());
168
            assert!(parse_human_format("2022-00-01 00:40:05 +08:00").is_err());
169
            assert!(parse_human_format("2022-00-01 00:40:05 08:00").is_err());
170
            assert!(parse_human_format("2022-00-01 00:40:05+08:00").is_err());
171
            assert!(parse_human_format("20221-00-01 00:40:05+08:00").is_err());
172
            assert!(parse_human_format("20221-01-01 00:40:05 +08:00").is_ok());
173
        }
174
    }
175

176
    #[test]
177
    fn test_source_date_epoch() {
178
        std::env::set_var(DEFINE_SOURCE_DATE_EPOCH, "1628080443");
179
        let time = now_date_time();
180
        assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
181
    }
182

183
    #[test]
184
    fn test_timestamp_2_utc() {
185
        let time = DateTime::timestamp_2_utc(1628080443).unwrap();
186
        assert_eq!(time.to_rfc2822(), "Wed, 4 Aug 2021 12:34:03 +0000");
187
        assert_eq!(time.to_rfc3339(), "2021-08-04T12:34:03Z");
188
        assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
189
        assert_eq!(time.timestamp(), 1628080443);
190
    }
191

192
    #[test]
193
    fn test_from_iso8601_string() {
194
        let time = DateTime::from_iso8601_string("2021-08-04T12:34:03+08:00").unwrap();
195
        assert_eq!(time.to_rfc2822(), "Wed, 4 Aug 2021 12:34:03 +0800");
196
        assert_eq!(time.to_rfc3339(), "2021-08-04T12:34:03+08:00");
197
        assert_eq!(time.human_format(), "2021-08-04 12:34:03 +08:00");
198
    }
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc