1 | /*! |
2 | This module provides logic for validating rounding increments. |
3 | |
4 | Each of the types we support rounding for have their own logic for how the |
5 | rounding increment is validated. For example, when rounding timestamps, only |
6 | rounding increments up to hours are supported. But when rounding datetimes, |
7 | rounding increments up to days are supported. Similarly, rounding increments |
8 | for time units must divide evenly into 1 unit of the next highest unit. |
9 | */ |
10 | |
11 | use crate::{ |
12 | error::{err, Error}, |
13 | util::{ |
14 | rangeint::RFrom, |
15 | t::{self, Constant, C}, |
16 | }, |
17 | Unit, |
18 | }; |
19 | |
20 | /// Validates the given rounding increment for the given unit. |
21 | /// |
22 | /// This validation ensures the rounding increment is valid for rounding spans. |
23 | pub(crate) fn for_span( |
24 | unit: Unit, |
25 | increment: i64, |
26 | ) -> Result<t::NoUnits128, Error> { |
27 | // Indexed by `Unit`. |
28 | static LIMIT: &[Constant] = &[ |
29 | t::NANOS_PER_MICRO, |
30 | t::MICROS_PER_MILLI, |
31 | t::MILLIS_PER_SECOND, |
32 | t::SECONDS_PER_MINUTE, |
33 | t::MINUTES_PER_HOUR, |
34 | t::HOURS_PER_CIVIL_DAY, |
35 | ]; |
36 | // We allow any kind of increment for calendar units, but for time units, |
37 | // they have to divide evenly into the next highest unit (and also be less |
38 | // than that). The reason for this is that calendar units vary, where as |
39 | // for time units, given a balanced span, you know that time units will |
40 | // always spill over into days so that hours/minutes/... will never exceed |
41 | // 24/60/... |
42 | if unit >= Unit::Day { |
43 | // We specifically go from NoUnits to NoUnits128 here instead of |
44 | // directly to NoUnits128 to ensure our increment bounds match the |
45 | // bounds of i64 and not i128. |
46 | Ok(t::NoUnits128::rfrom(t::NoUnits::new_unchecked(increment))) |
47 | } else { |
48 | get_with_limit(unit, increment, "span" , LIMIT) |
49 | } |
50 | } |
51 | |
52 | /// Validates the given rounding increment for the given unit. |
53 | /// |
54 | /// This validation ensures the rounding increment is valid for rounding |
55 | /// datetimes (both civil and time zone aware). |
56 | pub(crate) fn for_datetime( |
57 | unit: Unit, |
58 | increment: i64, |
59 | ) -> Result<t::NoUnits128, Error> { |
60 | // Indexed by `Unit`. |
61 | static LIMIT: &[Constant] = &[ |
62 | t::NANOS_PER_MICRO, |
63 | t::MICROS_PER_MILLI, |
64 | t::MILLIS_PER_SECOND, |
65 | t::SECONDS_PER_MINUTE, |
66 | t::MINUTES_PER_HOUR, |
67 | t::HOURS_PER_CIVIL_DAY, |
68 | Constant(2), |
69 | ]; |
70 | get_with_limit(unit, increment, what:"datetime" , LIMIT) |
71 | } |
72 | |
73 | /// Validates the given rounding increment for the given unit. |
74 | /// |
75 | /// This validation ensures the rounding increment is valid for rounding |
76 | /// civil times. |
77 | pub(crate) fn for_time( |
78 | unit: Unit, |
79 | increment: i64, |
80 | ) -> Result<t::NoUnits128, Error> { |
81 | // Indexed by `Unit`. |
82 | static LIMIT: &[Constant] = &[ |
83 | t::NANOS_PER_MICRO, |
84 | t::MICROS_PER_MILLI, |
85 | t::MILLIS_PER_SECOND, |
86 | t::SECONDS_PER_MINUTE, |
87 | t::MINUTES_PER_HOUR, |
88 | t::HOURS_PER_CIVIL_DAY, |
89 | ]; |
90 | get_with_limit(unit, increment, what:"time" , LIMIT) |
91 | } |
92 | |
93 | /// Validates the given rounding increment for the given unit. |
94 | /// |
95 | /// This validation ensures the rounding increment is valid for rounding |
96 | /// timestamps. |
97 | pub(crate) fn for_timestamp( |
98 | unit: Unit, |
99 | increment: i64, |
100 | ) -> Result<t::NoUnits128, Error> { |
101 | // Indexed by `Unit`. |
102 | static MAX: &[Constant] = &[ |
103 | t::NANOS_PER_CIVIL_DAY, |
104 | t::MICROS_PER_CIVIL_DAY, |
105 | t::MILLIS_PER_CIVIL_DAY, |
106 | t::SECONDS_PER_CIVIL_DAY, |
107 | t::MINUTES_PER_CIVIL_DAY, |
108 | t::HOURS_PER_CIVIL_DAY, |
109 | ]; |
110 | get_with_max(unit, increment, what:"timestamp" , MAX) |
111 | } |
112 | |
113 | fn get_with_limit( |
114 | unit: Unit, |
115 | increment: i64, |
116 | what: &'static str, |
117 | limit: &[t::Constant], |
118 | ) -> Result<t::NoUnits128, Error> { |
119 | // OK because `NoUnits` specifically allows any `i64` value. |
120 | let increment = t::NoUnits::new_unchecked(increment); |
121 | if increment <= C(0) { |
122 | return Err(err!( |
123 | "rounding increment {increment} for {unit} must be \ |
124 | greater than zero" , |
125 | unit = unit.plural(), |
126 | )); |
127 | } |
128 | let Some(must_divide) = limit.get(unit as usize) else { |
129 | return Err(err!( |
130 | " {what} rounding does not support {unit}" , |
131 | unit = unit.plural() |
132 | )); |
133 | }; |
134 | let must_divide = t::NoUnits::rfrom(*must_divide); |
135 | if increment >= must_divide || must_divide % increment != C(0) { |
136 | Err(err!( |
137 | "increment {increment} for rounding {what} to {unit} \ |
138 | must be 1) less than {must_divide}, 2) divide into \ |
139 | it evenly and 3) greater than zero" , |
140 | unit = unit.plural(), |
141 | )) |
142 | } else { |
143 | Ok(t::NoUnits128::rfrom(increment)) |
144 | } |
145 | } |
146 | |
147 | fn get_with_max( |
148 | unit: Unit, |
149 | increment: i64, |
150 | what: &'static str, |
151 | max: &[t::Constant], |
152 | ) -> Result<t::NoUnits128, Error> { |
153 | // OK because `NoUnits` specifically allows any `i64` value. |
154 | let increment = t::NoUnits::new_unchecked(increment); |
155 | if increment <= C(0) { |
156 | return Err(err!( |
157 | "rounding increment {increment} for {unit} must be \ |
158 | greater than zero" , |
159 | unit = unit.plural(), |
160 | )); |
161 | } |
162 | let Some(must_divide) = max.get(unit as usize) else { |
163 | return Err(err!( |
164 | " {what} rounding does not support {unit}" , |
165 | unit = unit.plural() |
166 | )); |
167 | }; |
168 | let must_divide = t::NoUnits::rfrom(*must_divide); |
169 | if increment > must_divide || must_divide % increment != C(0) { |
170 | Err(err!( |
171 | "increment {increment} for rounding {what} to {unit} \ |
172 | must be 1) less than or equal to {must_divide}, \ |
173 | 2) divide into it evenly and 3) greater than zero" , |
174 | unit = unit.plural(), |
175 | )) |
176 | } else { |
177 | Ok(t::NoUnits128::rfrom(increment)) |
178 | } |
179 | } |
180 | |