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