1use uqa_core::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.months == i32::MIN && self.days == i32::MIN && self.micros == i64::MIN)
62 || (self.months == i32::MAX && self.days == i32::MAX && self.micros == i64::MAX)
63 }
64
65 fn finite(self) -> Result<Self> {
66 if self.is_reserved() {
67 Err(datetime_out_of_range("interval"))
68 } else {
69 Ok(self)
70 }
71 }
72
73 pub fn negate(self) -> Result<Self> {
75 match (
76 self.months.checked_neg(),
77 self.days.checked_neg(),
78 self.micros.checked_neg(),
79 ) {
80 (Some(months), Some(days), Some(micros)) => Self {
81 months,
82 days,
83 micros,
84 }
85 .finite(),
86 _ => Err(datetime_out_of_range("interval")),
87 }
88 }
89
90 pub fn plus(self, other: Self) -> Result<Self> {
92 match (
93 self.months.checked_add(other.months),
94 self.days.checked_add(other.days),
95 self.micros.checked_add(other.micros),
96 ) {
97 (Some(months), Some(days), Some(micros)) => Self {
98 months,
99 days,
100 micros,
101 }
102 .finite(),
103 _ => Err(datetime_out_of_range("interval")),
104 }
105 }
106
107 pub fn minus(self, other: Self) -> Result<Self> {
109 match (
110 self.months.checked_sub(other.months),
111 self.days.checked_sub(other.days),
112 self.micros.checked_sub(other.micros),
113 ) {
114 (Some(months), Some(days), Some(micros)) => Self {
115 months,
116 days,
117 micros,
118 }
119 .finite(),
120 _ => Err(datetime_out_of_range("interval")),
121 }
122 }
123
124 pub fn multiply(self, factor: f64) -> Result<Self> {
126 if factor.is_nan() || factor.is_infinite() {
127 return Err(datetime_out_of_range("interval"));
128 }
129 self.scale(|field| field * factor)
130 }
131
132 pub fn divide(self, factor: f64) -> Result<Self> {
134 if factor == 0.0 {
135 return Err(division_by_zero());
136 }
137 if factor.is_nan() {
138 return Err(datetime_out_of_range("interval"));
139 }
140 self.scale(|field| field / factor)
141 }
142
143 pub fn justify_hours(self) -> Result<Self> {
145 let whole_days = self.micros / USECS_PER_DAY;
146 let days = self
147 .days
148 .checked_add(whole_days as i32)
149 .ok_or_else(|| datetime_out_of_range("interval"))?;
150 let (days, micros) = align_time_with_days(days, self.micros - whole_days * USECS_PER_DAY);
151 Ok(Self {
152 months: self.months,
153 days,
154 micros,
155 })
156 }
157
158 pub fn justify_days(self) -> Result<Self> {
160 let (months, days) = carry_whole_months(self.months, self.days)?;
161 let (months, days) = if months > 0 && days < 0 {
162 (months - 1, days + DAYS_PER_MONTH_INT)
163 } else if months < 0 && days > 0 {
164 (months + 1, days - DAYS_PER_MONTH_INT)
165 } else {
166 (months, days)
167 };
168 Ok(Self {
169 months,
170 days,
171 micros: self.micros,
172 })
173 }
174
175 pub fn justify_interval(self) -> Result<Self> {
177 let (mut months, mut days) = (self.months, self.days);
178 if (days > 0 && self.micros > 0) || (days < 0 && self.micros < 0) {
179 (months, days) = carry_whole_months(months, days)?;
180 }
181 let whole_days = self.micros / USECS_PER_DAY;
182 let mut micros = self.micros - whole_days * USECS_PER_DAY;
183 days = i32::try_from(i64::from(days) + whole_days)
185 .map_err(|_| datetime_out_of_range("interval"))?;
186 (months, days) = carry_whole_months(months, days)?;
187 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
188 days += DAYS_PER_MONTH_INT;
189 months -= 1;
190 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
191 days -= DAYS_PER_MONTH_INT;
192 months += 1;
193 }
194 (days, micros) = align_time_with_days(days, micros);
195 Ok(Self {
196 months,
197 days,
198 micros,
199 })
200 }
201
202 fn scale(self, scale: impl Fn(f64) -> f64) -> Result<Self> {
204 let months = truncate_to_i32(scale(f64::from(self.months)))?;
205 let days = truncate_to_i32(scale(f64::from(self.days)))?;
206 let month_remainder_days =
207 timestamp_round((scale(f64::from(self.months)) - f64::from(months)) * DAYS_PER_MONTH);
208 let mut sec_remainder = timestamp_round(
209 (scale(f64::from(self.days)) - f64::from(days) + month_remainder_days
210 - f64::from(month_remainder_days as i32))
211 * SECS_PER_DAY,
212 );
213 let mut days = days;
214 if sec_remainder.abs() >= SECS_PER_DAY {
216 let whole_days = (sec_remainder / SECS_PER_DAY) as i32;
217 days = days
218 .checked_add(whole_days)
219 .ok_or_else(|| datetime_out_of_range("interval"))?;
220 sec_remainder -= f64::from(whole_days) * SECS_PER_DAY;
221 }
222 let days = days
223 .checked_add(month_remainder_days as i32)
224 .ok_or_else(|| datetime_out_of_range("interval"))?;
225 let micros = (scale(self.micros as f64) + sec_remainder * USECS_PER_SEC).round_ties_even();
226 if micros.is_nan() || !(micros >= i64::MIN as f64 && micros < -(i64::MIN as f64)) {
227 return Err(datetime_out_of_range("interval"));
228 }
229 Self {
230 months,
231 days,
232 micros: micros as i64,
233 }
234 .finite()
235 }
236}
237
238fn carry_whole_months(months: i32, days: i32) -> Result<(i32, i32)> {
240 let whole_months = days / DAYS_PER_MONTH_INT;
241 let months = months
242 .checked_add(whole_months)
243 .ok_or_else(|| datetime_out_of_range("interval"))?;
244 Ok((months, days - whole_months * DAYS_PER_MONTH_INT))
245}
246
247fn align_time_with_days(days: i32, micros: i64) -> (i32, i64) {
249 if days > 0 && micros < 0 {
250 (days - 1, micros + USECS_PER_DAY)
251 } else if days < 0 && micros > 0 {
252 (days + 1, micros - USECS_PER_DAY)
253 } else {
254 (days, micros)
255 }
256}
257
258fn truncate_to_i32(value: f64) -> Result<i32> {
260 if value.is_nan() || !(value >= f64::from(i32::MIN) && value < -f64::from(i32::MIN)) {
261 return Err(datetime_out_of_range("interval"));
262 }
263 Ok(value as i32)
264}
265
266fn timestamp_round(value: f64) -> f64 {
268 (value * USECS_PER_SEC).round_ties_even() / USECS_PER_SEC
269}
270
271#[cfg(test)]
272mod tests;