1 | // This is a part of Chrono. |
2 | // See README.md and LICENSE.txt for details. |
3 | |
4 | use crate::datetime::DateTime; |
5 | use crate::oldtime::Duration; |
6 | use crate::NaiveDateTime; |
7 | use crate::TimeZone; |
8 | use crate::Timelike; |
9 | use core::cmp::Ordering; |
10 | use core::fmt; |
11 | use core::marker::Sized; |
12 | use core::ops::{Add, Sub}; |
13 | |
14 | /// Extension trait for subsecond rounding or truncation to a maximum number |
15 | /// of digits. Rounding can be used to decrease the error variance when |
16 | /// serializing/persisting to lower precision. Truncation is the default |
17 | /// behavior in Chrono display formatting. Either can be used to guarantee |
18 | /// equality (e.g. for testing) when round-tripping through a lower precision |
19 | /// format. |
20 | pub trait SubsecRound { |
21 | /// Return a copy rounded to the specified number of subsecond digits. With |
22 | /// 9 or more digits, self is returned unmodified. Halfway values are |
23 | /// rounded up (away from zero). |
24 | /// |
25 | /// # Example |
26 | /// ``` rust |
27 | /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; |
28 | /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); |
29 | /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); |
30 | /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); |
31 | /// ``` |
32 | fn round_subsecs(self, digits: u16) -> Self; |
33 | |
34 | /// Return a copy truncated to the specified number of subsecond |
35 | /// digits. With 9 or more digits, self is returned unmodified. |
36 | /// |
37 | /// # Example |
38 | /// ``` rust |
39 | /// # use chrono::{SubsecRound, Timelike, Utc, NaiveDate}; |
40 | /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); |
41 | /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); |
42 | /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); |
43 | /// ``` |
44 | fn trunc_subsecs(self, digits: u16) -> Self; |
45 | } |
46 | |
47 | impl<T> SubsecRound for T |
48 | where |
49 | T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>, |
50 | { |
51 | fn round_subsecs(self, digits: u16) -> T { |
52 | let span = span_for_digits(digits); |
53 | let delta_down = self.nanosecond() % span; |
54 | if delta_down > 0 { |
55 | let delta_up = span - delta_down; |
56 | if delta_up <= delta_down { |
57 | self + Duration::nanoseconds(delta_up.into()) |
58 | } else { |
59 | self - Duration::nanoseconds(delta_down.into()) |
60 | } |
61 | } else { |
62 | self // unchanged |
63 | } |
64 | } |
65 | |
66 | fn trunc_subsecs(self, digits: u16) -> T { |
67 | let span = span_for_digits(digits); |
68 | let delta_down = self.nanosecond() % span; |
69 | if delta_down > 0 { |
70 | self - Duration::nanoseconds(delta_down.into()) |
71 | } else { |
72 | self // unchanged |
73 | } |
74 | } |
75 | } |
76 | |
77 | // Return the maximum span in nanoseconds for the target number of digits. |
78 | const fn span_for_digits(digits: u16) -> u32 { |
79 | // fast lookup form of: 10^(9-min(9,digits)) |
80 | match digits { |
81 | 0 => 1_000_000_000, |
82 | 1 => 100_000_000, |
83 | 2 => 10_000_000, |
84 | 3 => 1_000_000, |
85 | 4 => 100_000, |
86 | 5 => 10_000, |
87 | 6 => 1_000, |
88 | 7 => 100, |
89 | 8 => 10, |
90 | _ => 1, |
91 | } |
92 | } |
93 | |
94 | /// Extension trait for rounding or truncating a DateTime by a Duration. |
95 | /// |
96 | /// # Limitations |
97 | /// Both rounding and truncating are done via [`Duration::num_nanoseconds`] and |
98 | /// [`DateTime::timestamp_nanos`]. This means that they will fail if either the |
99 | /// `Duration` or the `DateTime` are too big to represented as nanoseconds. They |
100 | /// will also fail if the `Duration` is bigger than the timestamp. |
101 | pub trait DurationRound: Sized { |
102 | /// Error that can occur in rounding or truncating |
103 | #[cfg (any(feature = "std" , test))] |
104 | type Err: std::error::Error; |
105 | |
106 | /// Error that can occur in rounding or truncating |
107 | #[cfg (not(any(feature = "std" , test)))] |
108 | type Err: fmt::Debug + fmt::Display; |
109 | |
110 | /// Return a copy rounded by Duration. |
111 | /// |
112 | /// # Example |
113 | /// ``` rust |
114 | /// # use chrono::{DurationRound, Duration, Utc, NaiveDate}; |
115 | /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); |
116 | /// assert_eq!( |
117 | /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), |
118 | /// "2018-01-11 12:00:00.150 UTC" |
119 | /// ); |
120 | /// assert_eq!( |
121 | /// dt.duration_round(Duration::days(1)).unwrap().to_string(), |
122 | /// "2018-01-12 00:00:00 UTC" |
123 | /// ); |
124 | /// ``` |
125 | fn duration_round(self, duration: Duration) -> Result<Self, Self::Err>; |
126 | |
127 | /// Return a copy truncated by Duration. |
128 | /// |
129 | /// # Example |
130 | /// ``` rust |
131 | /// # use chrono::{DurationRound, Duration, Utc, NaiveDate}; |
132 | /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); |
133 | /// assert_eq!( |
134 | /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), |
135 | /// "2018-01-11 12:00:00.150 UTC" |
136 | /// ); |
137 | /// assert_eq!( |
138 | /// dt.duration_trunc(Duration::days(1)).unwrap().to_string(), |
139 | /// "2018-01-11 00:00:00 UTC" |
140 | /// ); |
141 | /// ``` |
142 | fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err>; |
143 | } |
144 | |
145 | /// The maximum number of seconds a DateTime can be to be represented as nanoseconds |
146 | const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036; |
147 | |
148 | impl<Tz: TimeZone> DurationRound for DateTime<Tz> { |
149 | type Err = RoundingError; |
150 | |
151 | fn duration_round(self, duration: Duration) -> Result<Self, Self::Err> { |
152 | duration_round(self.naive_local(), self, duration) |
153 | } |
154 | |
155 | fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err> { |
156 | duration_trunc(self.naive_local(), self, duration) |
157 | } |
158 | } |
159 | |
160 | impl DurationRound for NaiveDateTime { |
161 | type Err = RoundingError; |
162 | |
163 | fn duration_round(self, duration: Duration) -> Result<Self, Self::Err> { |
164 | duration_round(self, self, duration) |
165 | } |
166 | |
167 | fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err> { |
168 | duration_trunc(self, self, duration) |
169 | } |
170 | } |
171 | |
172 | fn duration_round<T>( |
173 | naive: NaiveDateTime, |
174 | original: T, |
175 | duration: Duration, |
176 | ) -> Result<T, RoundingError> |
177 | where |
178 | T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>, |
179 | { |
180 | if let Some(span) = duration.num_nanoseconds() { |
181 | if span < 0 { |
182 | return Err(RoundingError::DurationExceedsLimit); |
183 | } |
184 | if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { |
185 | return Err(RoundingError::TimestampExceedsLimit); |
186 | } |
187 | let stamp = naive.timestamp_nanos(); |
188 | if span > stamp.abs() { |
189 | return Err(RoundingError::DurationExceedsTimestamp); |
190 | } |
191 | if span == 0 { |
192 | return Ok(original); |
193 | } |
194 | let delta_down = stamp % span; |
195 | if delta_down == 0 { |
196 | Ok(original) |
197 | } else { |
198 | let (delta_up, delta_down) = if delta_down < 0 { |
199 | (delta_down.abs(), span - delta_down.abs()) |
200 | } else { |
201 | (span - delta_down, delta_down) |
202 | }; |
203 | if delta_up <= delta_down { |
204 | Ok(original + Duration::nanoseconds(delta_up)) |
205 | } else { |
206 | Ok(original - Duration::nanoseconds(delta_down)) |
207 | } |
208 | } |
209 | } else { |
210 | Err(RoundingError::DurationExceedsLimit) |
211 | } |
212 | } |
213 | |
214 | fn duration_trunc<T>( |
215 | naive: NaiveDateTime, |
216 | original: T, |
217 | duration: Duration, |
218 | ) -> Result<T, RoundingError> |
219 | where |
220 | T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>, |
221 | { |
222 | if let Some(span: i64) = duration.num_nanoseconds() { |
223 | if span < 0 { |
224 | return Err(RoundingError::DurationExceedsLimit); |
225 | } |
226 | if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { |
227 | return Err(RoundingError::TimestampExceedsLimit); |
228 | } |
229 | let stamp: i64 = naive.timestamp_nanos(); |
230 | if span > stamp.abs() { |
231 | return Err(RoundingError::DurationExceedsTimestamp); |
232 | } |
233 | let delta_down: i64 = stamp % span; |
234 | match delta_down.cmp(&0) { |
235 | Ordering::Equal => Ok(original), |
236 | Ordering::Greater => Ok(original - Duration::nanoseconds(nanos:delta_down)), |
237 | Ordering::Less => Ok(original - Duration::nanoseconds(nanos:span - delta_down.abs())), |
238 | } |
239 | } else { |
240 | Err(RoundingError::DurationExceedsLimit) |
241 | } |
242 | } |
243 | |
244 | /// An error from rounding by `Duration` |
245 | /// |
246 | /// See: [`DurationRound`] |
247 | #[derive (Debug, Clone, PartialEq, Eq, Copy)] |
248 | pub enum RoundingError { |
249 | /// Error when the Duration exceeds the Duration from or until the Unix epoch. |
250 | /// |
251 | /// ``` rust |
252 | /// # use chrono::{DurationRound, Duration, RoundingError, TimeZone, Utc}; |
253 | /// let dt = Utc.with_ymd_and_hms(1970, 12, 12, 0, 0, 0).unwrap(); |
254 | /// |
255 | /// assert_eq!( |
256 | /// dt.duration_round(Duration::days(365)), |
257 | /// Err(RoundingError::DurationExceedsTimestamp), |
258 | /// ); |
259 | /// ``` |
260 | DurationExceedsTimestamp, |
261 | |
262 | /// Error when `Duration.num_nanoseconds` exceeds the limit. |
263 | /// |
264 | /// ``` rust |
265 | /// # use chrono::{DurationRound, Duration, RoundingError, Utc, NaiveDate}; |
266 | /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_75_500_000).unwrap().and_local_timezone(Utc).unwrap(); |
267 | /// |
268 | /// assert_eq!( |
269 | /// dt.duration_round(Duration::days(300 * 365)), |
270 | /// Err(RoundingError::DurationExceedsLimit) |
271 | /// ); |
272 | /// ``` |
273 | DurationExceedsLimit, |
274 | |
275 | /// Error when `DateTime.timestamp_nanos` exceeds the limit. |
276 | /// |
277 | /// ``` rust |
278 | /// # use chrono::{DurationRound, Duration, RoundingError, TimeZone, Utc}; |
279 | /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); |
280 | /// |
281 | /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); |
282 | /// ``` |
283 | TimestampExceedsLimit, |
284 | } |
285 | |
286 | impl fmt::Display for RoundingError { |
287 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
288 | match *self { |
289 | RoundingError::DurationExceedsTimestamp => { |
290 | write!(f, "duration in nanoseconds exceeds timestamp" ) |
291 | } |
292 | RoundingError::DurationExceedsLimit => { |
293 | write!(f, "duration exceeds num_nanoseconds limit" ) |
294 | } |
295 | RoundingError::TimestampExceedsLimit => { |
296 | write!(f, "timestamp exceeds num_nanoseconds limit" ) |
297 | } |
298 | } |
299 | } |
300 | } |
301 | |
302 | #[cfg (any(feature = "std" , test))] |
303 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
304 | impl std::error::Error for RoundingError { |
305 | #[allow (deprecated)] |
306 | fn description(&self) -> &str { |
307 | "error from rounding or truncating with DurationRound" |
308 | } |
309 | } |
310 | |
311 | #[cfg (test)] |
312 | mod tests { |
313 | use super::{Duration, DurationRound, RoundingError, SubsecRound}; |
314 | use crate::offset::{FixedOffset, TimeZone, Utc}; |
315 | use crate::Timelike; |
316 | use crate::{NaiveDate, NaiveDateTime}; |
317 | |
318 | #[test ] |
319 | fn test_round_subsecs() { |
320 | let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); |
321 | let dt = pst |
322 | .from_local_datetime( |
323 | &NaiveDate::from_ymd_opt(2018, 1, 11) |
324 | .unwrap() |
325 | .and_hms_nano_opt(10, 5, 13, 84_660_684) |
326 | .unwrap(), |
327 | ) |
328 | .unwrap(); |
329 | |
330 | assert_eq!(dt.round_subsecs(10), dt); |
331 | assert_eq!(dt.round_subsecs(9), dt); |
332 | assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680); |
333 | assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700); |
334 | assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000); |
335 | assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000); |
336 | assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000); |
337 | assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000); |
338 | assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000); |
339 | assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000); |
340 | |
341 | assert_eq!(dt.round_subsecs(0).nanosecond(), 0); |
342 | assert_eq!(dt.round_subsecs(0).second(), 13); |
343 | |
344 | let dt = Utc |
345 | .from_local_datetime( |
346 | &NaiveDate::from_ymd_opt(2018, 1, 11) |
347 | .unwrap() |
348 | .and_hms_nano_opt(10, 5, 27, 750_500_000) |
349 | .unwrap(), |
350 | ) |
351 | .unwrap(); |
352 | assert_eq!(dt.round_subsecs(9), dt); |
353 | assert_eq!(dt.round_subsecs(4), dt); |
354 | assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000); |
355 | assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000); |
356 | assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000); |
357 | |
358 | assert_eq!(dt.round_subsecs(0).nanosecond(), 0); |
359 | assert_eq!(dt.round_subsecs(0).second(), 28); |
360 | } |
361 | |
362 | #[test ] |
363 | fn test_round_leap_nanos() { |
364 | let dt = Utc |
365 | .from_local_datetime( |
366 | &NaiveDate::from_ymd_opt(2016, 12, 31) |
367 | .unwrap() |
368 | .and_hms_nano_opt(23, 59, 59, 1_750_500_000) |
369 | .unwrap(), |
370 | ) |
371 | .unwrap(); |
372 | assert_eq!(dt.round_subsecs(9), dt); |
373 | assert_eq!(dt.round_subsecs(4), dt); |
374 | assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); |
375 | assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000); |
376 | assert_eq!(dt.round_subsecs(1).second(), 59); |
377 | |
378 | assert_eq!(dt.round_subsecs(0).nanosecond(), 0); |
379 | assert_eq!(dt.round_subsecs(0).second(), 0); |
380 | } |
381 | |
382 | #[test ] |
383 | fn test_trunc_subsecs() { |
384 | let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); |
385 | let dt = pst |
386 | .from_local_datetime( |
387 | &NaiveDate::from_ymd_opt(2018, 1, 11) |
388 | .unwrap() |
389 | .and_hms_nano_opt(10, 5, 13, 84_660_684) |
390 | .unwrap(), |
391 | ) |
392 | .unwrap(); |
393 | |
394 | assert_eq!(dt.trunc_subsecs(10), dt); |
395 | assert_eq!(dt.trunc_subsecs(9), dt); |
396 | assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680); |
397 | assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600); |
398 | assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000); |
399 | assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000); |
400 | assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000); |
401 | assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000); |
402 | assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000); |
403 | assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0); |
404 | |
405 | assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); |
406 | assert_eq!(dt.trunc_subsecs(0).second(), 13); |
407 | |
408 | let dt = pst |
409 | .from_local_datetime( |
410 | &NaiveDate::from_ymd_opt(2018, 1, 11) |
411 | .unwrap() |
412 | .and_hms_nano_opt(10, 5, 27, 750_500_000) |
413 | .unwrap(), |
414 | ) |
415 | .unwrap(); |
416 | assert_eq!(dt.trunc_subsecs(9), dt); |
417 | assert_eq!(dt.trunc_subsecs(4), dt); |
418 | assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000); |
419 | assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000); |
420 | assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000); |
421 | |
422 | assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); |
423 | assert_eq!(dt.trunc_subsecs(0).second(), 27); |
424 | } |
425 | |
426 | #[test ] |
427 | fn test_trunc_leap_nanos() { |
428 | let dt = Utc |
429 | .from_local_datetime( |
430 | &NaiveDate::from_ymd_opt(2016, 12, 31) |
431 | .unwrap() |
432 | .and_hms_nano_opt(23, 59, 59, 1_750_500_000) |
433 | .unwrap(), |
434 | ) |
435 | .unwrap(); |
436 | assert_eq!(dt.trunc_subsecs(9), dt); |
437 | assert_eq!(dt.trunc_subsecs(4), dt); |
438 | assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); |
439 | assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000); |
440 | assert_eq!(dt.trunc_subsecs(1).second(), 59); |
441 | |
442 | assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000); |
443 | assert_eq!(dt.trunc_subsecs(0).second(), 59); |
444 | } |
445 | |
446 | #[test ] |
447 | fn test_duration_round() { |
448 | let dt = Utc |
449 | .from_local_datetime( |
450 | &NaiveDate::from_ymd_opt(2016, 12, 31) |
451 | .unwrap() |
452 | .and_hms_nano_opt(23, 59, 59, 175_500_000) |
453 | .unwrap(), |
454 | ) |
455 | .unwrap(); |
456 | |
457 | assert_eq!( |
458 | dt.duration_round(Duration::zero()).unwrap().to_string(), |
459 | "2016-12-31 23:59:59.175500 UTC" |
460 | ); |
461 | |
462 | assert_eq!( |
463 | dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), |
464 | "2016-12-31 23:59:59.180 UTC" |
465 | ); |
466 | |
467 | // round up |
468 | let dt = Utc |
469 | .from_local_datetime( |
470 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
471 | .unwrap() |
472 | .and_hms_milli_opt(18, 22, 30, 0) |
473 | .unwrap(), |
474 | ) |
475 | .unwrap(); |
476 | assert_eq!( |
477 | dt.duration_round(Duration::minutes(5)).unwrap().to_string(), |
478 | "2012-12-12 18:25:00 UTC" |
479 | ); |
480 | // round down |
481 | let dt = Utc |
482 | .from_local_datetime( |
483 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
484 | .unwrap() |
485 | .and_hms_milli_opt(18, 22, 29, 999) |
486 | .unwrap(), |
487 | ) |
488 | .unwrap(); |
489 | assert_eq!( |
490 | dt.duration_round(Duration::minutes(5)).unwrap().to_string(), |
491 | "2012-12-12 18:20:00 UTC" |
492 | ); |
493 | |
494 | assert_eq!( |
495 | dt.duration_round(Duration::minutes(10)).unwrap().to_string(), |
496 | "2012-12-12 18:20:00 UTC" |
497 | ); |
498 | assert_eq!( |
499 | dt.duration_round(Duration::minutes(30)).unwrap().to_string(), |
500 | "2012-12-12 18:30:00 UTC" |
501 | ); |
502 | assert_eq!( |
503 | dt.duration_round(Duration::hours(1)).unwrap().to_string(), |
504 | "2012-12-12 18:00:00 UTC" |
505 | ); |
506 | assert_eq!( |
507 | dt.duration_round(Duration::days(1)).unwrap().to_string(), |
508 | "2012-12-13 00:00:00 UTC" |
509 | ); |
510 | |
511 | // timezone east |
512 | let dt = |
513 | FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); |
514 | assert_eq!( |
515 | dt.duration_round(Duration::days(1)).unwrap().to_string(), |
516 | "2020-10-28 00:00:00 +01:00" |
517 | ); |
518 | assert_eq!( |
519 | dt.duration_round(Duration::weeks(1)).unwrap().to_string(), |
520 | "2020-10-29 00:00:00 +01:00" |
521 | ); |
522 | |
523 | // timezone west |
524 | let dt = |
525 | FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); |
526 | assert_eq!( |
527 | dt.duration_round(Duration::days(1)).unwrap().to_string(), |
528 | "2020-10-28 00:00:00 -01:00" |
529 | ); |
530 | assert_eq!( |
531 | dt.duration_round(Duration::weeks(1)).unwrap().to_string(), |
532 | "2020-10-29 00:00:00 -01:00" |
533 | ); |
534 | } |
535 | |
536 | #[test ] |
537 | fn test_duration_round_naive() { |
538 | let dt = Utc |
539 | .from_local_datetime( |
540 | &NaiveDate::from_ymd_opt(2016, 12, 31) |
541 | .unwrap() |
542 | .and_hms_nano_opt(23, 59, 59, 175_500_000) |
543 | .unwrap(), |
544 | ) |
545 | .unwrap() |
546 | .naive_utc(); |
547 | |
548 | assert_eq!( |
549 | dt.duration_round(Duration::zero()).unwrap().to_string(), |
550 | "2016-12-31 23:59:59.175500" |
551 | ); |
552 | |
553 | assert_eq!( |
554 | dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), |
555 | "2016-12-31 23:59:59.180" |
556 | ); |
557 | |
558 | // round up |
559 | let dt = Utc |
560 | .from_local_datetime( |
561 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
562 | .unwrap() |
563 | .and_hms_milli_opt(18, 22, 30, 0) |
564 | .unwrap(), |
565 | ) |
566 | .unwrap() |
567 | .naive_utc(); |
568 | assert_eq!( |
569 | dt.duration_round(Duration::minutes(5)).unwrap().to_string(), |
570 | "2012-12-12 18:25:00" |
571 | ); |
572 | // round down |
573 | let dt = Utc |
574 | .from_local_datetime( |
575 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
576 | .unwrap() |
577 | .and_hms_milli_opt(18, 22, 29, 999) |
578 | .unwrap(), |
579 | ) |
580 | .unwrap() |
581 | .naive_utc(); |
582 | assert_eq!( |
583 | dt.duration_round(Duration::minutes(5)).unwrap().to_string(), |
584 | "2012-12-12 18:20:00" |
585 | ); |
586 | |
587 | assert_eq!( |
588 | dt.duration_round(Duration::minutes(10)).unwrap().to_string(), |
589 | "2012-12-12 18:20:00" |
590 | ); |
591 | assert_eq!( |
592 | dt.duration_round(Duration::minutes(30)).unwrap().to_string(), |
593 | "2012-12-12 18:30:00" |
594 | ); |
595 | assert_eq!( |
596 | dt.duration_round(Duration::hours(1)).unwrap().to_string(), |
597 | "2012-12-12 18:00:00" |
598 | ); |
599 | assert_eq!( |
600 | dt.duration_round(Duration::days(1)).unwrap().to_string(), |
601 | "2012-12-13 00:00:00" |
602 | ); |
603 | } |
604 | |
605 | #[test ] |
606 | fn test_duration_round_pre_epoch() { |
607 | let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); |
608 | assert_eq!( |
609 | dt.duration_round(Duration::minutes(10)).unwrap().to_string(), |
610 | "1969-12-12 12:10:00 UTC" |
611 | ); |
612 | } |
613 | |
614 | #[test ] |
615 | fn test_duration_trunc() { |
616 | let dt = Utc |
617 | .from_local_datetime( |
618 | &NaiveDate::from_ymd_opt(2016, 12, 31) |
619 | .unwrap() |
620 | .and_hms_nano_opt(23, 59, 59, 175_500_000) |
621 | .unwrap(), |
622 | ) |
623 | .unwrap(); |
624 | |
625 | assert_eq!( |
626 | dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), |
627 | "2016-12-31 23:59:59.170 UTC" |
628 | ); |
629 | |
630 | // would round up |
631 | let dt = Utc |
632 | .from_local_datetime( |
633 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
634 | .unwrap() |
635 | .and_hms_milli_opt(18, 22, 30, 0) |
636 | .unwrap(), |
637 | ) |
638 | .unwrap(); |
639 | assert_eq!( |
640 | dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), |
641 | "2012-12-12 18:20:00 UTC" |
642 | ); |
643 | // would round down |
644 | let dt = Utc |
645 | .from_local_datetime( |
646 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
647 | .unwrap() |
648 | .and_hms_milli_opt(18, 22, 29, 999) |
649 | .unwrap(), |
650 | ) |
651 | .unwrap(); |
652 | assert_eq!( |
653 | dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), |
654 | "2012-12-12 18:20:00 UTC" |
655 | ); |
656 | assert_eq!( |
657 | dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), |
658 | "2012-12-12 18:20:00 UTC" |
659 | ); |
660 | assert_eq!( |
661 | dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), |
662 | "2012-12-12 18:00:00 UTC" |
663 | ); |
664 | assert_eq!( |
665 | dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), |
666 | "2012-12-12 18:00:00 UTC" |
667 | ); |
668 | assert_eq!( |
669 | dt.duration_trunc(Duration::days(1)).unwrap().to_string(), |
670 | "2012-12-12 00:00:00 UTC" |
671 | ); |
672 | |
673 | // timezone east |
674 | let dt = |
675 | FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); |
676 | assert_eq!( |
677 | dt.duration_trunc(Duration::days(1)).unwrap().to_string(), |
678 | "2020-10-27 00:00:00 +01:00" |
679 | ); |
680 | assert_eq!( |
681 | dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), |
682 | "2020-10-22 00:00:00 +01:00" |
683 | ); |
684 | |
685 | // timezone west |
686 | let dt = |
687 | FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); |
688 | assert_eq!( |
689 | dt.duration_trunc(Duration::days(1)).unwrap().to_string(), |
690 | "2020-10-27 00:00:00 -01:00" |
691 | ); |
692 | assert_eq!( |
693 | dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), |
694 | "2020-10-22 00:00:00 -01:00" |
695 | ); |
696 | } |
697 | |
698 | #[test ] |
699 | fn test_duration_trunc_naive() { |
700 | let dt = Utc |
701 | .from_local_datetime( |
702 | &NaiveDate::from_ymd_opt(2016, 12, 31) |
703 | .unwrap() |
704 | .and_hms_nano_opt(23, 59, 59, 175_500_000) |
705 | .unwrap(), |
706 | ) |
707 | .unwrap() |
708 | .naive_utc(); |
709 | |
710 | assert_eq!( |
711 | dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), |
712 | "2016-12-31 23:59:59.170" |
713 | ); |
714 | |
715 | // would round up |
716 | let dt = Utc |
717 | .from_local_datetime( |
718 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
719 | .unwrap() |
720 | .and_hms_milli_opt(18, 22, 30, 0) |
721 | .unwrap(), |
722 | ) |
723 | .unwrap() |
724 | .naive_utc(); |
725 | assert_eq!( |
726 | dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), |
727 | "2012-12-12 18:20:00" |
728 | ); |
729 | // would round down |
730 | let dt = Utc |
731 | .from_local_datetime( |
732 | &NaiveDate::from_ymd_opt(2012, 12, 12) |
733 | .unwrap() |
734 | .and_hms_milli_opt(18, 22, 29, 999) |
735 | .unwrap(), |
736 | ) |
737 | .unwrap() |
738 | .naive_utc(); |
739 | assert_eq!( |
740 | dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), |
741 | "2012-12-12 18:20:00" |
742 | ); |
743 | assert_eq!( |
744 | dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), |
745 | "2012-12-12 18:20:00" |
746 | ); |
747 | assert_eq!( |
748 | dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), |
749 | "2012-12-12 18:00:00" |
750 | ); |
751 | assert_eq!( |
752 | dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), |
753 | "2012-12-12 18:00:00" |
754 | ); |
755 | assert_eq!( |
756 | dt.duration_trunc(Duration::days(1)).unwrap().to_string(), |
757 | "2012-12-12 00:00:00" |
758 | ); |
759 | } |
760 | |
761 | #[test ] |
762 | fn test_duration_trunc_pre_epoch() { |
763 | let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); |
764 | assert_eq!( |
765 | dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), |
766 | "1969-12-12 12:10:00 UTC" |
767 | ); |
768 | } |
769 | |
770 | #[test ] |
771 | fn issue1010() { |
772 | let dt = NaiveDateTime::from_timestamp_opt(-4227854320, 1678774288).unwrap(); |
773 | let span = Duration::microseconds(-7019067213869040); |
774 | assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); |
775 | |
776 | let dt = NaiveDateTime::from_timestamp_opt(320041586, 1920103021).unwrap(); |
777 | let span = Duration::nanoseconds(-8923838508697114584); |
778 | assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); |
779 | |
780 | let dt = NaiveDateTime::from_timestamp_opt(-2621440, 0).unwrap(); |
781 | let span = Duration::nanoseconds(-9223372036854771421); |
782 | assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); |
783 | } |
784 | } |
785 | |