1use crate::{Roundable, Tie};
4use core::time::Duration;
5
6pub const MICROSECOND: Duration = Duration::from_micros(1);
15
16pub const MILLISECOND: Duration = Duration::from_millis(1);
25
26pub const SECOND: Duration = Duration::from_secs(1);
35
36pub const MINUTE: Duration = Duration::from_secs(60);
45
46pub const HOUR: Duration = Duration::from_secs(60 * 60);
55
56impl Roundable for Duration {
57 fn try_round_to(self, factor: Self, tie: Tie) -> Option<Self> {
58 self.as_nanos()
60 .try_round_to(factor.as_nanos(), tie)
61 .map(nanos_to_duration)
62 }
63}
64
65#[must_use]
87pub fn nanos_to_duration(total: u128) -> Duration {
88 const NANOS_PER_SECOND: u128 = 1_000_000_000;
90 #[allow(clippy::integer_division)]
91 Duration::new(
92 (total / NANOS_PER_SECOND).try_into().expect(
93 "nanos_to_duration() overflowed seconds value for Duration",
94 ),
95 (total % NANOS_PER_SECOND).try_into().unwrap(),
96 )
97}
98
99#[cfg(test)]
100#[allow(clippy::cognitive_complexity)]
101mod tests {
102 use super::*;
103 use assert2::check;
104
105 const fn ms(ms: u64) -> Duration {
107 Duration::from_millis(ms)
108 }
109
110 #[test]
111 fn round_millisecond_to_nearest_millisecond() {
112 check!(ms(10) == ms(10).round_to(MILLISECOND, Tie::Up));
113
114 check!(ms(10) == ms(10).round_to(ms(2), Tie::Up));
115 check!(ms(10) == ms(9).round_to(ms(2), Tie::Up));
116
117 check!(ms(9) == ms(9).round_to(ms(3), Tie::Up));
118 check!(ms(9) == ms(10).round_to(ms(3), Tie::Up));
119 check!(ms(12) == ms(11).round_to(ms(3), Tie::Up));
120 check!(ms(12) == ms(12).round_to(ms(3), Tie::Up));
121 }
122
123 #[test]
124 fn round_second_to_nearest_millisecond() {
125 check!(ms(1_010) == ms(1_010).round_to(MILLISECOND, Tie::Up));
126
127 check!(ms(1_010) == ms(1_010).round_to(ms(2), Tie::Up));
128 check!(ms(1_010) == ms(1_009).round_to(ms(2), Tie::Up));
129
130 check!(ms(1_008) == ms(1_008).round_to(ms(3), Tie::Up));
131 check!(ms(1_008) == ms(1_009).round_to(ms(3), Tie::Up));
132 check!(ms(1_011) == ms(1_010).round_to(ms(3), Tie::Up));
133 check!(ms(1_011) == ms(1_011).round_to(ms(3), Tie::Up));
134 }
135
136 #[test]
137 fn round_second_to_nearest_second() {
138 check!(ms(0) == ms(499).round_to(SECOND, Tie::Up));
139 check!(SECOND == ms(500).round_to(SECOND, Tie::Up));
140 check!(SECOND == ms(1_010).round_to(SECOND, Tie::Up));
141 check!(SECOND == ms(1_499).round_to(SECOND, Tie::Up));
142 check!(ms(2_000) == ms(1_500).round_to(SECOND, Tie::Up));
143
144 check!(ms(1_001) == ms(1_000).round_to(ms(1_001), Tie::Up));
145 check!(ms(1_001) == ms(1_001).round_to(ms(1_001), Tie::Up));
146 check!(ms(1_001) == ms(1_002).round_to(ms(1_001), Tie::Up));
147 }
148
149 #[test]
150 fn round_to_giant_factor() {
151 check!(ms(0) == ms(1_000_000).round_to(Duration::MAX, Tie::Up));
152 check!(Duration::MAX == Duration::MAX.round_to(Duration::MAX, Tie::Up));
153 }
154
155 #[test]
156 #[should_panic(expected = "try_round_to() requires positive factor")]
157 fn round_to_zero_factor() {
158 let _ = ms(10).round_to(ms(0), Tie::Up);
159 }
160
161 const NANOS_MAX: u128 = u64::MAX as u128 * 1_000_000_000 + 999_999_999;
163
164 #[test]
165 #[allow(clippy::arithmetic_side_effects)]
166 fn nanos_to_duration_ok() {
167 check!(Duration::ZERO == nanos_to_duration(0));
168 check!(Duration::new(1, 1) == nanos_to_duration(1_000_000_001));
169
170 check!(Duration::MAX == nanos_to_duration(Duration::MAX.as_nanos()));
173 check!(
174 Duration::new(u64::MAX, 999_999_999)
175 == nanos_to_duration(NANOS_MAX)
176 );
177 }
178
179 #[test]
180 #[should_panic(
181 expected = "nanos_to_duration() overflowed seconds value for Duration: TryFromIntError(())"
182 )]
183 fn nanos_to_duration_overflow() {
184 let _ = nanos_to_duration(Duration::MAX.as_nanos() + 1);
185 }
186
187 #[test]
188 #[should_panic(
189 expected = "nanos_to_duration() overflowed seconds value for Duration: TryFromIntError(())"
190 )]
191 #[allow(clippy::arithmetic_side_effects)]
192 fn nanos_to_duration_overflow_manual() {
193 let _ = nanos_to_duration(NANOS_MAX + 1);
197 }
198}