Skip to main content

rupg_func/
datetime.rs

1//! The arithmetic of the date and time types: an interval added to a timestamp, a time or a date, the difference of two timestamps, `age`, the `justify` functions, the operators of `interval` and `OVERLAPS`. The rules are those of `timestamp.c` and `date.c`.
2
3use std::cmp::Ordering;
4
5use rupg_common::{Error, Result, SqlState};
6use rupg_types::{
7    DATE_INFINITY, DATE_NEGATIVE_INFINITY, Interval, POSTGRES_EPOCH_JDATE, TIMESTAMP_INFINITY,
8    TIMESTAMP_NEGATIVE_INFINITY, TimeZone, UNIX_TO_POSTGRES_USECS, USECS_PER_DAY, USECS_PER_SEC,
9    Value, date2j, j2date,
10};
11
12use crate::cast::{
13    TIMESTAMP_END_JULIAN, infinite, local, midnight, timestamp_range, valid_timestamp,
14};
15use crate::compare::{compare, span};
16use crate::timezone::lookup_timezone;
17use crate::{Call, Kernel, bad_value};
18
19/// `DAYS_PER_MONTH`.
20const DAYS_PER_MONTH: i32 = 30;
21/// `SECS_PER_DAY`.
22const SECS_PER_DAY: i32 = 86_400;
23/// `MONTHS_PER_YEAR`.
24const MONTHS_PER_YEAR: i32 = 12;
25
26fn interval_range() -> Error {
27    Error::new(SqlState::DATETIME_VALUE_OUT_OF_RANGE, "interval out of range")
28}
29
30fn date_range() -> Error {
31    Error::new(SqlState::DATETIME_VALUE_OUT_OF_RANGE, "date out of range for timestamp")
32}
33
34pub(crate) fn is_nobegin(interval: &Interval) -> bool {
35    *interval == Interval::NEGATIVE_INFINITY
36}
37
38pub(crate) fn is_noend(interval: &Interval) -> bool {
39    *interval == Interval::INFINITY
40}
41
42/// `INTERVAL_NOT_FINITE`.
43fn not_finite(interval: &Interval) -> bool {
44    is_nobegin(interval) || is_noend(interval)
45}
46
47pub(crate) fn interval_arg(v: &Value) -> Result<Interval> {
48    let Value::Interval(interval) = v else { return Err(bad_value()) };
49    Ok(*interval)
50}
51
52pub(crate) fn timestamp_arg(v: &Value) -> Result<i64> {
53    match v {
54        Value::Timestamp(t) | Value::TimestampTz(t) => Ok(*t),
55        _ => Err(bad_value()),
56    }
57}
58
59fn float_arg(v: &Value) -> Result<f64> {
60    let Value::Float8(f) = v else { return Err(bad_value()) };
61    Ok(*f)
62}
63
64/// `interval_um_internal`.
65pub(crate) fn negate(interval: &Interval) -> Result<Interval> {
66    if is_nobegin(interval) {
67        return Ok(Interval::INFINITY);
68    }
69    if is_noend(interval) {
70        return Ok(Interval::NEGATIVE_INFINITY);
71    }
72    let result = Interval {
73        time: 0i64.checked_sub(interval.time).ok_or_else(interval_range)?,
74        day: 0i32.checked_sub(interval.day).ok_or_else(interval_range)?,
75        month: 0i32.checked_sub(interval.month).ok_or_else(interval_range)?,
76    };
77    if not_finite(&result) {
78        return Err(interval_range());
79    }
80    Ok(result)
81}
82
83/// The sum of two intervals, as `interval_pl` gives it.
84pub(crate) fn interval_plus(a: &Interval, b: &Interval) -> Result<Interval> {
85    if is_nobegin(a) {
86        if is_noend(b) {
87            return Err(interval_range());
88        }
89        return Ok(Interval::NEGATIVE_INFINITY);
90    }
91    if is_noend(a) {
92        if is_nobegin(b) {
93            return Err(interval_range());
94        }
95        return Ok(Interval::INFINITY);
96    }
97    if not_finite(b) {
98        return Ok(*b);
99    }
100    let result = Interval {
101        month: a.month.checked_add(b.month).ok_or_else(interval_range)?,
102        day: a.day.checked_add(b.day).ok_or_else(interval_range)?,
103        time: a.time.checked_add(b.time).ok_or_else(interval_range)?,
104    };
105    if not_finite(&result) {
106        return Err(interval_range());
107    }
108    Ok(result)
109}
110
111/// The difference of two intervals, as `interval_mi` gives it.
112pub(crate) fn interval_minus(a: &Interval, b: &Interval) -> Result<Interval> {
113    if is_nobegin(a) {
114        if is_nobegin(b) {
115            return Err(interval_range());
116        }
117        return Ok(Interval::NEGATIVE_INFINITY);
118    }
119    if is_noend(a) {
120        if is_noend(b) {
121            return Err(interval_range());
122        }
123        return Ok(Interval::INFINITY);
124    }
125    if is_nobegin(b) {
126        return Ok(Interval::INFINITY);
127    }
128    if is_noend(b) {
129        return Ok(Interval::NEGATIVE_INFINITY);
130    }
131    let result = Interval {
132        month: a.month.checked_sub(b.month).ok_or_else(interval_range)?,
133        day: a.day.checked_sub(b.day).ok_or_else(interval_range)?,
134        time: a.time.checked_sub(b.time).ok_or_else(interval_range)?,
135    };
136    if not_finite(&result) {
137        return Err(interval_range());
138    }
139    Ok(result)
140}
141
142fn interval_pl(_: &Call<'_>, args: &[Value]) -> Result<Value> {
143    let [a, b] = args else { return Err(bad_value()) };
144    interval_plus(&interval_arg(a)?, &interval_arg(b)?).map(Value::Interval)
145}
146
147fn interval_mi(_: &Call<'_>, args: &[Value]) -> Result<Value> {
148    let [a, b] = args else { return Err(bad_value()) };
149    interval_minus(&interval_arg(a)?, &interval_arg(b)?).map(Value::Interval)
150}
151
152fn interval_um(_: &Call<'_>, args: &[Value]) -> Result<Value> {
153    let [a] = args else { return Err(bad_value()) };
154    negate(&interval_arg(a)?).map(Value::Interval)
155}
156
157/// `TSROUND`: a number rounded to microseconds.
158fn ts_round(x: f64) -> f64 {
159    (x * 1_000_000.0).round_ties_even() / 1_000_000.0
160}
161
162/// `FLOAT8_FITS_IN_INT32`.
163fn fits_i32(x: f64) -> bool {
164    x >= f64::from(i32::MIN) && x < -f64::from(i32::MIN)
165}
166
167/// `FLOAT8_FITS_IN_INT64`.
168#[allow(clippy::cast_precision_loss)]
169fn fits_i64(x: f64) -> bool {
170    x >= i64::MIN as f64 && x < -(i64::MIN as f64)
171}
172
173/// The finite part of `interval_mul` and `interval_div`: `op` multiplies or divides each field by the factor. The fractions of the months and the days go down into the days and the time.
174#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
175fn scale(interval: &Interval, op: impl Fn(f64) -> f64) -> Result<Interval> {
176    let months = op(f64::from(interval.month));
177    if months.is_nan() || !fits_i32(months) {
178        return Err(interval_range());
179    }
180    let month = months as i32;
181    let days = op(f64::from(interval.day));
182    if days.is_nan() || !fits_i32(days) {
183        return Err(interval_range());
184    }
185    let mut day = days as i32;
186    let month_remainder_days = ts_round((months - f64::from(month)) * f64::from(DAYS_PER_MONTH));
187    let mut sec_remainder = ts_round(
188        (days - f64::from(day) + month_remainder_days - f64::from(month_remainder_days as i32))
189            * f64::from(SECS_PER_DAY),
190    );
191    if sec_remainder.abs() >= f64::from(SECS_PER_DAY) {
192        let whole = (sec_remainder / f64::from(SECS_PER_DAY)) as i32;
193        day = day.checked_add(whole).ok_or_else(interval_range)?;
194        sec_remainder -= f64::from(whole * SECS_PER_DAY);
195    }
196    day = day.checked_add(month_remainder_days as i32).ok_or_else(interval_range)?;
197    let time = (op(interval.time as f64) + sec_remainder * USECS_PER_SEC as f64).round_ties_even();
198    if time.is_nan() || !fits_i64(time) {
199        return Err(interval_range());
200    }
201    let result = Interval { time: time as i64, day, month };
202    if not_finite(&result) {
203        return Err(interval_range());
204    }
205    Ok(result)
206}
207
208/// `interval_mul`.
209fn interval_times(interval: &Interval, factor: f64) -> Result<Interval> {
210    if factor.is_nan() {
211        return Err(interval_range());
212    }
213    if not_finite(interval) {
214        if factor == 0.0 {
215            return Err(interval_range());
216        }
217        return if factor < 0.0 { negate(interval) } else { Ok(*interval) };
218    }
219    if factor.is_infinite() {
220        let sign = match span(interval).cmp(&0) {
221            Ordering::Less => -1.0,
222            Ordering::Equal => return Err(interval_range()),
223            Ordering::Greater => 1.0,
224        };
225        return Ok(if factor * sign < 0.0 {
226            Interval::NEGATIVE_INFINITY
227        } else {
228            Interval::INFINITY
229        });
230    }
231    scale(interval, |x| x * factor)
232}
233
234/// `IntervalAggState`: the state of `sum` and `avg` of `interval`. The infinite values do not count in `count` and `sum`.
235#[derive(Clone, Debug)]
236pub struct IntervalSum {
237    count: i64,
238    sum: Interval,
239    pinf: i64,
240    ninf: i64,
241}
242
243impl Default for IntervalSum {
244    fn default() -> Self {
245        IntervalSum { count: 0, sum: Interval { time: 0, day: 0, month: 0 }, pinf: 0, ninf: 0 }
246    }
247}
248
249impl IntervalSum {
250    /// `do_interval_accum`: adds one value.
251    pub fn add(&mut self, value: &Interval) -> Result<()> {
252        if is_nobegin(value) {
253            self.ninf += 1;
254        } else if is_noend(value) {
255            self.pinf += 1;
256        } else {
257            self.sum = interval_plus(&self.sum, value)?;
258            self.count += 1;
259        }
260        Ok(())
261    }
262
263    /// The infinite result when the values have infinities of one sign, or the error when they have both signs. `None` when all the values are finite.
264    fn infinite(&self) -> Result<Option<Interval>> {
265        match (self.pinf > 0, self.ninf > 0) {
266            (true, true) => Err(interval_range()),
267            (true, false) => Ok(Some(Interval::INFINITY)),
268            (false, true) => Ok(Some(Interval::NEGATIVE_INFINITY)),
269            (false, false) => Ok(None),
270        }
271    }
272
273    /// `interval_sum`: the sum, or `None` when there were no values.
274    pub fn sum(&self) -> Result<Option<Interval>> {
275        if self.count + self.pinf + self.ninf == 0 {
276            return Ok(None);
277        }
278        Ok(Some(self.infinite()?.unwrap_or(self.sum)))
279    }
280
281    /// `interval_avg`: the average, or `None` when there were no values.
282    pub fn avg(&self) -> Result<Option<Interval>> {
283        if self.count + self.pinf + self.ninf == 0 {
284            return Ok(None);
285        }
286        if let Some(infinite) = self.infinite()? {
287            return Ok(Some(infinite));
288        }
289        #[allow(clippy::cast_precision_loss)]
290        let count = self.count as f64;
291        scale(&self.sum, |x| x / count).map(Some)
292    }
293}
294
295/// `interval_lerp` of `orderedsetaggs.c`: the interval at the fraction `pct` of the way from `lo` to `hi`, for `percentile_cont`.
296pub fn interval_lerp(lo: &Interval, hi: &Interval, pct: f64) -> Result<Interval> {
297    interval_plus(&interval_times(&interval_minus(hi, lo)?, pct)?, lo)
298}
299
300fn interval_mul(_: &Call<'_>, args: &[Value]) -> Result<Value> {
301    let [a, b] = args else { return Err(bad_value()) };
302    interval_times(&interval_arg(a)?, float_arg(b)?).map(Value::Interval)
303}
304
305fn mul_d_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
306    let [a, b] = args else { return Err(bad_value()) };
307    interval_times(&interval_arg(b)?, float_arg(a)?).map(Value::Interval)
308}
309
310fn interval_div(_: &Call<'_>, args: &[Value]) -> Result<Value> {
311    let [a, b] = args else { return Err(bad_value()) };
312    let (interval, factor) = (interval_arg(a)?, float_arg(b)?);
313    if factor == 0.0 {
314        return Err(Error::new(SqlState::DIVISION_BY_ZERO, "division by zero"));
315    }
316    if factor.is_nan() {
317        return Err(interval_range());
318    }
319    if not_finite(&interval) {
320        if factor.is_infinite() {
321            return Err(interval_range());
322        }
323        let result = if factor < 0.0 { negate(&interval)? } else { interval };
324        return Ok(Value::Interval(result));
325    }
326    scale(&interval, |x| x / factor).map(Value::Interval)
327}
328
329/// `interval_justify_hours`: the whole days of the time go into the days.
330fn justify_hours(interval: &Interval) -> Result<Interval> {
331    if not_finite(interval) {
332        return Ok(*interval);
333    }
334    let mut result = *interval;
335    let whole = result.time / USECS_PER_DAY;
336    result.time -= whole * USECS_PER_DAY;
337    let whole = i32::try_from(whole).map_err(|_| interval_range())?;
338    result.day = result.day.checked_add(whole).ok_or_else(interval_range)?;
339    if result.day > 0 && result.time < 0 {
340        result.time += USECS_PER_DAY;
341        result.day -= 1;
342    } else if result.day < 0 && result.time > 0 {
343        result.time -= USECS_PER_DAY;
344        result.day += 1;
345    }
346    Ok(result)
347}
348
349/// `interval_justify_days`: each 30 days go into the months.
350fn justify_days(interval: &Interval) -> Result<Interval> {
351    if not_finite(interval) {
352        return Ok(*interval);
353    }
354    let mut result = *interval;
355    let whole = result.day / DAYS_PER_MONTH;
356    result.day -= whole * DAYS_PER_MONTH;
357    result.month = result.month.checked_add(whole).ok_or_else(interval_range)?;
358    if result.month > 0 && result.day < 0 {
359        result.day += DAYS_PER_MONTH;
360        result.month -= 1;
361    } else if result.month < 0 && result.day > 0 {
362        result.day -= DAYS_PER_MONTH;
363        result.month += 1;
364    }
365    Ok(result)
366}
367
368/// `interval_justify_interval`: the two justifications, and one sign for the three fields.
369#[allow(clippy::cast_possible_truncation)]
370fn justify_interval(interval: &Interval) -> Result<Interval> {
371    if not_finite(interval) {
372        return Ok(*interval);
373    }
374    let mut result = *interval;
375    if (result.day > 0 && result.time > 0) || (result.day < 0 && result.time < 0) {
376        let whole = result.day / DAYS_PER_MONTH;
377        result.day -= whole * DAYS_PER_MONTH;
378        result.month = result.month.checked_add(whole).ok_or_else(interval_range)?;
379    }
380    let whole = result.time / USECS_PER_DAY;
381    result.time -= whole * USECS_PER_DAY;
382    result.day += whole as i32;
383    let whole = result.day / DAYS_PER_MONTH;
384    result.day -= whole * DAYS_PER_MONTH;
385    result.month = result.month.checked_add(whole).ok_or_else(interval_range)?;
386    if result.month > 0 && (result.day < 0 || (result.day == 0 && result.time < 0)) {
387        result.day += DAYS_PER_MONTH;
388        result.month -= 1;
389    } else if result.month < 0 && (result.day > 0 || (result.day == 0 && result.time > 0)) {
390        result.day -= DAYS_PER_MONTH;
391        result.month += 1;
392    }
393    if result.day > 0 && result.time < 0 {
394        result.time += USECS_PER_DAY;
395        result.day -= 1;
396    } else if result.day < 0 && result.time > 0 {
397        result.time -= USECS_PER_DAY;
398        result.day += 1;
399    }
400    Ok(result)
401}
402
403fn interval_justify_hours(_: &Call<'_>, args: &[Value]) -> Result<Value> {
404    let [a] = args else { return Err(bad_value()) };
405    justify_hours(&interval_arg(a)?).map(Value::Interval)
406}
407
408fn interval_justify_days(_: &Call<'_>, args: &[Value]) -> Result<Value> {
409    let [a] = args else { return Err(bad_value()) };
410    justify_days(&interval_arg(a)?).map(Value::Interval)
411}
412
413fn interval_justify_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
414    let [a] = args else { return Err(bad_value()) };
415    justify_interval(&interval_arg(a)?).map(Value::Interval)
416}
417
418/// The date fields and the time of day of a timestamp, as `struct pg_tm` and `fsec_t` hold them.
419pub(crate) struct Tm {
420    pub(crate) year: i32,
421    pub(crate) mon: i32,
422    pub(crate) mday: i32,
423    /// The microseconds since midnight.
424    pub(crate) time: i64,
425}
426
427/// `isleap`.
428fn is_leap(year: i32) -> bool {
429    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
430}
431
432/// `day_tab`: the days of a month.
433pub(crate) fn days_of_month(year: i32, mon: i32) -> i32 {
434    const DAYS: [i32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
435    if mon == 2 && is_leap(year) { 29 } else { DAYS[(mon - 1) as usize] }
436}
437
438impl Tm {
439    /// `timestamp2tm` with no zone, with the error of its callers.
440    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
441    pub(crate) fn of(ts: i64) -> Result<Tm> {
442        let julian = ts.div_euclid(USECS_PER_DAY) + i64::from(POSTGRES_EPOCH_JDATE);
443        let julian = i32::try_from(julian).ok().filter(|&j| j >= 0).ok_or_else(timestamp_range)?;
444        let (year, mon, mday) = j2date(julian);
445        Ok(Tm { year, mon: mon as i32, mday: mday as i32, time: ts.rem_euclid(USECS_PER_DAY) })
446    }
447
448    /// `timestamp2tm` in the zone of the session: the fields of the local time.
449    fn of_local(call: &Call<'_>, ts: i64) -> Result<Tm> {
450        Tm::of(ts)?;
451        Tm::of(local(call.session, ts)?)
452    }
453
454    /// `timestamp2tm` in a zone: the fields of the local time.
455    fn of_zone(zone: &dyn TimeZone, ts: i64) -> Result<Tm> {
456        Tm::of(ts)?;
457        let unix = ts.div_euclid(USECS_PER_SEC) - UNIX_TO_POSTGRES_USECS / USECS_PER_SEC;
458        let east = i64::from(zone.at(unix).0);
459        Tm::of(ts.checked_add(east * USECS_PER_SEC).ok_or_else(timestamp_range)?)
460    }
461
462    /// `tm2timestamp`. With a zone, the offset is the one that `DetermineTimeZoneOffset` gives for the fields.
463    pub(crate) fn timestamp(&self, zone: Option<&dyn TimeZone>) -> Result<i64> {
464        let local = self.local()?;
465        let west = zone.map_or(0, |zone| {
466            let seconds = local.div_euclid(USECS_PER_SEC) - UNIX_TO_POSTGRES_USECS / USECS_PER_SEC;
467            rupg_types::local_offset(seconds, zone).0
468        });
469        Tm::shift(local, west)
470    }
471
472    /// `tm2timestamp` with an offset in seconds west of UTC.
473    pub(crate) fn timestamp_west(&self, west: i32) -> Result<i64> {
474        Tm::shift(self.local()?, west)
475    }
476
477    /// The fields as a timestamp with no zone, with the checks of `tm2timestamp` before it applies the offset.
478    fn local(&self) -> Result<i64> {
479        let valid = (self.year > -4713 || (self.year == -4713 && self.mon >= 11))
480            && (self.year < 5_874_898 || (self.year == 5_874_898 && self.mon < 6));
481        if !valid {
482            return Err(timestamp_range());
483        }
484        let date = i64::from(date2j(self.year, self.mon, self.mday) - POSTGRES_EPOCH_JDATE);
485        date.checked_mul(USECS_PER_DAY)
486            .and_then(|t| t.checked_add(self.time))
487            .ok_or_else(timestamp_range)
488    }
489
490    /// `dt2local` with the offset, and the range check of `tm2timestamp`.
491    fn shift(local: i64, west: i32) -> Result<i64> {
492        let result = local.wrapping_add(i64::from(west) * USECS_PER_SEC);
493        if !valid_timestamp(result) {
494            return Err(timestamp_range());
495        }
496        Ok(result)
497    }
498}
499
500/// `timestamp_pl_interval`, and `timestamptz_pl_interval_internal` when `zone` is given: the months and the days are added to the fields of the local time, and then the time is added.
501pub(crate) fn timestamp_plus(
502    ts: i64,
503    interval: &Interval,
504    zone: Option<&dyn TimeZone>,
505) -> Result<i64> {
506    if is_nobegin(interval) {
507        if ts == TIMESTAMP_INFINITY {
508            return Err(timestamp_range());
509        }
510        return Ok(TIMESTAMP_NEGATIVE_INFINITY);
511    }
512    if is_noend(interval) {
513        if ts == TIMESTAMP_NEGATIVE_INFINITY {
514            return Err(timestamp_range());
515        }
516        return Ok(TIMESTAMP_INFINITY);
517    }
518    if infinite(ts) {
519        return Ok(ts);
520    }
521    let fields = |ts: i64| match zone {
522        Some(zone) => Tm::of_zone(zone, ts),
523        None => Tm::of(ts),
524    };
525    let mut ts = ts;
526    if interval.month != 0 {
527        let mut tm = fields(ts)?;
528        tm.mon = tm.mon.checked_add(interval.month).ok_or_else(timestamp_range)?;
529        if tm.mon > MONTHS_PER_YEAR {
530            tm.year = tm.year.wrapping_add((tm.mon - 1) / MONTHS_PER_YEAR);
531            tm.mon = (tm.mon - 1) % MONTHS_PER_YEAR + 1;
532        } else if tm.mon < 1 {
533            tm.year = tm.year.wrapping_add(tm.mon / MONTHS_PER_YEAR - 1);
534            tm.mon = tm.mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR;
535        }
536        tm.mday = tm.mday.min(days_of_month(tm.year, tm.mon));
537        ts = tm.timestamp(zone)?;
538    }
539    if interval.day != 0 {
540        let mut tm = fields(ts)?;
541        let lowest = if zone.is_some() { -1 } else { 0 };
542        let julian = date2j(tm.year, tm.mon, tm.mday)
543            .checked_add(interval.day)
544            .filter(|&j| j >= lowest)
545            .ok_or_else(timestamp_range)?;
546        let (year, mon, mday) = j2date(julian);
547        (tm.year, tm.mon, tm.mday) = (year, mon as i32, mday as i32);
548        ts = tm.timestamp(zone)?;
549    }
550    let ts = ts.checked_add(interval.time).ok_or_else(timestamp_range)?;
551    if !valid_timestamp(ts) {
552        return Err(timestamp_range());
553    }
554    Ok(ts)
555}
556
557fn timestamp_pl_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
558    let [a, b] = args else { return Err(bad_value()) };
559    timestamp_plus(timestamp_arg(a)?, &interval_arg(b)?, None).map(Value::Timestamp)
560}
561
562fn timestamp_mi_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
563    let [a, b] = args else { return Err(bad_value()) };
564    timestamp_plus(timestamp_arg(a)?, &negate(&interval_arg(b)?)?, None).map(Value::Timestamp)
565}
566
567fn timestamptz_pl_interval(call: &Call<'_>, args: &[Value]) -> Result<Value> {
568    let [a, b] = args else { return Err(bad_value()) };
569    let zone = call.session.zone()?;
570    timestamp_plus(timestamp_arg(a)?, &interval_arg(b)?, Some(zone.as_ref()))
571        .map(Value::TimestampTz)
572}
573
574fn timestamptz_mi_interval(call: &Call<'_>, args: &[Value]) -> Result<Value> {
575    let [a, b] = args else { return Err(bad_value()) };
576    let interval = negate(&interval_arg(b)?)?;
577    let zone = call.session.zone()?;
578    timestamp_plus(timestamp_arg(a)?, &interval, Some(zone.as_ref())).map(Value::TimestampTz)
579}
580
581/// `timestamptz_pl_interval_at_zone`: `date_add` with the arithmetic in a zone that a name gives.
582fn timestamptz_pl_interval_at_zone(call: &Call<'_>, args: &[Value]) -> Result<Value> {
583    let [a, b, zone] = args else { return Err(bad_value()) };
584    let zone = lookup_timezone(call, zone)?;
585    timestamp_plus(timestamp_arg(a)?, &interval_arg(b)?, Some(zone.as_ref()))
586        .map(Value::TimestampTz)
587}
588
589/// `timestamptz_mi_interval_at_zone`: `date_subtract` with the arithmetic in a zone that a name gives.
590fn timestamptz_mi_interval_at_zone(call: &Call<'_>, args: &[Value]) -> Result<Value> {
591    let [a, b, zone] = args else { return Err(bad_value()) };
592    let interval = negate(&interval_arg(b)?)?;
593    let zone = lookup_timezone(call, zone)?;
594    timestamp_plus(timestamp_arg(a)?, &interval, Some(zone.as_ref())).map(Value::TimestampTz)
595}
596
597/// `timestamp_mi`, also for `timestamptz`: the difference in microseconds, with the whole days in the days.
598fn timestamp_mi(_: &Call<'_>, args: &[Value]) -> Result<Value> {
599    let [a, b] = args else { return Err(bad_value()) };
600    let (a, b) = (timestamp_arg(a)?, timestamp_arg(b)?);
601    if infinite(a) || infinite(b) {
602        let result = match (a, b) {
603            (TIMESTAMP_NEGATIVE_INFINITY, TIMESTAMP_NEGATIVE_INFINITY)
604            | (TIMESTAMP_INFINITY, TIMESTAMP_INFINITY) => return Err(interval_range()),
605            (TIMESTAMP_NEGATIVE_INFINITY, _) | (_, TIMESTAMP_INFINITY) => {
606                Interval::NEGATIVE_INFINITY
607            }
608            _ => Interval::INFINITY,
609        };
610        return Ok(Value::Interval(result));
611    }
612    let time = a.checked_sub(b).ok_or_else(interval_range)?;
613    justify_hours(&Interval { time, day: 0, month: 0 }).map(Value::Interval)
614}
615
616/// `timestamp_age` and `timestamptz_age`: the differences of the fields, with a borrow from the next field when a field is negative.
617fn age(a: i64, b: i64, call: Option<&Call<'_>>) -> Result<Interval> {
618    match (a, b) {
619        (TIMESTAMP_NEGATIVE_INFINITY, TIMESTAMP_NEGATIVE_INFINITY)
620        | (TIMESTAMP_INFINITY, TIMESTAMP_INFINITY) => return Err(interval_range()),
621        (TIMESTAMP_NEGATIVE_INFINITY, _) | (_, TIMESTAMP_INFINITY) => {
622            return Ok(Interval::NEGATIVE_INFINITY);
623        }
624        (TIMESTAMP_INFINITY, _) | (_, TIMESTAMP_NEGATIVE_INFINITY) => {
625            return Ok(Interval::INFINITY);
626        }
627        _ => {}
628    }
629    let fields = |ts: i64| match call {
630        Some(call) => Tm::of_local(call, ts),
631        None => Tm::of(ts),
632    };
633    let (tm1, tm2) = (fields(a)?, fields(b)?);
634    let split = |tm: &Tm| {
635        let seconds = tm.time / USECS_PER_SEC;
636        [tm.time % USECS_PER_SEC, seconds % 60, seconds / 60 % 60, seconds / 3600]
637    };
638    let (t1, t2) = (split(&tm1), split(&tm2));
639    let sign = if a < b { -1 } else { 1 };
640    let mut usec = sign * (t1[0] - t2[0]);
641    let mut sec = sign * (t1[1] - t2[1]);
642    let mut min = sign * (t1[2] - t2[2]);
643    let mut hour = sign * (t1[3] - t2[3]);
644    let mut mday = sign * i64::from(tm1.mday - tm2.mday);
645    let mut mon = sign * i64::from(tm1.mon - tm2.mon);
646    let mut year = sign * (i64::from(tm1.year) - i64::from(tm2.year));
647    while usec < 0 {
648        usec += USECS_PER_SEC;
649        sec -= 1;
650    }
651    while sec < 0 {
652        sec += 60;
653        min -= 1;
654    }
655    while min < 0 {
656        min += 60;
657        hour -= 1;
658    }
659    while hour < 0 {
660        hour += 24;
661        mday -= 1;
662    }
663    let borrow = if a < b { &tm1 } else { &tm2 };
664    while mday < 0 {
665        mday += i64::from(days_of_month(borrow.year, borrow.mon));
666        mon -= 1;
667    }
668    while mon < 0 {
669        mon += i64::from(MONTHS_PER_YEAR);
670        year -= 1;
671    }
672    let months = sign * (year * i64::from(MONTHS_PER_YEAR) + mon);
673    let month = i32::try_from(months).map_err(|_| interval_range())?;
674    let day = i32::try_from(sign * mday).map_err(|_| interval_range())?;
675    let time = (sign * hour)
676        .checked_mul(3600 * USECS_PER_SEC)
677        .and_then(|t| t.checked_add(sign * min * 60 * USECS_PER_SEC))
678        .and_then(|t| t.checked_add(sign * sec * USECS_PER_SEC))
679        .and_then(|t| t.checked_add(sign * usec))
680        .ok_or_else(interval_range)?;
681    let result = Interval { time, day, month };
682    if not_finite(&result) {
683        return Err(interval_range());
684    }
685    Ok(result)
686}
687
688fn timestamp_age(_: &Call<'_>, args: &[Value]) -> Result<Value> {
689    let [a, b] = args else { return Err(bad_value()) };
690    age(timestamp_arg(a)?, timestamp_arg(b)?, None).map(Value::Interval)
691}
692
693fn timestamptz_age(call: &Call<'_>, args: &[Value]) -> Result<Value> {
694    let [a, b] = args else { return Err(bad_value()) };
695    age(timestamp_arg(a)?, timestamp_arg(b)?, Some(call)).map(Value::Interval)
696}
697
698/// A time of day plus a number of microseconds, which wraps at midnight.
699fn wrap_time(time: i64, add: i64) -> i64 {
700    time.wrapping_add(add).rem_euclid(USECS_PER_DAY)
701}
702
703fn infinite_time(add: bool) -> Error {
704    let message = if add {
705        "cannot add infinite interval to time"
706    } else {
707        "cannot subtract infinite interval from time"
708    };
709    Error::new(SqlState::DATETIME_VALUE_OUT_OF_RANGE, message)
710}
711
712fn time_pl_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
713    let [Value::Time(t), b] = args else { return Err(bad_value()) };
714    let interval = interval_arg(b)?;
715    if not_finite(&interval) {
716        return Err(infinite_time(true));
717    }
718    Ok(Value::Time(wrap_time(*t, interval.time)))
719}
720
721fn time_mi_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
722    let [Value::Time(t), b] = args else { return Err(bad_value()) };
723    let interval = interval_arg(b)?;
724    if not_finite(&interval) {
725        return Err(infinite_time(false));
726    }
727    Ok(Value::Time(wrap_time(*t, interval.time.wrapping_neg())))
728}
729
730fn timetz_pl_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
731    let [Value::TimeTz(t, zone), b] = args else { return Err(bad_value()) };
732    let interval = interval_arg(b)?;
733    if not_finite(&interval) {
734        return Err(infinite_time(true));
735    }
736    Ok(Value::TimeTz(wrap_time(*t, interval.time), *zone))
737}
738
739fn timetz_mi_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
740    let [Value::TimeTz(t, zone), b] = args else { return Err(bad_value()) };
741    let interval = interval_arg(b)?;
742    if not_finite(&interval) {
743        return Err(infinite_time(false));
744    }
745    Ok(Value::TimeTz(wrap_time(*t, interval.time.wrapping_neg()), *zone))
746}
747
748fn time_mi_time(_: &Call<'_>, args: &[Value]) -> Result<Value> {
749    let [Value::Time(a), Value::Time(b)] = args else { return Err(bad_value()) };
750    Ok(Value::Interval(Interval { time: a - b, day: 0, month: 0 }))
751}
752
753/// `interval_time`: the part of the interval after the whole days, with the floor for a negative interval.
754fn interval_time(_: &Call<'_>, args: &[Value]) -> Result<Value> {
755    let [a] = args else { return Err(bad_value()) };
756    let interval = interval_arg(a)?;
757    if not_finite(&interval) {
758        return Err(Error::new(
759            SqlState::DATETIME_VALUE_OUT_OF_RANGE,
760            "cannot convert infinite interval to time",
761        ));
762    }
763    Ok(Value::Time(interval.time.rem_euclid(USECS_PER_DAY)))
764}
765
766fn time_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
767    let [Value::Time(t)] = args else { return Err(bad_value()) };
768    Ok(Value::Interval(Interval { time: *t, day: 0, month: 0 }))
769}
770
771fn date_pl_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
772    let [Value::Date(d), b] = args else { return Err(bad_value()) };
773    timestamp_plus(midnight(*d)?, &interval_arg(b)?, None).map(Value::Timestamp)
774}
775
776fn date_mi_interval(_: &Call<'_>, args: &[Value]) -> Result<Value> {
777    let [Value::Date(d), b] = args else { return Err(bad_value()) };
778    timestamp_plus(midnight(*d)?, &negate(&interval_arg(b)?)?, None).map(Value::Timestamp)
779}
780
781/// `datetime_timestamp`: a date and a time of day.
782fn datetime_timestamp(_: &Call<'_>, args: &[Value]) -> Result<Value> {
783    let [Value::Date(d), Value::Time(t)] = args else { return Err(bad_value()) };
784    let result = midnight(*d)?;
785    if infinite(result) {
786        return Ok(Value::Timestamp(result));
787    }
788    let result = result + t;
789    if !valid_timestamp(result) {
790        return Err(timestamp_range());
791    }
792    Ok(Value::Timestamp(result))
793}
794
795/// `datetimetz_timestamptz`: a date and a time of day with its offset.
796fn datetimetz_timestamptz(_: &Call<'_>, args: &[Value]) -> Result<Value> {
797    let [Value::Date(d), Value::TimeTz(t, zone)] = args else { return Err(bad_value()) };
798    match *d {
799        DATE_NEGATIVE_INFINITY => return Ok(Value::TimestampTz(TIMESTAMP_NEGATIVE_INFINITY)),
800        DATE_INFINITY => return Ok(Value::TimestampTz(TIMESTAMP_INFINITY)),
801        d if d >= TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE => return Err(date_range()),
802        _ => {}
803    }
804    let result = i64::from(*d) * USECS_PER_DAY + t + i64::from(*zone) * USECS_PER_SEC;
805    if !valid_timestamp(result) {
806        return Err(date_range());
807    }
808    Ok(Value::TimestampTz(result))
809}
810
811/// `overlaps_timestamp`, `overlaps_time` and `overlaps_timetz`, which are not strict: a null end point gives null when the answer depends on it.
812fn overlaps(_: &Call<'_>, args: &[Value]) -> Result<Value> {
813    let [s1, e1, s2, e2] = args else { return Err(bad_value()) };
814    let order = |a: &Value, b: &Value| compare(0, a, 0, b);
815    /// The lesser end point first, or the end point that is not null and None.
816    fn ends<'a>(
817        s: &'a Value,
818        e: &'a Value,
819        order: &impl Fn(&Value, &Value) -> Result<Ordering>,
820    ) -> Result<Option<(&'a Value, Option<&'a Value>)>> {
821        Ok(match (s.is_null(), e.is_null()) {
822            (true, true) => None,
823            (true, false) => Some((e, None)),
824            (false, true) => Some((s, None)),
825            (false, false) if order(s, e)? == Ordering::Greater => Some((e, Some(s))),
826            (false, false) => Some((s, Some(e))),
827        })
828    }
829    let Some((s1, e1)) = ends(s1, e1, &order)? else { return Ok(Value::Null) };
830    let Some((s2, e2)) = ends(s2, e2, &order)? else { return Ok(Value::Null) };
831    // When the starts differ, the later start must be before the end of the other interval.
832    let later = |start: &Value, end: Option<&Value>, other_end: Option<&Value>| -> Result<Value> {
833        let Some(end) = end else { return Ok(Value::Null) };
834        if order(start, end)? == Ordering::Less {
835            return Ok(Value::Bool(true));
836        }
837        Ok(if other_end.is_none() { Value::Null } else { Value::Bool(false) })
838    };
839    match order(s1, s2)? {
840        Ordering::Greater => later(s1, e2, e1),
841        Ordering::Less => later(s2, e1, e2),
842        Ordering::Equal => {
843            Ok(if e1.is_none() || e2.is_none() { Value::Null } else { Value::Bool(true) })
844        }
845    }
846}
847
848/// The kernel of an internal function of this module by its `prosrc`.
849pub(crate) fn by_src(src: &str) -> Option<Kernel> {
850    Some(match src {
851        "interval_pl" => interval_pl,
852        "interval_mi" => interval_mi,
853        "interval_um" => interval_um,
854        "interval_mul" => interval_mul,
855        "mul_d_interval" => mul_d_interval,
856        "interval_div" => interval_div,
857        "interval_justify_hours" => interval_justify_hours,
858        "interval_justify_days" => interval_justify_days,
859        "interval_justify_interval" => interval_justify_interval,
860        "timestamp_pl_interval" => timestamp_pl_interval,
861        "timestamp_mi_interval" => timestamp_mi_interval,
862        "timestamptz_pl_interval" => timestamptz_pl_interval,
863        "timestamptz_mi_interval" => timestamptz_mi_interval,
864        "timestamptz_pl_interval_at_zone" => timestamptz_pl_interval_at_zone,
865        "timestamptz_mi_interval_at_zone" => timestamptz_mi_interval_at_zone,
866        "timestamp_mi" => timestamp_mi,
867        "timestamp_age" => timestamp_age,
868        "timestamptz_age" => timestamptz_age,
869        "time_pl_interval" => time_pl_interval,
870        "time_mi_interval" => time_mi_interval,
871        "timetz_pl_interval" => timetz_pl_interval,
872        "timetz_mi_interval" => timetz_mi_interval,
873        "time_mi_time" => time_mi_time,
874        "interval_time" => interval_time,
875        "time_interval" => time_interval,
876        "date_pl_interval" => date_pl_interval,
877        "date_mi_interval" => date_mi_interval,
878        "datetime_timestamp" => datetime_timestamp,
879        "datetimetz_timestamptz" => datetimetz_timestamptz,
880        "overlaps_timestamp" | "overlaps_time" | "overlaps_timetz" => overlaps,
881        _ => return None,
882    })
883}