1use uqa_core::{TemporalInfinity, TemporalValue};
10
11use super::super::{datetime_out_of_range, division_by_zero};
12use crate::error::Result;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct IntervalFields {
17 pub months: i32,
18 pub days: i32,
19 pub micros: i64,
20}
21
22const DAYS_PER_MONTH_INT: i32 = 30;
23const DAYS_PER_MONTH: f64 = 30.0;
24const SECS_PER_DAY: f64 = 86_400.0;
25const USECS_PER_SEC: f64 = 1_000_000.0;
26const USECS_PER_DAY: i64 = 86_400_000_000;
27
28impl IntervalFields {
29 pub const ZERO: Self = Self {
30 months: 0,
31 days: 0,
32 micros: 0,
33 };
34
35 pub fn of(value: &TemporalValue) -> Option<Self> {
37 match *value {
38 TemporalValue::Interval {
39 months,
40 days,
41 micros,
42 } => Some(Self {
43 months,
44 days,
45 micros,
46 }),
47 _ => None,
48 }
49 }
50
51 pub fn value(self) -> TemporalValue {
52 TemporalValue::Interval {
53 months: self.months,
54 days: self.days,
55 micros: self.micros,
56 }
57 }
58
59 fn is_reserved(self) -> bool {
61 !self.value().is_finite()
62 }
63
64 fn infinite(infinity: TemporalInfinity) -> Self {
65 Self::of(&infinity.interval()).expect("interval endpoint")
66 }
67
68 fn finite(self) -> Result<Self> {
69 if self.is_reserved() {
70 Err(datetime_out_of_range("interval"))
71 } else {
72 Ok(self)
73 }
74 }
75
76 pub fn negate(self) -> Result<Self> {
78 if let Some(infinity) = self.value().infinity() {
79 return Ok(Self::infinite(infinity.negate()));
80 }
81 match (
82 self.months.checked_neg(),
83 self.days.checked_neg(),
84 self.micros.checked_neg(),
85 ) {
86 (Some(months), Some(days), Some(micros)) => Self {
87 months,
88 days,
89 micros,
90 }
91 .finite(),
92 _ => Err(datetime_out_of_range("interval")),
93 }
94 }
95
96 pub fn plus(self, other: Self) -> Result<Self> {
98 if let Some(infinity) = super::infinite::add(
99 self.value().infinity(),
100 other.value().infinity(),
101 "interval",
102 )? {
103 return Ok(Self::infinite(infinity));
104 }
105 match (
106 self.months.checked_add(other.months),
107 self.days.checked_add(other.days),
108 self.micros.checked_add(other.micros),
109 ) {
110 (Some(months), Some(days), Some(micros)) => Self {
111 months,
112 days,
113 micros,
114 }
115 .finite(),
116 _ => Err(datetime_out_of_range("interval")),
117 }
118 }
119
120 pub fn minus(self, other: Self) -> Result<Self> {
122 if let Some(infinity) =
123 super::infinite::subtract(self.value().infinity(), other.value().infinity())?
124 {
125 return Ok(Self::infinite(infinity));
126 }
127 match (
128 self.months.checked_sub(other.months),
129 self.days.checked_sub(other.days),
130 self.micros.checked_sub(other.micros),
131 ) {
132 (Some(months), Some(days), Some(micros)) => Self {
133 months,
134 days,
135 micros,
136 }
137 .finite(),
138 _ => Err(datetime_out_of_range("interval")),
139 }
140 }
141
142 pub fn multiply(self, factor: f64) -> Result<Self> {
144 if factor.is_nan() {
145 return Err(datetime_out_of_range("interval"));
146 }
147 if let Some(infinity) = self.value().infinity() {
148 if factor == 0.0 {
149 return Err(datetime_out_of_range("interval"));
150 }
151 return Ok(Self::infinite(if factor < 0.0 {
152 infinity.negate()
153 } else {
154 infinity
155 }));
156 }
157 if factor.is_infinite() {
158 let ordering = self.value().cmp(&Self::ZERO.value());
159 if ordering.is_eq() {
160 return Err(datetime_out_of_range("interval"));
161 }
162 return Ok(Self::infinite(
163 if ordering.is_lt() == factor.is_sign_negative() {
164 TemporalInfinity::Positive
165 } else {
166 TemporalInfinity::Negative
167 },
168 ));
169 }
170 self.scale(|field| field * factor)
171 }
172
173 pub fn divide(self, factor: f64) -> Result<Self> {
175 if factor == 0.0 {
176 return Err(division_by_zero());
177 }
178 if factor.is_nan() {
179 return Err(datetime_out_of_range("interval"));
180 }
181 if let Some(infinity) = self.value().infinity() {
182 if factor.is_infinite() {
183 return Err(datetime_out_of_range("interval"));
184 }
185 return Ok(Self::infinite(if factor < 0.0 {
186 infinity.negate()
187 } else {
188 infinity
189 }));
190 }
191 self.scale(|field| field / factor)
192 }
193
194 pub fn justify_hours(self) -> Result<Self> {
196 if self.is_reserved() {
197 return Ok(self);
198 }
199 let whole_days = self.micros / USECS_PER_DAY;
200 let days = self
201 .days
202 .checked_add(whole_days as i32)
203 .ok_or_else(|| datetime_out_of_range("interval"))?;
204 let (days, micros) = align_time_with_days(days, self.micros - whole_days * USECS_PER_DAY);
205 Ok(Self {
206 months: self.months,
207 days,
208 micros,
209 })
210 }
211
212 pub fn justify_days(self) -> Result<Self> {
214 if self.is_reserved() {
215 return Ok(self);
216 }
217 let (months, days) = carry_whole_months(self.months, self.days)?;
218 let (months, days) = if months > 0 && days < 0 {
219 (months - 1, days + DAYS_PER_MONTH_INT)
220 } else if months < 0 && days > 0 {
221 (months + 1, days - DAYS_PER_MONTH_INT)
222 } else {
223 (months, days)
224 };
225 Ok(Self {
226 months,
227 days,
228 micros: self.micros,
229 })
230 }
231
232 pub fn justify_interval(self) -> Result<Self> {
234 if self.is_reserved() {
235 return Ok(self);
236 }
237 let (mut months, mut days) = (self.months, self.days);
238 if (days > 0 && self.micros > 0) || (days < 0 && self.micros < 0) {
239 (months, days) = carry_whole_months(months, days)?;
240 }
241 let whole_days = self.micros / USECS_PER_DAY;
242 let mut micros = self.micros - whole_days * USECS_PER_DAY;
243 days = i32::try_from(i64::from(days) + whole_days)
245 .map_err(|_| datetime_out_of_range("interval"))?;
246 (months, days) = carry_whole_months(months, days)?;
247 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
248 days += DAYS_PER_MONTH_INT;
249 months -= 1;
250 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
251 days -= DAYS_PER_MONTH_INT;
252 months += 1;
253 }
254 (days, micros) = align_time_with_days(days, micros);
255 Ok(Self {
256 months,
257 days,
258 micros,
259 })
260 }
261
262 fn scale(self, scale: impl Fn(f64) -> f64) -> Result<Self> {
264 let months = truncate_to_i32(scale(f64::from(self.months)))?;
265 let days = truncate_to_i32(scale(f64::from(self.days)))?;
266 let month_remainder_days =
267 timestamp_round((scale(f64::from(self.months)) - f64::from(months)) * DAYS_PER_MONTH);
268 let mut sec_remainder = timestamp_round(
269 (scale(f64::from(self.days)) - f64::from(days) + month_remainder_days
270 - f64::from(month_remainder_days as i32))
271 * SECS_PER_DAY,
272 );
273 let mut days = days;
274 if sec_remainder.abs() >= SECS_PER_DAY {
276 let whole_days = (sec_remainder / SECS_PER_DAY) as i32;
277 days = days
278 .checked_add(whole_days)
279 .ok_or_else(|| datetime_out_of_range("interval"))?;
280 sec_remainder -= f64::from(whole_days) * SECS_PER_DAY;
281 }
282 let days = days
283 .checked_add(month_remainder_days as i32)
284 .ok_or_else(|| datetime_out_of_range("interval"))?;
285 let micros = (scale(self.micros as f64) + sec_remainder * USECS_PER_SEC).round_ties_even();
286 if micros.is_nan() || !(micros >= i64::MIN as f64 && micros < -(i64::MIN as f64)) {
287 return Err(datetime_out_of_range("interval"));
288 }
289 Self {
290 months,
291 days,
292 micros: micros as i64,
293 }
294 .finite()
295 }
296}
297
298fn carry_whole_months(months: i32, days: i32) -> Result<(i32, i32)> {
300 let whole_months = days / DAYS_PER_MONTH_INT;
301 let months = months
302 .checked_add(whole_months)
303 .ok_or_else(|| datetime_out_of_range("interval"))?;
304 Ok((months, days - whole_months * DAYS_PER_MONTH_INT))
305}
306
307fn align_time_with_days(days: i32, micros: i64) -> (i32, i64) {
309 if days > 0 && micros < 0 {
310 (days - 1, micros + USECS_PER_DAY)
311 } else if days < 0 && micros > 0 {
312 (days + 1, micros - USECS_PER_DAY)
313 } else {
314 (days, micros)
315 }
316}
317
318fn truncate_to_i32(value: f64) -> Result<i32> {
320 if value.is_nan() || !(value >= f64::from(i32::MIN) && value < -f64::from(i32::MIN)) {
321 return Err(datetime_out_of_range("interval"));
322 }
323 Ok(value as i32)
324}
325
326fn timestamp_round(value: f64) -> f64 {
328 (value * USECS_PER_SEC).round_ties_even() / USECS_PER_SEC
329}
330
331#[cfg(test)]
332mod tests;