/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.28/src/util/round.rs
Line | Count | Source |
1 | | use crate::{ |
2 | | error::{ |
3 | | unit::{MustDivide, UnitConfigError}, |
4 | | util::RoundingIncrementError, |
5 | | Error, ErrorContext, |
6 | | }, |
7 | | util::b, |
8 | | SignedDuration, Unit, |
9 | | }; |
10 | | |
11 | | /// A representation of a rounding increment in a particular unit. |
12 | | /// |
13 | | /// This implements the core rounding interface of Jiff, shared across all |
14 | | /// types. The constructors on `Increment` know which units *and* increments |
15 | | /// are valid for a particular rounding configuration. For example, only |
16 | | /// `Increment::for_span` supports any `unit` value. All other constructors |
17 | | /// have some kind of limitation (e.g., you can't round a date to the nearest |
18 | | /// month). |
19 | | /// |
20 | | /// For most cases, rounding is done via `Increment::round`. This takes a |
21 | | /// quantity in a number of nanoseconds and rounds it to the nearest increment |
22 | | /// (after converting the increment to nanoseconds as well, based on the unit |
23 | | /// provided). This API makes it difficult for callers to get rounding wrong. |
24 | | /// They just need to use the right constructor and provide a quantity in units |
25 | | /// of nanoseconds. |
26 | | /// |
27 | | /// For lower level needs, one can use `RoundMode::round_by_duration` directly. |
28 | | /// This doesn't know about any units other than nanoseconds. It's useful in |
29 | | /// contexts when dealing with variable length units and there are no rules |
30 | | /// for what is a valid increment. |
31 | | #[derive(Clone, Copy, Debug)] |
32 | | pub(crate) struct Increment { |
33 | | unit: Unit, |
34 | | value: i32, |
35 | | } |
36 | | |
37 | | impl Increment { |
38 | | /// Return a rounding increment suitable for use when rounding a `Span`. |
39 | 0 | pub(crate) fn for_span( |
40 | 0 | unit: Unit, |
41 | 0 | value: i64, |
42 | 0 | ) -> Result<Increment, Error> { |
43 | | // Indexed by `Unit`. |
44 | | static LIMITS: &[MustDivide] = &[ |
45 | | MustDivide::NanosPerMicro, |
46 | | MustDivide::MicrosPerMilli, |
47 | | MustDivide::MillisPerSec, |
48 | | MustDivide::SecsPerMin, |
49 | | MustDivide::MinsPerHour, |
50 | | MustDivide::HoursPerCivilDay, |
51 | | ]; |
52 | | |
53 | | // We allow any kind of increment for calendar units, but for time |
54 | | // units, they have to divide evenly into the next highest unit (and |
55 | | // also be less than that). The reason for this is that calendar |
56 | | // units vary, where as for time units, given a balanced span, you |
57 | | // know that time units will always spill over into days so that |
58 | | // hours/minutes/... will never exceed 24/60/... |
59 | 0 | if unit < Unit::Day { |
60 | 0 | Increment::for_limits(unit, value, LIMITS) |
61 | 0 | .context(RoundingIncrementError::ForSpan) |
62 | | } else { |
63 | | // For calendar units, just verify that the increment is in the |
64 | | // correct range. |
65 | 0 | let value = b::Increment32::check(value) |
66 | 0 | .context(RoundingIncrementError::ForSpan)?; |
67 | 0 | Ok(Increment { unit, value }) |
68 | | } |
69 | 0 | } |
70 | | |
71 | | /// Return a rounding increment suitable for use when rounding a |
72 | | /// `SignedDuration`. |
73 | 0 | pub(crate) fn for_signed_duration( |
74 | 0 | unit: Unit, |
75 | 0 | value: i64, |
76 | 0 | ) -> Result<Increment, Error> { |
77 | 0 | if unit > Unit::Hour { |
78 | 0 | return Err(Error::from( |
79 | 0 | UnitConfigError::SignedDurationCalendarUnit { smallest: unit }, |
80 | 0 | )); |
81 | 0 | } |
82 | | |
83 | | // For signed durations, we allow any increment that is within range. |
84 | 0 | let value = b::Increment32::check(value) |
85 | 0 | .context(RoundingIncrementError::ForSignedDuration)?; |
86 | 0 | Ok(Increment { unit, value }) |
87 | 0 | } |
88 | | |
89 | | /// Return a rounding increment suitable for use when rounding a |
90 | | /// `tz::Offset`. |
91 | 0 | pub(crate) fn for_offset( |
92 | 0 | unit: Unit, |
93 | 0 | value: i64, |
94 | 0 | ) -> Result<Increment, Error> { |
95 | 0 | if !(Unit::Second <= unit && unit <= Unit::Hour) { |
96 | 0 | return Err(Error::from(UnitConfigError::TimeZoneOffset { |
97 | 0 | given: unit, |
98 | 0 | })); |
99 | 0 | } |
100 | | |
101 | | // For time zone offsets, we allow any increment that is within range. |
102 | 0 | let value = b::Increment32::check(value) |
103 | 0 | .context(RoundingIncrementError::ForOffset)?; |
104 | 0 | Ok(Increment { unit, value }) |
105 | 0 | } |
106 | | |
107 | | /// Return a rounding increment suitable for use when rounding a |
108 | | /// `civil::DateTime` or a `Zoned`. |
109 | 0 | pub(crate) fn for_datetime( |
110 | 0 | unit: Unit, |
111 | 0 | value: i64, |
112 | 0 | ) -> Result<Increment, Error> { |
113 | | // Indexed by `Unit`. |
114 | | static LIMITS: &[MustDivide] = &[ |
115 | | MustDivide::NanosPerMicro, |
116 | | MustDivide::MicrosPerMilli, |
117 | | MustDivide::MillisPerSec, |
118 | | MustDivide::SecsPerMin, |
119 | | MustDivide::MinsPerHour, |
120 | | MustDivide::HoursPerCivilDay, |
121 | | MustDivide::Days, |
122 | | ]; |
123 | 0 | Increment::for_limits(unit, value, LIMITS) |
124 | 0 | .context(RoundingIncrementError::ForDateTime) |
125 | 0 | } |
126 | | |
127 | | /// Return a rounding increment suitable for use when rounding a |
128 | | /// `civil::Time`. |
129 | 0 | pub(crate) fn for_time( |
130 | 0 | unit: Unit, |
131 | 0 | value: i64, |
132 | 0 | ) -> Result<Increment, Error> { |
133 | | // Indexed by `Unit`. |
134 | | static LIMITS: &[MustDivide] = &[ |
135 | | MustDivide::NanosPerMicro, |
136 | | MustDivide::MicrosPerMilli, |
137 | | MustDivide::MillisPerSec, |
138 | | MustDivide::SecsPerMin, |
139 | | MustDivide::MinsPerHour, |
140 | | MustDivide::HoursPerCivilDay, |
141 | | ]; |
142 | 0 | Increment::for_limits(unit, value, LIMITS) |
143 | 0 | .context(RoundingIncrementError::ForTime) |
144 | 0 | } |
145 | | |
146 | | /// Return a rounding increment suitable for use when rounding a |
147 | | /// `civil::Timestamp`. |
148 | 0 | pub(crate) fn for_timestamp( |
149 | 0 | unit: Unit, |
150 | 0 | value: i64, |
151 | 0 | ) -> Result<Increment, Error> { |
152 | | // Indexed by `Unit`. |
153 | | static MAXIMUMS: &[MustDivide] = &[ |
154 | | MustDivide::NanosPerCivilDay, |
155 | | MustDivide::MicrosPerCivilDay, |
156 | | MustDivide::MillisPerCivilDay, |
157 | | MustDivide::SecsPerCivilDay, |
158 | | MustDivide::MinsPerCivilDay, |
159 | | MustDivide::HoursPerCivilDay, |
160 | | ]; |
161 | 0 | Increment::for_maximums(unit, value, MAXIMUMS) |
162 | 0 | .context(RoundingIncrementError::ForTimestamp) |
163 | 0 | } |
164 | | |
165 | | /// Rounds the given quantity to the nearest increment value (in terms of |
166 | | /// the units given to this increment's constructor). Rounding down or up |
167 | | /// is determined by the mode given. |
168 | 0 | pub(crate) fn round( |
169 | 0 | &self, |
170 | 0 | mode: RoundMode, |
171 | 0 | quantity: SignedDuration, |
172 | 0 | ) -> Result<SignedDuration, Error> { |
173 | | // OK because the max for the number of nanoseconds in a unit |
174 | | // is weeks at `604_800_000_000_000` and `increment` cannot be |
175 | | // any larger than `1_000_000_000`. The multiplication of these two |
176 | | // values does not exceed `SignedDuration::MAX`. |
177 | 0 | let increment = self.value() * self.unit().duration(); |
178 | 0 | mode.round_by_duration(quantity, increment) |
179 | 0 | } |
180 | | |
181 | | /// Returns this increment's unit. |
182 | | /// |
183 | | /// The increment value is always in terms of this unit. |
184 | 0 | pub(crate) fn unit(&self) -> Unit { |
185 | 0 | self.unit |
186 | 0 | } |
187 | | |
188 | | /// Returns this increment's value. |
189 | | /// |
190 | | /// e.g., "round to the nearest 15th minute" is expressed with an increment |
191 | | /// value of `15` and a unit of `Unit::Minute`. |
192 | | /// |
193 | | /// The value returned is guaranteed to be in the range |
194 | | /// `1..=1_000_000_000`. |
195 | 0 | pub(crate) fn value(&self) -> i32 { |
196 | 0 | self.value |
197 | 0 | } |
198 | | } |
199 | | |
200 | | /// Lower level constructors that require the caller to know the precise |
201 | | /// limits or maximums. These should generally stay unexported. |
202 | | impl Increment { |
203 | | /// Validate the `increment` value for the given `unit` and limits. |
204 | | /// |
205 | | /// Specifically, this ensures that rounding to the given `unit` is |
206 | | /// supported and that the specific value is *less than* to the |
207 | | /// corresponding limit. |
208 | 0 | fn for_limits( |
209 | 0 | unit: Unit, |
210 | 0 | value: i64, |
211 | 0 | limits: &[MustDivide], |
212 | 0 | ) -> Result<Increment, Error> { |
213 | 0 | let Some(&must_divide) = limits.get(unit as usize) else { |
214 | 0 | return Err(Error::from( |
215 | 0 | UnitConfigError::RoundToUnitUnsupported { unit }, |
216 | 0 | )); |
217 | | }; |
218 | | |
219 | 0 | let value32 = b::Increment32::check(value)?; |
220 | 0 | let must_divide_i64 = must_divide.as_i64(); |
221 | 0 | if value < must_divide_i64 && must_divide_i64 % value == 0 { |
222 | 0 | return Ok(Increment { unit, value: value32 }); |
223 | 0 | } |
224 | 0 | Err(Error::from(UnitConfigError::IncrementDivide { |
225 | 0 | unit, |
226 | 0 | must_divide, |
227 | 0 | })) |
228 | 0 | } |
229 | | |
230 | | /// Validate the `increment` value for the given `unit` and maximums. |
231 | | /// |
232 | | /// Specifically, this ensures that rounding to the given `unit` is |
233 | | /// supported and that the specific value is *less than or equal* to the |
234 | | /// corresponding maximum. |
235 | 0 | fn for_maximums( |
236 | 0 | unit: Unit, |
237 | 0 | value: i64, |
238 | 0 | maximums: &[MustDivide], |
239 | 0 | ) -> Result<Increment, Error> { |
240 | 0 | let Some(&must_divide) = maximums.get(unit as usize) else { |
241 | 0 | return Err(Error::from( |
242 | 0 | UnitConfigError::RoundToUnitUnsupported { unit }, |
243 | 0 | )); |
244 | | }; |
245 | | |
246 | 0 | let value32 = b::Increment32::check(value)?; |
247 | 0 | let must_divide_i64 = must_divide.as_i64(); |
248 | 0 | if value <= must_divide_i64 && must_divide_i64 % value == 0 { |
249 | 0 | return Ok(Increment { unit, value: value32 }); |
250 | 0 | } |
251 | 0 | Err(Error::from(UnitConfigError::IncrementDivide { |
252 | 0 | unit, |
253 | 0 | must_divide, |
254 | 0 | })) |
255 | 0 | } |
256 | | } |
257 | | |
258 | | /// The mode for dealing with the remainder when rounding datetimes or spans. |
259 | | /// |
260 | | /// This is used in APIs like [`Span::round`](crate::Span::round) for rounding |
261 | | /// spans, and APIs like [`Zoned::round`](crate::Zoned::round) for rounding |
262 | | /// datetimes. |
263 | | /// |
264 | | /// In the documentation for each variant, we refer to concepts like the |
265 | | /// "smallest" unit and the "rounding increment." These are best described |
266 | | /// in the documentation for what you're rounding. For example, |
267 | | /// [`SpanRound::smallest`](crate::SpanRound::smallest) |
268 | | /// and [`SpanRound::increment`](crate::SpanRound::increment). |
269 | | /// |
270 | | /// # Example |
271 | | /// |
272 | | /// This shows how to round a span with a different rounding mode than the |
273 | | /// default: |
274 | | /// |
275 | | /// ``` |
276 | | /// use jiff::{RoundMode, SpanRound, ToSpan, Unit}; |
277 | | /// |
278 | | /// // The default rounds like how you were taught in school: |
279 | | /// assert_eq!( |
280 | | /// 1.hour().minutes(59).round(Unit::Hour)?, |
281 | | /// 2.hours().fieldwise(), |
282 | | /// ); |
283 | | /// // But we can change the mode, e.g., truncation: |
284 | | /// let options = SpanRound::new().smallest(Unit::Hour).mode(RoundMode::Trunc); |
285 | | /// assert_eq!( |
286 | | /// 1.hour().minutes(59).round(options)?, |
287 | | /// 1.hour().fieldwise(), |
288 | | /// ); |
289 | | /// |
290 | | /// # Ok::<(), Box<dyn std::error::Error>>(()) |
291 | | /// ``` |
292 | | #[non_exhaustive] |
293 | | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
294 | | pub enum RoundMode { |
295 | | /// Rounds toward positive infinity. |
296 | | /// |
297 | | /// For negative spans and datetimes, this option will make the absolute |
298 | | /// value smaller, which could be unexpected. To round away from zero, use |
299 | | /// `Expand`. |
300 | | Ceil, |
301 | | /// Rounds toward negative infinity. |
302 | | /// |
303 | | /// This mode acts like `Trunc` for positive spans and datetimes, but |
304 | | /// for negative values, it will make the absolute value larger, which |
305 | | /// could be unexpected. To round towards zero, use `Trunc`. |
306 | | Floor, |
307 | | /// Rounds away from zero like `Ceil` for positive spans and datetimes, |
308 | | /// and like `Floor` for negative spans and datetimes. |
309 | | Expand, |
310 | | /// Rounds toward zero, chopping off any fractional part of a unit. |
311 | | /// |
312 | | /// This is the default when rounding spans returned from |
313 | | /// datetime arithmetic. (But it is not the default for |
314 | | /// [`Span::round`](crate::Span::round).) |
315 | | Trunc, |
316 | | /// Rounds to the nearest allowed value like `HalfExpand`, but when there |
317 | | /// is a tie, round towards positive infinity like `Ceil`. |
318 | | HalfCeil, |
319 | | /// Rounds to the nearest allowed value like `HalfExpand`, but when there |
320 | | /// is a tie, round towards negative infinity like `Floor`. |
321 | | HalfFloor, |
322 | | /// Rounds to the nearest value allowed by the rounding increment and the |
323 | | /// smallest unit. When there is a tie, round away from zero like `Ceil` |
324 | | /// for positive spans and datetimes and like `Floor` for negative spans |
325 | | /// and datetimes. |
326 | | /// |
327 | | /// This corresponds to how rounding is often taught in school. |
328 | | /// |
329 | | /// This is the default for rounding spans and datetimes. |
330 | | HalfExpand, |
331 | | /// Rounds to the nearest allowed value like `HalfExpand`, but when there |
332 | | /// is a tie, round towards zero like `Trunc`. |
333 | | HalfTrunc, |
334 | | /// Rounds to the nearest allowed value like `HalfExpand`, but when there |
335 | | /// is a tie, round towards the value that is an even multiple of the |
336 | | /// rounding increment. For example, with a rounding increment of `3`, |
337 | | /// the number `10` would round up to `12` instead of down to `9`, because |
338 | | /// `12` is an even multiple of `3`, where as `9` is is an odd multiple. |
339 | | HalfEven, |
340 | | } |
341 | | |
342 | | impl RoundMode { |
343 | | /// Round `quantity` to the nearest `increment` using this rounding mode. |
344 | | /// |
345 | | /// If this the rounding result would overflow `SignedDuration`, then an |
346 | | /// error is returned. |
347 | | /// |
348 | | /// Callers should generally prefer higher level APIs. But this one is |
349 | | /// unavoidable when the increment isn't tied to an invariant length and |
350 | | /// can vary. |
351 | | /// |
352 | | /// # Panics |
353 | | /// |
354 | | /// Callers must ensure that `increment` has a number of nanoseconds |
355 | | /// greater than or equal to `1`. |
356 | 0 | pub(crate) fn round_by_duration( |
357 | 0 | self, |
358 | 0 | quantity: SignedDuration, |
359 | 0 | increment: SignedDuration, |
360 | 0 | ) -> Result<SignedDuration, Error> { |
361 | 0 | let quantity = quantity.as_nanos(); |
362 | 0 | let increment = increment.as_nanos(); |
363 | 0 | assert!(increment >= 1); |
364 | 0 | let rounded = self.round_by_i128(quantity, increment); |
365 | | // This can fail in the case where `quantity` is somehow rounded to a |
366 | | // value above/below a signed duration's max/min. This cannot *usually* |
367 | | // happen in practice because our `quantity` is typically limited by |
368 | | // the maximum Jiff datetime span. Even spanning from -9999 to 9999, a |
369 | | // 96-bit integer number of nanoseconds still can't cause an overflow. |
370 | | // Moreover, `increment` is usually capped to `1_000_000_000`. The only |
371 | | // case where it isn't is when we're rounding based on variable length |
372 | | // units (in which case, this lower level rounding routine is called |
373 | | // directly). For example, when rounding to years with an increment |
374 | | // (set by the user) to `15_000`. That will get translated to 15,000 |
375 | | // years *in nanoseconds*. |
376 | | // |
377 | | // Even still, the complete span of time supported by Jiff can |
378 | | // comfortably fit into a `SignedDuration`. And because of the limit |
379 | | // placed on `increment`, I don't believe this error can ever actually |
380 | | // occur. |
381 | 0 | SignedDuration::try_from_nanos_i128(rounded) |
382 | 0 | .ok_or_else(|| Error::from(b::SignedDurationSeconds::error())) |
383 | 0 | } |
384 | | |
385 | | /// The internal API that does the actual rounding. |
386 | | /// |
387 | | /// This does not error on overflow because callers can never use values |
388 | | /// close to the `i128` limits. If an overflow would otherwise occur due to |
389 | | /// rounding, then it saturates instead of panicking or returning an error. |
390 | | /// |
391 | | /// I wish we could do modulus on `SignedDuration` directly. Then we |
392 | | /// wouldn't need 128-bit arithmetic at all. But I looked into making |
393 | | /// `SignedDuration % SignedDuration` work, and it was quite involved? So I |
394 | | /// guess we should just let the compiler handle it for `i128`. We'd also |
395 | | /// need division. |
396 | | /// |
397 | | /// # Panics |
398 | | /// |
399 | | /// When `increment < 1`. |
400 | 0 | fn round_by_i128(self, quantity: i128, increment: i128) -> i128 { |
401 | 0 | assert!(increment >= 1); |
402 | | // ref: https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement |
403 | 0 | let mode = self; |
404 | 0 | let mut quotient = quantity / increment; |
405 | 0 | let remainder = quantity % increment; |
406 | 0 | if remainder == 0 { |
407 | 0 | return quantity; |
408 | 0 | } |
409 | 0 | let sign = if remainder < 0 { -1 } else { 1 }; |
410 | 0 | let tiebreaker = (remainder * 2).abs(); |
411 | 0 | let tie = tiebreaker == increment; |
412 | 0 | let expand_is_nearer = tiebreaker > increment; |
413 | | // ref: https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement |
414 | 0 | match mode { |
415 | | RoundMode::Ceil => { |
416 | 0 | if sign > 0 { |
417 | 0 | quotient += sign; |
418 | 0 | } |
419 | | } |
420 | | RoundMode::Floor => { |
421 | 0 | if sign < 0 { |
422 | 0 | quotient += sign; |
423 | 0 | } |
424 | | } |
425 | 0 | RoundMode::Expand => { |
426 | 0 | quotient += sign; |
427 | 0 | } |
428 | 0 | RoundMode::Trunc => {} |
429 | | RoundMode::HalfCeil => { |
430 | 0 | if expand_is_nearer || (tie && sign > 0) { |
431 | 0 | quotient += sign; |
432 | 0 | } |
433 | | } |
434 | | RoundMode::HalfFloor => { |
435 | 0 | if expand_is_nearer || (tie && sign < 0) { |
436 | 0 | quotient += sign; |
437 | 0 | } |
438 | | } |
439 | | RoundMode::HalfExpand => { |
440 | 0 | if expand_is_nearer || tie { |
441 | 0 | quotient += sign; |
442 | 0 | } |
443 | | } |
444 | | RoundMode::HalfTrunc => { |
445 | 0 | if expand_is_nearer { |
446 | 0 | quotient += sign; |
447 | 0 | } |
448 | | } |
449 | | RoundMode::HalfEven => { |
450 | 0 | if expand_is_nearer || (tie && quotient.rem_euclid(2) == 1) { |
451 | 0 | quotient += sign; |
452 | 0 | } |
453 | | } |
454 | | } |
455 | | // We use saturating arithmetic here because this can overflow when |
456 | | // `quantity` is the max value. Since we're rounding, we just refuse to |
457 | | // go over the maximum. This can't happen in practice because this is |
458 | | // a private API and all paths to this API enforce that we are never |
459 | | // going to round over the `i128` maximum. |
460 | 0 | quotient.saturating_mul(increment) |
461 | 0 | } |
462 | | } |
463 | | |
464 | | #[cfg(test)] |
465 | | mod tests { |
466 | | use super::*; |
467 | | |
468 | | #[test] |
469 | | fn for_span() { |
470 | | let get = |unit, value| Increment::for_span(unit, value); |
471 | | |
472 | | assert!(get(Unit::Nanosecond, 1).is_ok()); |
473 | | assert!(get(Unit::Nanosecond, 500).is_ok()); |
474 | | assert!(get(Unit::Nanosecond, -1).is_err()); |
475 | | assert!(get(Unit::Nanosecond, 0).is_err()); |
476 | | assert!(get(Unit::Nanosecond, 7).is_err()); |
477 | | assert!(get(Unit::Nanosecond, 1_000).is_err()); |
478 | | assert!(get(Unit::Nanosecond, i64::MIN).is_err()); |
479 | | assert!(get(Unit::Nanosecond, i64::MAX).is_err()); |
480 | | |
481 | | assert!(get(Unit::Microsecond, 1).is_ok()); |
482 | | assert!(get(Unit::Microsecond, 500).is_ok()); |
483 | | assert!(get(Unit::Microsecond, -1).is_err()); |
484 | | assert!(get(Unit::Microsecond, 0).is_err()); |
485 | | assert!(get(Unit::Microsecond, 7).is_err()); |
486 | | assert!(get(Unit::Microsecond, 1_000).is_err()); |
487 | | assert!(get(Unit::Microsecond, i64::MIN).is_err()); |
488 | | assert!(get(Unit::Microsecond, i64::MAX).is_err()); |
489 | | |
490 | | assert!(get(Unit::Millisecond, 1).is_ok()); |
491 | | assert!(get(Unit::Millisecond, 500).is_ok()); |
492 | | assert!(get(Unit::Millisecond, -1).is_err()); |
493 | | assert!(get(Unit::Millisecond, 0).is_err()); |
494 | | assert!(get(Unit::Millisecond, 7).is_err()); |
495 | | assert!(get(Unit::Millisecond, 1_000).is_err()); |
496 | | assert!(get(Unit::Millisecond, i64::MIN).is_err()); |
497 | | assert!(get(Unit::Millisecond, i64::MAX).is_err()); |
498 | | |
499 | | assert!(get(Unit::Second, 1).is_ok()); |
500 | | assert!(get(Unit::Second, 15).is_ok()); |
501 | | assert!(get(Unit::Second, -1).is_err()); |
502 | | assert!(get(Unit::Second, 0).is_err()); |
503 | | assert!(get(Unit::Second, 7).is_err()); |
504 | | assert!(get(Unit::Second, 1_000).is_err()); |
505 | | assert!(get(Unit::Second, i64::MIN).is_err()); |
506 | | assert!(get(Unit::Second, i64::MAX).is_err()); |
507 | | |
508 | | assert!(get(Unit::Minute, 1).is_ok()); |
509 | | assert!(get(Unit::Minute, 15).is_ok()); |
510 | | assert!(get(Unit::Minute, -1).is_err()); |
511 | | assert!(get(Unit::Minute, 0).is_err()); |
512 | | assert!(get(Unit::Minute, 7).is_err()); |
513 | | assert!(get(Unit::Minute, 1_000).is_err()); |
514 | | assert!(get(Unit::Minute, i64::MIN).is_err()); |
515 | | assert!(get(Unit::Minute, i64::MAX).is_err()); |
516 | | |
517 | | assert!(get(Unit::Hour, 1).is_ok()); |
518 | | assert!(get(Unit::Hour, 6).is_ok()); |
519 | | assert!(get(Unit::Hour, 12).is_ok()); |
520 | | assert!(get(Unit::Hour, -1).is_err()); |
521 | | assert!(get(Unit::Hour, 0).is_err()); |
522 | | assert!(get(Unit::Hour, 15).is_err()); |
523 | | assert!(get(Unit::Hour, 18).is_err()); |
524 | | assert!(get(Unit::Hour, 23).is_err()); |
525 | | assert!(get(Unit::Hour, 24).is_err()); |
526 | | assert!(get(Unit::Hour, i64::MIN).is_err()); |
527 | | assert!(get(Unit::Hour, i64::MAX).is_err()); |
528 | | |
529 | | assert!(get(Unit::Day, 1).is_ok()); |
530 | | assert!(get(Unit::Day, 6).is_ok()); |
531 | | assert!(get(Unit::Day, 7).is_ok()); |
532 | | assert!(get(Unit::Day, 12).is_ok()); |
533 | | assert!(get(Unit::Day, 30).is_ok()); |
534 | | assert!(get(Unit::Day, 31).is_ok()); |
535 | | assert!(get(Unit::Day, 100).is_ok()); |
536 | | assert!(get(Unit::Day, 10_000_000).is_ok()); |
537 | | assert!(get(Unit::Day, 1_000_000_000).is_ok()); |
538 | | assert!(get(Unit::Day, -1).is_err()); |
539 | | assert!(get(Unit::Day, 0).is_err()); |
540 | | assert!(get(Unit::Day, 1_000_000_001).is_err()); |
541 | | assert!(get(Unit::Day, i64::MIN).is_err()); |
542 | | assert!(get(Unit::Day, i64::MAX).is_err()); |
543 | | |
544 | | assert!(get(Unit::Week, 1).is_ok()); |
545 | | assert!(get(Unit::Week, 6).is_ok()); |
546 | | assert!(get(Unit::Week, 7).is_ok()); |
547 | | assert!(get(Unit::Week, 12).is_ok()); |
548 | | assert!(get(Unit::Week, 30).is_ok()); |
549 | | assert!(get(Unit::Week, 31).is_ok()); |
550 | | assert!(get(Unit::Week, 100).is_ok()); |
551 | | assert!(get(Unit::Week, 2_000_000).is_ok()); |
552 | | assert!(get(Unit::Week, 1_000_000_000).is_ok()); |
553 | | assert!(get(Unit::Week, -1).is_err()); |
554 | | assert!(get(Unit::Week, 0).is_err()); |
555 | | assert!(get(Unit::Week, 1_000_000_001).is_err()); |
556 | | assert!(get(Unit::Week, i64::MIN).is_err()); |
557 | | assert!(get(Unit::Week, i64::MAX).is_err()); |
558 | | |
559 | | assert!(get(Unit::Month, 1).is_ok()); |
560 | | assert!(get(Unit::Month, 6).is_ok()); |
561 | | assert!(get(Unit::Month, 7).is_ok()); |
562 | | assert!(get(Unit::Month, 12).is_ok()); |
563 | | assert!(get(Unit::Month, 30).is_ok()); |
564 | | assert!(get(Unit::Month, 31).is_ok()); |
565 | | assert!(get(Unit::Month, 100).is_ok()); |
566 | | assert!(get(Unit::Month, 300_000).is_ok()); |
567 | | assert!(get(Unit::Month, 1_000_000_000).is_ok()); |
568 | | assert!(get(Unit::Month, -1).is_err()); |
569 | | assert!(get(Unit::Month, 0).is_err()); |
570 | | assert!(get(Unit::Month, 1_000_000_001).is_err()); |
571 | | assert!(get(Unit::Month, i64::MIN).is_err()); |
572 | | assert!(get(Unit::Month, i64::MAX).is_err()); |
573 | | |
574 | | assert!(get(Unit::Year, 1).is_ok()); |
575 | | assert!(get(Unit::Year, 6).is_ok()); |
576 | | assert!(get(Unit::Year, 7).is_ok()); |
577 | | assert!(get(Unit::Year, 12).is_ok()); |
578 | | assert!(get(Unit::Year, 30).is_ok()); |
579 | | assert!(get(Unit::Year, 31).is_ok()); |
580 | | assert!(get(Unit::Year, 100).is_ok()); |
581 | | assert!(get(Unit::Year, 50_000).is_ok()); |
582 | | assert!(get(Unit::Year, 1_000_000_000).is_ok()); |
583 | | assert!(get(Unit::Year, -1).is_err()); |
584 | | assert!(get(Unit::Year, 0).is_err()); |
585 | | assert!(get(Unit::Year, 1_000_000_001).is_err()); |
586 | | assert!(get(Unit::Year, i64::MIN).is_err()); |
587 | | assert!(get(Unit::Year, i64::MAX).is_err()); |
588 | | } |
589 | | |
590 | | #[test] |
591 | | fn for_datetime() { |
592 | | let get = |unit, value| Increment::for_datetime(unit, value); |
593 | | |
594 | | assert!(get(Unit::Nanosecond, 1).is_ok()); |
595 | | assert!(get(Unit::Nanosecond, 500).is_ok()); |
596 | | assert!(get(Unit::Nanosecond, -1).is_err()); |
597 | | assert!(get(Unit::Nanosecond, 0).is_err()); |
598 | | assert!(get(Unit::Nanosecond, 7).is_err()); |
599 | | assert!(get(Unit::Nanosecond, 1_000).is_err()); |
600 | | assert!(get(Unit::Nanosecond, i64::MIN).is_err()); |
601 | | assert!(get(Unit::Nanosecond, i64::MAX).is_err()); |
602 | | |
603 | | assert!(get(Unit::Microsecond, 1).is_ok()); |
604 | | assert!(get(Unit::Microsecond, 500).is_ok()); |
605 | | assert!(get(Unit::Microsecond, -1).is_err()); |
606 | | assert!(get(Unit::Microsecond, 0).is_err()); |
607 | | assert!(get(Unit::Microsecond, 7).is_err()); |
608 | | assert!(get(Unit::Microsecond, 1_000).is_err()); |
609 | | assert!(get(Unit::Microsecond, i64::MIN).is_err()); |
610 | | assert!(get(Unit::Microsecond, i64::MAX).is_err()); |
611 | | |
612 | | assert!(get(Unit::Millisecond, 1).is_ok()); |
613 | | assert!(get(Unit::Millisecond, 500).is_ok()); |
614 | | assert!(get(Unit::Millisecond, -1).is_err()); |
615 | | assert!(get(Unit::Millisecond, 0).is_err()); |
616 | | assert!(get(Unit::Millisecond, 7).is_err()); |
617 | | assert!(get(Unit::Millisecond, 1_000).is_err()); |
618 | | assert!(get(Unit::Millisecond, i64::MIN).is_err()); |
619 | | assert!(get(Unit::Millisecond, i64::MAX).is_err()); |
620 | | |
621 | | assert!(get(Unit::Second, 1).is_ok()); |
622 | | assert!(get(Unit::Second, 15).is_ok()); |
623 | | assert!(get(Unit::Second, -1).is_err()); |
624 | | assert!(get(Unit::Second, 0).is_err()); |
625 | | assert!(get(Unit::Second, 7).is_err()); |
626 | | assert!(get(Unit::Second, 1_000).is_err()); |
627 | | assert!(get(Unit::Second, i64::MIN).is_err()); |
628 | | assert!(get(Unit::Second, i64::MAX).is_err()); |
629 | | |
630 | | assert!(get(Unit::Minute, 1).is_ok()); |
631 | | assert!(get(Unit::Minute, 15).is_ok()); |
632 | | assert!(get(Unit::Minute, -1).is_err()); |
633 | | assert!(get(Unit::Minute, 0).is_err()); |
634 | | assert!(get(Unit::Minute, 7).is_err()); |
635 | | assert!(get(Unit::Minute, 1_000).is_err()); |
636 | | assert!(get(Unit::Minute, i64::MIN).is_err()); |
637 | | assert!(get(Unit::Minute, i64::MAX).is_err()); |
638 | | |
639 | | assert!(get(Unit::Hour, 1).is_ok()); |
640 | | assert!(get(Unit::Hour, 6).is_ok()); |
641 | | assert!(get(Unit::Hour, 12).is_ok()); |
642 | | assert!(get(Unit::Hour, -1).is_err()); |
643 | | assert!(get(Unit::Hour, 0).is_err()); |
644 | | assert!(get(Unit::Hour, 15).is_err()); |
645 | | assert!(get(Unit::Hour, 18).is_err()); |
646 | | assert!(get(Unit::Hour, 23).is_err()); |
647 | | assert!(get(Unit::Hour, 24).is_err()); |
648 | | assert!(get(Unit::Hour, i64::MIN).is_err()); |
649 | | assert!(get(Unit::Hour, i64::MAX).is_err()); |
650 | | |
651 | | assert!(get(Unit::Day, 1).is_ok()); |
652 | | assert!(get(Unit::Day, -1).is_err()); |
653 | | assert!(get(Unit::Day, 0).is_err()); |
654 | | assert!(get(Unit::Day, 2).is_err()); |
655 | | assert!(get(Unit::Day, 4).is_err()); |
656 | | assert!(get(Unit::Day, 7).is_err()); |
657 | | assert!(get(Unit::Day, i64::MIN).is_err()); |
658 | | assert!(get(Unit::Day, i64::MAX).is_err()); |
659 | | } |
660 | | |
661 | | #[test] |
662 | | fn for_time() { |
663 | | let get = |unit, value| Increment::for_time(unit, value); |
664 | | |
665 | | assert!(get(Unit::Nanosecond, 1).is_ok()); |
666 | | assert!(get(Unit::Nanosecond, 500).is_ok()); |
667 | | assert!(get(Unit::Nanosecond, -1).is_err()); |
668 | | assert!(get(Unit::Nanosecond, 0).is_err()); |
669 | | assert!(get(Unit::Nanosecond, 7).is_err()); |
670 | | assert!(get(Unit::Nanosecond, 1_000).is_err()); |
671 | | assert!(get(Unit::Nanosecond, i64::MIN).is_err()); |
672 | | assert!(get(Unit::Nanosecond, i64::MAX).is_err()); |
673 | | |
674 | | assert!(get(Unit::Microsecond, 1).is_ok()); |
675 | | assert!(get(Unit::Microsecond, 500).is_ok()); |
676 | | assert!(get(Unit::Microsecond, -1).is_err()); |
677 | | assert!(get(Unit::Microsecond, 0).is_err()); |
678 | | assert!(get(Unit::Microsecond, 7).is_err()); |
679 | | assert!(get(Unit::Microsecond, 1_000).is_err()); |
680 | | assert!(get(Unit::Microsecond, i64::MIN).is_err()); |
681 | | assert!(get(Unit::Microsecond, i64::MAX).is_err()); |
682 | | |
683 | | assert!(get(Unit::Millisecond, 1).is_ok()); |
684 | | assert!(get(Unit::Millisecond, 500).is_ok()); |
685 | | assert!(get(Unit::Millisecond, -1).is_err()); |
686 | | assert!(get(Unit::Millisecond, 0).is_err()); |
687 | | assert!(get(Unit::Millisecond, 7).is_err()); |
688 | | assert!(get(Unit::Millisecond, 1_000).is_err()); |
689 | | assert!(get(Unit::Millisecond, i64::MIN).is_err()); |
690 | | assert!(get(Unit::Millisecond, i64::MAX).is_err()); |
691 | | |
692 | | assert!(get(Unit::Second, 1).is_ok()); |
693 | | assert!(get(Unit::Second, 15).is_ok()); |
694 | | assert!(get(Unit::Second, -1).is_err()); |
695 | | assert!(get(Unit::Second, 0).is_err()); |
696 | | assert!(get(Unit::Second, 7).is_err()); |
697 | | assert!(get(Unit::Second, 1_000).is_err()); |
698 | | assert!(get(Unit::Second, i64::MIN).is_err()); |
699 | | assert!(get(Unit::Second, i64::MAX).is_err()); |
700 | | |
701 | | assert!(get(Unit::Minute, 1).is_ok()); |
702 | | assert!(get(Unit::Minute, 15).is_ok()); |
703 | | assert!(get(Unit::Minute, -1).is_err()); |
704 | | assert!(get(Unit::Minute, 0).is_err()); |
705 | | assert!(get(Unit::Minute, 7).is_err()); |
706 | | assert!(get(Unit::Minute, 1_000).is_err()); |
707 | | assert!(get(Unit::Minute, i64::MIN).is_err()); |
708 | | assert!(get(Unit::Minute, i64::MAX).is_err()); |
709 | | |
710 | | assert!(get(Unit::Hour, 1).is_ok()); |
711 | | assert!(get(Unit::Hour, 6).is_ok()); |
712 | | assert!(get(Unit::Hour, 12).is_ok()); |
713 | | assert!(get(Unit::Hour, -1).is_err()); |
714 | | assert!(get(Unit::Hour, 0).is_err()); |
715 | | assert!(get(Unit::Hour, 15).is_err()); |
716 | | assert!(get(Unit::Hour, 18).is_err()); |
717 | | assert!(get(Unit::Hour, 23).is_err()); |
718 | | assert!(get(Unit::Hour, 24).is_err()); |
719 | | assert!(get(Unit::Hour, i64::MIN).is_err()); |
720 | | assert!(get(Unit::Hour, i64::MAX).is_err()); |
721 | | |
722 | | assert!(get(Unit::Day, 1).is_err()); |
723 | | assert!(get(Unit::Day, -1).is_err()); |
724 | | assert!(get(Unit::Day, 0).is_err()); |
725 | | assert!(get(Unit::Day, 2).is_err()); |
726 | | assert!(get(Unit::Day, 4).is_err()); |
727 | | assert!(get(Unit::Day, 7).is_err()); |
728 | | assert!(get(Unit::Day, i64::MIN).is_err()); |
729 | | assert!(get(Unit::Day, i64::MAX).is_err()); |
730 | | } |
731 | | |
732 | | #[test] |
733 | | fn for_timestamp() { |
734 | | let get = |unit, value| Increment::for_timestamp(unit, value); |
735 | | |
736 | | assert!(get(Unit::Nanosecond, 1).is_ok()); |
737 | | assert!(get(Unit::Nanosecond, 500).is_ok()); |
738 | | assert!(get(Unit::Nanosecond, 1_000).is_ok()); |
739 | | assert!(get(Unit::Nanosecond, 1_000_000_000).is_ok()); |
740 | | assert!(get(Unit::Nanosecond, -1).is_err()); |
741 | | assert!(get(Unit::Nanosecond, 0).is_err()); |
742 | | assert!(get(Unit::Nanosecond, 7).is_err()); |
743 | | assert!(get(Unit::Nanosecond, 1_000_000_001).is_err()); |
744 | | assert!(get(Unit::Nanosecond, i64::MIN).is_err()); |
745 | | assert!(get(Unit::Nanosecond, i64::MAX).is_err()); |
746 | | |
747 | | assert!(get(Unit::Microsecond, 1).is_ok()); |
748 | | assert!(get(Unit::Microsecond, 500).is_ok()); |
749 | | assert!(get(Unit::Microsecond, 1_000).is_ok()); |
750 | | assert!(get(Unit::Microsecond, 2_000).is_ok()); |
751 | | assert!(get(Unit::Microsecond, -1).is_err()); |
752 | | assert!(get(Unit::Microsecond, 0).is_err()); |
753 | | assert!(get(Unit::Microsecond, 7).is_err()); |
754 | | assert!(get(Unit::Microsecond, 1_000_000_000).is_err()); |
755 | | assert!(get(Unit::Microsecond, 1_000_000_001).is_err()); |
756 | | assert!(get(Unit::Microsecond, i64::MIN).is_err()); |
757 | | assert!(get(Unit::Microsecond, i64::MAX).is_err()); |
758 | | |
759 | | assert!(get(Unit::Millisecond, 1).is_ok()); |
760 | | assert!(get(Unit::Millisecond, 500).is_ok()); |
761 | | assert!(get(Unit::Millisecond, 1_000).is_ok()); |
762 | | assert!(get(Unit::Millisecond, 2_000).is_ok()); |
763 | | assert!(get(Unit::Millisecond, 86_400_000).is_ok()); |
764 | | assert!(get(Unit::Millisecond, -1).is_err()); |
765 | | assert!(get(Unit::Millisecond, 0).is_err()); |
766 | | assert!(get(Unit::Millisecond, 7).is_err()); |
767 | | assert!(get(Unit::Millisecond, 1_000_000_000).is_err()); |
768 | | assert!(get(Unit::Millisecond, 1_000_000_001).is_err()); |
769 | | assert!(get(Unit::Millisecond, i64::MIN).is_err()); |
770 | | assert!(get(Unit::Millisecond, i64::MAX).is_err()); |
771 | | |
772 | | assert!(get(Unit::Second, 1).is_ok()); |
773 | | assert!(get(Unit::Second, 15).is_ok()); |
774 | | assert!(get(Unit::Second, 3_600).is_ok()); |
775 | | assert!(get(Unit::Second, 86_400).is_ok()); |
776 | | assert!(get(Unit::Second, -1).is_err()); |
777 | | assert!(get(Unit::Second, 0).is_err()); |
778 | | assert!(get(Unit::Second, 7).is_err()); |
779 | | assert!(get(Unit::Second, 1_000).is_err()); |
780 | | assert!(get(Unit::Second, 86_401).is_err()); |
781 | | assert!(get(Unit::Second, 172_800).is_err()); |
782 | | assert!(get(Unit::Second, 1_000_000_000).is_err()); |
783 | | assert!(get(Unit::Second, 1_000_000_001).is_err()); |
784 | | assert!(get(Unit::Second, i64::MIN).is_err()); |
785 | | assert!(get(Unit::Second, i64::MAX).is_err()); |
786 | | |
787 | | assert!(get(Unit::Minute, 1).is_ok()); |
788 | | assert!(get(Unit::Minute, 15).is_ok()); |
789 | | assert!(get(Unit::Minute, 1_440).is_ok()); |
790 | | assert!(get(Unit::Minute, -1).is_err()); |
791 | | assert!(get(Unit::Minute, 0).is_err()); |
792 | | assert!(get(Unit::Minute, 7).is_err()); |
793 | | assert!(get(Unit::Minute, 1_000).is_err()); |
794 | | assert!(get(Unit::Minute, 1_441).is_err()); |
795 | | assert!(get(Unit::Minute, 2_880).is_err()); |
796 | | assert!(get(Unit::Minute, 1_000_000_000).is_err()); |
797 | | assert!(get(Unit::Minute, 1_000_000_001).is_err()); |
798 | | assert!(get(Unit::Minute, i64::MIN).is_err()); |
799 | | assert!(get(Unit::Minute, i64::MAX).is_err()); |
800 | | |
801 | | assert!(get(Unit::Hour, 1).is_ok()); |
802 | | assert!(get(Unit::Hour, 6).is_ok()); |
803 | | assert!(get(Unit::Hour, 12).is_ok()); |
804 | | assert!(get(Unit::Hour, 24).is_ok()); |
805 | | assert!(get(Unit::Hour, -1).is_err()); |
806 | | assert!(get(Unit::Hour, 0).is_err()); |
807 | | assert!(get(Unit::Hour, 15).is_err()); |
808 | | assert!(get(Unit::Hour, 18).is_err()); |
809 | | assert!(get(Unit::Hour, 23).is_err()); |
810 | | assert!(get(Unit::Hour, 25).is_err()); |
811 | | assert!(get(Unit::Hour, 1_000_000_000).is_err()); |
812 | | assert!(get(Unit::Hour, 1_000_000_001).is_err()); |
813 | | assert!(get(Unit::Hour, i64::MIN).is_err()); |
814 | | assert!(get(Unit::Hour, i64::MAX).is_err()); |
815 | | |
816 | | assert!(get(Unit::Day, 1).is_err()); |
817 | | assert!(get(Unit::Day, -1).is_err()); |
818 | | assert!(get(Unit::Day, 0).is_err()); |
819 | | assert!(get(Unit::Day, 2).is_err()); |
820 | | assert!(get(Unit::Day, 4).is_err()); |
821 | | assert!(get(Unit::Day, 7).is_err()); |
822 | | assert!(get(Unit::Day, i64::MIN).is_err()); |
823 | | assert!(get(Unit::Day, i64::MAX).is_err()); |
824 | | |
825 | | assert!(get(Unit::Week, 1).is_err()); |
826 | | assert!(get(Unit::Week, -1).is_err()); |
827 | | assert!(get(Unit::Week, 0).is_err()); |
828 | | assert!(get(Unit::Week, 2).is_err()); |
829 | | assert!(get(Unit::Week, 4).is_err()); |
830 | | assert!(get(Unit::Week, 7).is_err()); |
831 | | assert!(get(Unit::Week, i64::MIN).is_err()); |
832 | | assert!(get(Unit::Week, i64::MAX).is_err()); |
833 | | |
834 | | assert!(get(Unit::Month, 1).is_err()); |
835 | | assert!(get(Unit::Month, -1).is_err()); |
836 | | assert!(get(Unit::Month, 0).is_err()); |
837 | | assert!(get(Unit::Month, 2).is_err()); |
838 | | assert!(get(Unit::Month, 4).is_err()); |
839 | | assert!(get(Unit::Month, 7).is_err()); |
840 | | assert!(get(Unit::Month, i64::MIN).is_err()); |
841 | | assert!(get(Unit::Month, i64::MAX).is_err()); |
842 | | |
843 | | assert!(get(Unit::Year, 1).is_err()); |
844 | | assert!(get(Unit::Year, -1).is_err()); |
845 | | assert!(get(Unit::Year, 0).is_err()); |
846 | | assert!(get(Unit::Year, 2).is_err()); |
847 | | assert!(get(Unit::Year, 4).is_err()); |
848 | | assert!(get(Unit::Year, 7).is_err()); |
849 | | assert!(get(Unit::Year, i64::MIN).is_err()); |
850 | | assert!(get(Unit::Year, i64::MAX).is_err()); |
851 | | } |
852 | | |
853 | | // Some ad hoc tests I wrote while writing the rounding increment code. |
854 | | #[test] |
855 | | fn round_to_increment_half_expand_ad_hoc() { |
856 | | let round = |quantity: i128, increment: i128| -> i128 { |
857 | | RoundMode::HalfExpand.round_by_i128(quantity, increment) |
858 | | }; |
859 | | assert_eq!(26, round(20, 13)); |
860 | | |
861 | | assert_eq!(0, round(29, 60)); |
862 | | assert_eq!(60, round(30, 60)); |
863 | | assert_eq!(60, round(31, 60)); |
864 | | |
865 | | assert_eq!(0, round(3, 7)); |
866 | | assert_eq!(7, round(4, 7)); |
867 | | } |
868 | | |
869 | | // The Temporal tests are inspired by the table from here: |
870 | | // https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement |
871 | | // |
872 | | // The main difference is that our rounding function specifically does not |
873 | | // use floating point, so we tweak the values a bit. |
874 | | |
875 | | #[test] |
876 | | fn round_to_increment_temporal_table_ceil() { |
877 | | let round = |quantity: i128, increment: i128| -> i128 { |
878 | | RoundMode::Ceil.round_by_i128(quantity, increment) |
879 | | }; |
880 | | assert_eq!(-10, round(-15, 10)); |
881 | | assert_eq!(0, round(-5, 10)); |
882 | | assert_eq!(10, round(4, 10)); |
883 | | assert_eq!(10, round(5, 10)); |
884 | | assert_eq!(10, round(6, 10)); |
885 | | assert_eq!(20, round(15, 10)); |
886 | | |
887 | | assert_eq!(i128::MAX, round(i128::MAX, 1)); |
888 | | assert_eq!(i128::MAX, round(i128::MAX, i128::MAX)); |
889 | | |
890 | | // TODO: This test currently panics on overflow. |
891 | | // We should overall scrutinize the input constraints |
892 | | // for our rounding routines and align with their call |
893 | | // sites. We were previously relying on ranged integers |
894 | | // to holistically verify we weren't doing anything that |
895 | | // could result in overflow. I suspect that we can't |
896 | | // actually support the i128 quantity range (and I don't |
897 | | // think we need to). The question is whether we should |
898 | | // make the rounding interface fallible or if this |
899 | | // should just document panicking conditions. |
900 | | // |
901 | | // Oh, we should introduce a new internal `Increment` |
902 | | // wrapper that provides guarantees about its range. |
903 | | // assert_eq!(i128::MIN, round(i128::MIN, -1)); |
904 | | // assert_eq!(i128::MIN, round(i128::MIN, i128::MIN)); |
905 | | } |
906 | | |
907 | | #[test] |
908 | | fn round_to_increment_temporal_table_floor() { |
909 | | let round = |quantity: i128, increment: i128| -> i128 { |
910 | | RoundMode::Floor.round_by_i128(quantity, increment) |
911 | | }; |
912 | | assert_eq!(-20, round(-15, 10)); |
913 | | assert_eq!(-10, round(-5, 10)); |
914 | | assert_eq!(0, round(4, 10)); |
915 | | assert_eq!(0, round(5, 10)); |
916 | | assert_eq!(0, round(6, 10)); |
917 | | assert_eq!(10, round(15, 10)); |
918 | | } |
919 | | |
920 | | #[test] |
921 | | fn round_to_increment_temporal_table_expand() { |
922 | | let round = |quantity: i128, increment: i128| -> i128 { |
923 | | RoundMode::Expand.round_by_i128(quantity, increment) |
924 | | }; |
925 | | assert_eq!(-20, round(-15, 10)); |
926 | | assert_eq!(-10, round(-5, 10)); |
927 | | assert_eq!(10, round(4, 10)); |
928 | | assert_eq!(10, round(5, 10)); |
929 | | assert_eq!(10, round(6, 10)); |
930 | | assert_eq!(20, round(15, 10)); |
931 | | } |
932 | | |
933 | | #[test] |
934 | | fn round_to_increment_temporal_table_trunc() { |
935 | | let round = |quantity: i128, increment: i128| -> i128 { |
936 | | RoundMode::Trunc.round_by_i128(quantity, increment) |
937 | | }; |
938 | | assert_eq!(-10, round(-15, 10)); |
939 | | assert_eq!(0, round(-5, 10)); |
940 | | assert_eq!(0, round(4, 10)); |
941 | | assert_eq!(0, round(5, 10)); |
942 | | assert_eq!(0, round(6, 10)); |
943 | | assert_eq!(10, round(15, 10)); |
944 | | } |
945 | | |
946 | | #[test] |
947 | | fn round_to_increment_temporal_table_half_ceil() { |
948 | | let round = |quantity: i128, increment: i128| -> i128 { |
949 | | RoundMode::HalfCeil.round_by_i128(quantity, increment) |
950 | | }; |
951 | | assert_eq!(-10, round(-15, 10)); |
952 | | assert_eq!(0, round(-5, 10)); |
953 | | assert_eq!(0, round(4, 10)); |
954 | | assert_eq!(10, round(5, 10)); |
955 | | assert_eq!(10, round(6, 10)); |
956 | | assert_eq!(20, round(15, 10)); |
957 | | } |
958 | | |
959 | | #[test] |
960 | | fn round_to_increment_temporal_table_half_floor() { |
961 | | let round = |quantity: i128, increment: i128| -> i128 { |
962 | | RoundMode::HalfFloor.round_by_i128(quantity, increment) |
963 | | }; |
964 | | assert_eq!(-20, round(-15, 10)); |
965 | | assert_eq!(-10, round(-5, 10)); |
966 | | assert_eq!(0, round(4, 10)); |
967 | | assert_eq!(0, round(5, 10)); |
968 | | assert_eq!(10, round(6, 10)); |
969 | | assert_eq!(10, round(15, 10)); |
970 | | } |
971 | | |
972 | | #[test] |
973 | | fn round_to_increment_temporal_table_half_expand() { |
974 | | let round = |quantity: i128, increment: i128| -> i128 { |
975 | | RoundMode::HalfExpand.round_by_i128(quantity, increment) |
976 | | }; |
977 | | assert_eq!(-20, round(-15, 10)); |
978 | | assert_eq!(-10, round(-5, 10)); |
979 | | assert_eq!(0, round(4, 10)); |
980 | | assert_eq!(10, round(5, 10)); |
981 | | assert_eq!(10, round(6, 10)); |
982 | | assert_eq!(20, round(15, 10)); |
983 | | } |
984 | | |
985 | | #[test] |
986 | | fn round_to_increment_temporal_table_half_trunc() { |
987 | | let round = |quantity: i128, increment: i128| -> i128 { |
988 | | RoundMode::HalfTrunc.round_by_i128(quantity, increment) |
989 | | }; |
990 | | assert_eq!(-10, round(-15, 10)); |
991 | | assert_eq!(0, round(-5, 10)); |
992 | | assert_eq!(0, round(4, 10)); |
993 | | assert_eq!(0, round(5, 10)); |
994 | | assert_eq!(10, round(6, 10)); |
995 | | assert_eq!(10, round(15, 10)); |
996 | | } |
997 | | |
998 | | #[test] |
999 | | fn round_to_increment_temporal_table_half_even() { |
1000 | | let round = |quantity: i128, increment: i128| -> i128 { |
1001 | | RoundMode::HalfEven.round_by_i128(quantity, increment) |
1002 | | }; |
1003 | | assert_eq!(-20, round(-15, 10)); |
1004 | | assert_eq!(0, round(-5, 10)); |
1005 | | assert_eq!(0, round(4, 10)); |
1006 | | assert_eq!(0, round(5, 10)); |
1007 | | assert_eq!(10, round(6, 10)); |
1008 | | assert_eq!(20, round(15, 10)); |
1009 | | } |
1010 | | |
1011 | | // Tests that the maximum possible increment, in units of nanoseconds, |
1012 | | // fits into a `SignedDuration`. |
1013 | | #[test] |
1014 | | fn maximum_increment_nanos() { |
1015 | | assert!(Increment::for_span(Unit::Week, 1_000_000_000).is_ok()); |
1016 | | assert!(Increment::for_span(Unit::Week, 1_000_000_001).is_err()); |
1017 | | assert_eq!( |
1018 | | Unit::Week.duration() * 1_000_000_000, |
1019 | | SignedDuration::from_secs(168_000_000_000 * 60 * 60) |
1020 | | ); |
1021 | | } |
1022 | | |
1023 | | // Tests some edge cases where rounding can actually overflow. We ensure |
1024 | | // that an error is returned instead of panicking. |
1025 | | // |
1026 | | // I do not believe it's possible for these overflows to be observed using |
1027 | | // Jiff's public API however. |
1028 | | #[test] |
1029 | | fn round_by_duration_overflow() { |
1030 | | let mode = RoundMode::Expand; |
1031 | | let quantity = SignedDuration::MAX; |
1032 | | let increment = SignedDuration::MAX - SignedDuration::from_secs(1); |
1033 | | assert!(mode.round_by_duration(quantity, increment).is_err()); |
1034 | | |
1035 | | let mode = RoundMode::Expand; |
1036 | | let quantity = SignedDuration::MIN; |
1037 | | let increment = SignedDuration::MAX; |
1038 | | assert!(mode.round_by_duration(quantity, increment).is_err()); |
1039 | | } |
1040 | | } |