mz_expr/scalar/func/impls/
timestamp.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::fmt;
11
12use chrono::{
13    DateTime, Duration, FixedOffset, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, Utc,
14};
15use mz_lowertest::MzReflect;
16use mz_ore::result::ResultExt;
17use mz_pgtz::timezone::Timezone;
18use mz_repr::adt::date::Date;
19use mz_repr::adt::datetime::DateTimeUnits;
20use mz_repr::adt::interval::Interval;
21use mz_repr::adt::numeric::{DecimalLike, Numeric};
22use mz_repr::adt::timestamp::{CheckedTimestamp, MAX_PRECISION, TimestampPrecision};
23use mz_repr::{SqlColumnType, SqlScalarType, strconv};
24use serde::{Deserialize, Serialize};
25
26use crate::EvalError;
27use crate::scalar::func::format::DateTimeFormat;
28use crate::scalar::func::{EagerUnaryFunc, TimestampLike};
29
30sqlfunc!(
31    #[sqlname = "timestamp_to_text"]
32    #[preserves_uniqueness = true]
33    #[inverse = to_unary!(super::CastStringToTimestamp(None))]
34    fn cast_timestamp_to_string(a: CheckedTimestamp<NaiveDateTime>) -> String {
35        let mut buf = String::new();
36        strconv::format_timestamp(&mut buf, &a);
37        buf
38    }
39);
40
41sqlfunc!(
42    #[sqlname = "timestamp_with_time_zone_to_text"]
43    #[preserves_uniqueness = true]
44    #[inverse = to_unary!(super::CastStringToTimestampTz(None))]
45    fn cast_timestamp_tz_to_string(a: CheckedTimestamp<DateTime<Utc>>) -> String {
46        let mut buf = String::new();
47        strconv::format_timestamptz(&mut buf, &a);
48        buf
49    }
50);
51
52sqlfunc!(
53    #[sqlname = "timestamp_to_date"]
54    #[preserves_uniqueness = false]
55    #[inverse = to_unary!(super::CastDateToTimestamp(None))]
56    #[is_monotone = true]
57    fn cast_timestamp_to_date(a: CheckedTimestamp<NaiveDateTime>) -> Result<Date, EvalError> {
58        Ok(a.date().try_into()?)
59    }
60);
61
62sqlfunc!(
63    #[sqlname = "timestamp_with_time_zone_to_date"]
64    #[preserves_uniqueness = false]
65    #[inverse = to_unary!(super::CastDateToTimestampTz(None))]
66    #[is_monotone = true]
67    fn cast_timestamp_tz_to_date(a: CheckedTimestamp<DateTime<Utc>>) -> Result<Date, EvalError> {
68        Ok(a.naive_utc().date().try_into()?)
69    }
70);
71
72#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
73pub struct CastTimestampToTimestampTz {
74    pub from: Option<TimestampPrecision>,
75    pub to: Option<TimestampPrecision>,
76}
77
78impl<'a> EagerUnaryFunc<'a> for CastTimestampToTimestampTz {
79    type Input = CheckedTimestamp<NaiveDateTime>;
80    type Output = Result<CheckedTimestamp<DateTime<Utc>>, EvalError>;
81
82    fn call(
83        &self,
84        a: CheckedTimestamp<NaiveDateTime>,
85    ) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
86        let out =
87            CheckedTimestamp::try_from(DateTime::<Utc>::from_naive_utc_and_offset(a.into(), Utc))?;
88        let updated = out.round_to_precision(self.to)?;
89        Ok(updated)
90    }
91
92    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
93        SqlScalarType::TimestampTz { precision: self.to }.nullable(input.nullable)
94    }
95
96    fn preserves_uniqueness(&self) -> bool {
97        let to_p = self.to.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
98        let from_p = self.from.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
99        // If it's getting cast to a higher precision, it should preserve uniqueness but not otherwise.
100        to_p >= from_p
101    }
102
103    fn inverse(&self) -> Option<crate::UnaryFunc> {
104        to_unary!(super::CastTimestampTzToTimestamp {
105            from: self.from,
106            to: self.to
107        })
108    }
109
110    fn is_monotone(&self) -> bool {
111        true
112    }
113}
114
115impl fmt::Display for CastTimestampToTimestampTz {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        f.write_str("timestamp_to_timestamp_with_time_zone")
118    }
119}
120
121#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
122pub struct AdjustTimestampPrecision {
123    pub from: Option<TimestampPrecision>,
124    pub to: Option<TimestampPrecision>,
125}
126
127impl<'a> EagerUnaryFunc<'a> for AdjustTimestampPrecision {
128    type Input = CheckedTimestamp<NaiveDateTime>;
129    type Output = Result<CheckedTimestamp<NaiveDateTime>, EvalError>;
130
131    fn call(
132        &self,
133        a: CheckedTimestamp<NaiveDateTime>,
134    ) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
135        // This should never have been called if precisions are same.
136        // Adding a soft-assert to flag if there are such instances.
137        mz_ore::soft_assert_no_log!(self.to != self.from);
138
139        let updated = a.round_to_precision(self.to)?;
140        Ok(updated)
141    }
142
143    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
144        SqlScalarType::Timestamp { precision: self.to }.nullable(input.nullable)
145    }
146
147    fn preserves_uniqueness(&self) -> bool {
148        let to_p = self.to.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
149        let from_p = self.from.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
150        // If it's getting cast to a higher precision, it should preserve uniqueness but not otherwise.
151        to_p >= from_p
152    }
153
154    fn inverse(&self) -> Option<crate::UnaryFunc> {
155        None
156    }
157
158    fn is_monotone(&self) -> bool {
159        true
160    }
161}
162
163impl fmt::Display for AdjustTimestampPrecision {
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        f.write_str("adjust_timestamp_precision")
166    }
167}
168
169#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
170pub struct CastTimestampTzToTimestamp {
171    pub from: Option<TimestampPrecision>,
172    pub to: Option<TimestampPrecision>,
173}
174
175impl<'a> EagerUnaryFunc<'a> for CastTimestampTzToTimestamp {
176    type Input = CheckedTimestamp<DateTime<Utc>>;
177    type Output = Result<CheckedTimestamp<NaiveDateTime>, EvalError>;
178
179    fn call(
180        &self,
181        a: CheckedTimestamp<DateTime<Utc>>,
182    ) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
183        let out = CheckedTimestamp::try_from(a.naive_utc())?;
184        let updated = out.round_to_precision(self.to)?;
185        Ok(updated)
186    }
187
188    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
189        SqlScalarType::Timestamp { precision: self.to }.nullable(input.nullable)
190    }
191
192    fn preserves_uniqueness(&self) -> bool {
193        let to_p = self.to.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
194        let from_p = self.from.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
195        // If it's getting cast to a higher precision, it should preserve uniqueness but not otherwise.
196        to_p >= from_p
197    }
198
199    fn inverse(&self) -> Option<crate::UnaryFunc> {
200        to_unary!(super::CastTimestampToTimestampTz {
201            from: self.from,
202            to: self.to
203        })
204    }
205
206    fn is_monotone(&self) -> bool {
207        true
208    }
209}
210
211impl fmt::Display for CastTimestampTzToTimestamp {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        f.write_str("timestamp_with_time_zone_to_timestamp")
214    }
215}
216
217#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
218pub struct AdjustTimestampTzPrecision {
219    pub from: Option<TimestampPrecision>,
220    pub to: Option<TimestampPrecision>,
221}
222
223impl<'a> EagerUnaryFunc<'a> for AdjustTimestampTzPrecision {
224    type Input = CheckedTimestamp<DateTime<Utc>>;
225    type Output = Result<CheckedTimestamp<DateTime<Utc>>, EvalError>;
226
227    fn call(
228        &self,
229        a: CheckedTimestamp<DateTime<Utc>>,
230    ) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
231        // This should never have been called if precisions are same.
232        // Adding a soft-assert to flag if there are such instances.
233        mz_ore::soft_assert_no_log!(self.to != self.from);
234
235        let updated = a.round_to_precision(self.to)?;
236        Ok(updated)
237    }
238
239    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
240        SqlScalarType::TimestampTz { precision: self.to }.nullable(input.nullable)
241    }
242
243    fn preserves_uniqueness(&self) -> bool {
244        let to_p = self.to.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
245        let from_p = self.from.map(|p| p.into_u8()).unwrap_or(MAX_PRECISION);
246        // If it's getting cast to a higher precision, it should preserve uniqueness but not otherwise.
247        to_p >= from_p
248    }
249
250    fn inverse(&self) -> Option<crate::UnaryFunc> {
251        None
252    }
253
254    fn is_monotone(&self) -> bool {
255        true
256    }
257}
258
259impl fmt::Display for AdjustTimestampTzPrecision {
260    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
261        f.write_str("adjust_timestamp_with_time_zone_precision")
262    }
263}
264
265sqlfunc!(
266    #[sqlname = "timestamp_to_time"]
267    #[preserves_uniqueness = false]
268    fn cast_timestamp_to_time(a: CheckedTimestamp<NaiveDateTime>) -> NaiveTime {
269        a.time()
270    }
271);
272
273sqlfunc!(
274    #[sqlname = "timestamp_with_time_zone_to_time"]
275    #[preserves_uniqueness = false]
276    fn cast_timestamp_tz_to_time(a: CheckedTimestamp<DateTime<Utc>>) -> NaiveTime {
277        a.naive_utc().time()
278    }
279);
280
281pub fn date_part_interval_inner<D>(units: DateTimeUnits, interval: Interval) -> Result<D, EvalError>
282where
283    D: DecimalLike,
284{
285    match units {
286        DateTimeUnits::Epoch => Ok(interval.as_epoch_seconds()),
287        DateTimeUnits::Millennium => Ok(D::from(interval.millennia())),
288        DateTimeUnits::Century => Ok(D::from(interval.centuries())),
289        DateTimeUnits::Decade => Ok(D::from(interval.decades())),
290        DateTimeUnits::Year => Ok(D::from(interval.years())),
291        DateTimeUnits::Quarter => Ok(D::from(interval.quarters())),
292        DateTimeUnits::Month => Ok(D::from(interval.months())),
293        DateTimeUnits::Day => Ok(D::lossy_from(interval.days())),
294        DateTimeUnits::Hour => Ok(D::lossy_from(interval.hours())),
295        DateTimeUnits::Minute => Ok(D::lossy_from(interval.minutes())),
296        DateTimeUnits::Second => Ok(interval.seconds()),
297        DateTimeUnits::Milliseconds => Ok(interval.milliseconds()),
298        DateTimeUnits::Microseconds => Ok(interval.microseconds()),
299        DateTimeUnits::Week
300        | DateTimeUnits::Timezone
301        | DateTimeUnits::TimezoneHour
302        | DateTimeUnits::TimezoneMinute
303        | DateTimeUnits::DayOfWeek
304        | DateTimeUnits::DayOfYear
305        | DateTimeUnits::IsoDayOfWeek
306        | DateTimeUnits::IsoDayOfYear => Err(EvalError::Unsupported {
307            feature: format!("'{}' timestamp units", units).into(),
308            discussion_no: None,
309        }),
310    }
311}
312
313#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
314pub struct ExtractInterval(pub DateTimeUnits);
315
316impl<'a> EagerUnaryFunc<'a> for ExtractInterval {
317    type Input = Interval;
318    type Output = Result<Numeric, EvalError>;
319
320    fn call(&self, a: Interval) -> Result<Numeric, EvalError> {
321        date_part_interval_inner(self.0, a)
322    }
323
324    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
325        SqlScalarType::Numeric { max_scale: None }.nullable(input.nullable)
326    }
327}
328
329impl fmt::Display for ExtractInterval {
330    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331        write!(f, "extract_{}_iv", self.0)
332    }
333}
334
335#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
336pub struct DatePartInterval(pub DateTimeUnits);
337
338impl<'a> EagerUnaryFunc<'a> for DatePartInterval {
339    type Input = Interval;
340    type Output = Result<f64, EvalError>;
341
342    fn call(&self, a: Interval) -> Result<f64, EvalError> {
343        date_part_interval_inner(self.0, a)
344    }
345
346    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
347        SqlScalarType::Float64.nullable(input.nullable)
348    }
349}
350
351impl fmt::Display for DatePartInterval {
352    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
353        write!(f, "date_part_{}_iv", self.0)
354    }
355}
356
357pub fn date_part_timestamp_inner<T, D>(units: DateTimeUnits, ts: &T) -> Result<D, EvalError>
358where
359    T: TimestampLike,
360    D: DecimalLike,
361{
362    match units {
363        DateTimeUnits::Epoch => Ok(TimestampLike::extract_epoch(ts)),
364        DateTimeUnits::Millennium => Ok(D::from(ts.millennium())),
365        DateTimeUnits::Century => Ok(D::from(ts.century())),
366        DateTimeUnits::Decade => Ok(D::from(ts.decade())),
367        DateTimeUnits::Year => Ok(D::from(ts.year())),
368        DateTimeUnits::Quarter => Ok(D::from(ts.quarter())),
369        DateTimeUnits::Week => Ok(D::from(ts.iso_week_number())),
370        DateTimeUnits::Month => Ok(D::from(ts.month())),
371        DateTimeUnits::Day => Ok(D::from(ts.day())),
372        DateTimeUnits::DayOfWeek => Ok(D::from(ts.day_of_week())),
373        DateTimeUnits::DayOfYear => Ok(D::from(ts.ordinal())),
374        DateTimeUnits::IsoDayOfWeek => Ok(D::from(ts.iso_day_of_week())),
375        DateTimeUnits::Hour => Ok(D::from(ts.hour())),
376        DateTimeUnits::Minute => Ok(D::from(ts.minute())),
377        DateTimeUnits::Second => Ok(ts.extract_second()),
378        DateTimeUnits::Milliseconds => Ok(ts.extract_millisecond()),
379        DateTimeUnits::Microseconds => Ok(ts.extract_microsecond()),
380        DateTimeUnits::Timezone
381        | DateTimeUnits::TimezoneHour
382        | DateTimeUnits::TimezoneMinute
383        | DateTimeUnits::IsoDayOfYear => Err(EvalError::Unsupported {
384            feature: format!("'{}' timestamp units", units).into(),
385            discussion_no: None,
386        }),
387    }
388}
389
390/// Will extracting this unit from the timestamp include the "most significant bits" of
391/// the timestamp?
392pub(crate) fn most_significant_unit(unit: DateTimeUnits) -> bool {
393    match unit {
394        DateTimeUnits::Epoch
395        | DateTimeUnits::Millennium
396        | DateTimeUnits::Century
397        | DateTimeUnits::Decade
398        | DateTimeUnits::Year => true,
399        _ => false,
400    }
401}
402
403#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
404pub struct ExtractTimestamp(pub DateTimeUnits);
405
406impl<'a> EagerUnaryFunc<'a> for ExtractTimestamp {
407    type Input = CheckedTimestamp<NaiveDateTime>;
408    type Output = Result<Numeric, EvalError>;
409
410    fn call(&self, a: CheckedTimestamp<NaiveDateTime>) -> Result<Numeric, EvalError> {
411        date_part_timestamp_inner(self.0, &*a)
412    }
413
414    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
415        SqlScalarType::Numeric { max_scale: None }.nullable(input.nullable)
416    }
417
418    fn is_monotone(&self) -> bool {
419        most_significant_unit(self.0)
420    }
421}
422
423impl fmt::Display for ExtractTimestamp {
424    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
425        write!(f, "extract_{}_ts", self.0)
426    }
427}
428
429#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
430pub struct ExtractTimestampTz(pub DateTimeUnits);
431
432impl<'a> EagerUnaryFunc<'a> for ExtractTimestampTz {
433    type Input = CheckedTimestamp<DateTime<Utc>>;
434    type Output = Result<Numeric, EvalError>;
435
436    fn call(&self, a: CheckedTimestamp<DateTime<Utc>>) -> Result<Numeric, EvalError> {
437        date_part_timestamp_inner(self.0, &*a)
438    }
439
440    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
441        SqlScalarType::Numeric { max_scale: None }.nullable(input.nullable)
442    }
443
444    fn is_monotone(&self) -> bool {
445        // Unlike the timezone-less timestamp, it's not safe to extract the "high-order bits" like
446        // year: year takes timezone into account, and it's quite possible for a different timezone
447        // to be in a previous year while having a later UTC-equivalent time.
448        self.0 == DateTimeUnits::Epoch
449    }
450}
451
452impl fmt::Display for ExtractTimestampTz {
453    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
454        write!(f, "extract_{}_tstz", self.0)
455    }
456}
457
458#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
459pub struct DatePartTimestamp(pub DateTimeUnits);
460
461impl<'a> EagerUnaryFunc<'a> for DatePartTimestamp {
462    type Input = CheckedTimestamp<NaiveDateTime>;
463    type Output = Result<f64, EvalError>;
464
465    fn call(&self, a: CheckedTimestamp<NaiveDateTime>) -> Result<f64, EvalError> {
466        date_part_timestamp_inner(self.0, &*a)
467    }
468
469    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
470        SqlScalarType::Float64.nullable(input.nullable)
471    }
472}
473
474impl fmt::Display for DatePartTimestamp {
475    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
476        write!(f, "date_part_{}_ts", self.0)
477    }
478}
479
480#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
481pub struct DatePartTimestampTz(pub DateTimeUnits);
482
483impl<'a> EagerUnaryFunc<'a> for DatePartTimestampTz {
484    type Input = CheckedTimestamp<DateTime<Utc>>;
485    type Output = Result<f64, EvalError>;
486
487    fn call(&self, a: CheckedTimestamp<DateTime<Utc>>) -> Result<f64, EvalError> {
488        date_part_timestamp_inner(self.0, &*a)
489    }
490
491    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
492        SqlScalarType::Float64.nullable(input.nullable)
493    }
494}
495
496impl fmt::Display for DatePartTimestampTz {
497    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
498        write!(f, "date_part_{}_tstz", self.0)
499    }
500}
501
502pub fn date_trunc_inner<T: TimestampLike>(units: DateTimeUnits, ts: &T) -> Result<T, EvalError> {
503    match units {
504        DateTimeUnits::Millennium => Ok(ts.truncate_millennium()),
505        DateTimeUnits::Century => Ok(ts.truncate_century()),
506        DateTimeUnits::Decade => Ok(ts.truncate_decade()),
507        DateTimeUnits::Year => Ok(ts.truncate_year()),
508        DateTimeUnits::Quarter => Ok(ts.truncate_quarter()),
509        DateTimeUnits::Week => Ok(ts.truncate_week()?),
510        DateTimeUnits::Day => Ok(ts.truncate_day()),
511        DateTimeUnits::Hour => Ok(ts.truncate_hour()),
512        DateTimeUnits::Minute => Ok(ts.truncate_minute()),
513        DateTimeUnits::Second => Ok(ts.truncate_second()),
514        DateTimeUnits::Month => Ok(ts.truncate_month()),
515        DateTimeUnits::Milliseconds => Ok(ts.truncate_milliseconds()),
516        DateTimeUnits::Microseconds => Ok(ts.truncate_microseconds()),
517        DateTimeUnits::Epoch
518        | DateTimeUnits::Timezone
519        | DateTimeUnits::TimezoneHour
520        | DateTimeUnits::TimezoneMinute
521        | DateTimeUnits::DayOfWeek
522        | DateTimeUnits::DayOfYear
523        | DateTimeUnits::IsoDayOfWeek
524        | DateTimeUnits::IsoDayOfYear => Err(EvalError::Unsupported {
525            feature: format!("'{}' timestamp units", units).into(),
526            discussion_no: None,
527        }),
528    }
529}
530
531#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
532pub struct DateTruncTimestamp(pub DateTimeUnits);
533
534impl<'a> EagerUnaryFunc<'a> for DateTruncTimestamp {
535    type Input = CheckedTimestamp<NaiveDateTime>;
536    type Output = Result<CheckedTimestamp<NaiveDateTime>, EvalError>;
537
538    fn call(
539        &self,
540        a: CheckedTimestamp<NaiveDateTime>,
541    ) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
542        date_trunc_inner(self.0, &*a)?.try_into().err_into()
543    }
544
545    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
546        SqlScalarType::Timestamp { precision: None }.nullable(input.nullable)
547    }
548
549    fn is_monotone(&self) -> bool {
550        true
551    }
552}
553
554impl fmt::Display for DateTruncTimestamp {
555    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
556        write!(f, "date_trunc_{}_ts", self.0)
557    }
558}
559
560#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
561pub struct DateTruncTimestampTz(pub DateTimeUnits);
562
563impl<'a> EagerUnaryFunc<'a> for DateTruncTimestampTz {
564    type Input = CheckedTimestamp<DateTime<Utc>>;
565    type Output = Result<CheckedTimestamp<DateTime<Utc>>, EvalError>;
566
567    fn call(
568        &self,
569        a: CheckedTimestamp<DateTime<Utc>>,
570    ) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
571        date_trunc_inner(self.0, &*a)?.try_into().err_into()
572    }
573
574    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
575        SqlScalarType::TimestampTz { precision: None }.nullable(input.nullable)
576    }
577
578    fn is_monotone(&self) -> bool {
579        true
580    }
581}
582
583impl fmt::Display for DateTruncTimestampTz {
584    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
585        write!(f, "date_trunc_{}_tstz", self.0)
586    }
587}
588
589/// Converts the timestamp `dt`, which is assumed to be in the time of the timezone `tz` to a timestamptz in UTC.
590/// This operation is fallible because certain timestamps at timezones that observe DST are simply impossible or
591/// ambiguous. In case of ambiguity (when a hour repeats) we will prefer the latest variant, and when an hour is
592/// impossible, we will attempt to fix it by advancing it. For example, `EST` and `2020-11-11T12:39:14` would return
593/// `2020-11-11T17:39:14Z`. A DST observing timezone like `America/New_York` would cause the following DST anomalies:
594/// `2020-11-01T00:59:59` -> `2020-11-01T04:59:59Z` and `2020-11-01T01:00:00` -> `2020-11-01T06:00:00Z`
595/// `2020-03-08T02:59:59` -> `2020-03-08T07:59:59Z` and `2020-03-08T03:00:00` -> `2020-03-08T07:00:00Z`
596pub fn timezone_timestamp(
597    tz: Timezone,
598    dt: NaiveDateTime,
599) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
600    let offset = match tz {
601        Timezone::FixedOffset(offset) => offset,
602        Timezone::Tz(tz) => match tz.offset_from_local_datetime(&dt).latest() {
603            Some(offset) => offset.fix(),
604            None => {
605                let dt = dt
606                    .checked_add_signed(
607                        Duration::try_hours(1).ok_or(EvalError::TimestampOutOfRange)?,
608                    )
609                    .ok_or(EvalError::TimestampOutOfRange)?;
610                tz.offset_from_local_datetime(&dt)
611                    .latest()
612                    .ok_or(EvalError::InvalidTimezoneConversion)?
613                    .fix()
614            }
615        },
616    };
617    DateTime::from_naive_utc_and_offset(dt - offset, Utc)
618        .try_into()
619        .err_into()
620}
621
622/// Converts the UTC timestamptz `utc` to the local timestamp of the timezone `tz`.
623/// For example, `EST` and `2020-11-11T17:39:14Z` would return `2020-11-11T12:39:14`.
624pub fn timezone_timestamptz(tz: Timezone, utc: DateTime<Utc>) -> Result<NaiveDateTime, EvalError> {
625    let offset = match tz {
626        Timezone::FixedOffset(offset) => offset,
627        Timezone::Tz(tz) => tz.offset_from_utc_datetime(&utc.naive_utc()).fix(),
628    };
629    checked_add_with_leapsecond(&utc.naive_utc(), &offset).ok_or(EvalError::TimestampOutOfRange)
630}
631
632/// Checked addition that is missing from chrono. Adapt its methods here but add a check.
633fn checked_add_with_leapsecond(lhs: &NaiveDateTime, rhs: &FixedOffset) -> Option<NaiveDateTime> {
634    // extract and temporarily remove the fractional part and later recover it
635    let nanos = lhs.nanosecond();
636    let lhs = lhs.with_nanosecond(0).unwrap();
637    let rhs = rhs.local_minus_utc();
638    lhs.checked_add_signed(match chrono::Duration::try_seconds(i64::from(rhs)) {
639        Some(dur) => dur,
640        None => return None,
641    })
642    .map(|dt| dt.with_nanosecond(nanos).unwrap())
643}
644
645#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
646pub struct TimezoneTimestamp(pub Timezone);
647
648impl<'a> EagerUnaryFunc<'a> for TimezoneTimestamp {
649    type Input = CheckedTimestamp<NaiveDateTime>;
650    type Output = Result<CheckedTimestamp<DateTime<Utc>>, EvalError>;
651
652    fn call(
653        &self,
654        a: CheckedTimestamp<NaiveDateTime>,
655    ) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
656        timezone_timestamp(self.0, a.to_naive())
657    }
658
659    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
660        SqlScalarType::TimestampTz { precision: None }.nullable(input.nullable)
661    }
662}
663
664impl fmt::Display for TimezoneTimestamp {
665    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
666        write!(f, "timezone_{}_ts", self.0)
667    }
668}
669
670#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
671pub struct TimezoneTimestampTz(pub Timezone);
672
673impl<'a> EagerUnaryFunc<'a> for TimezoneTimestampTz {
674    type Input = CheckedTimestamp<DateTime<Utc>>;
675    type Output = Result<CheckedTimestamp<NaiveDateTime>, EvalError>;
676
677    fn call(
678        &self,
679        a: CheckedTimestamp<DateTime<Utc>>,
680    ) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
681        timezone_timestamptz(self.0, a.into())?
682            .try_into()
683            .err_into()
684    }
685
686    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
687        SqlScalarType::Timestamp { precision: None }.nullable(input.nullable)
688    }
689}
690
691impl fmt::Display for TimezoneTimestampTz {
692    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
693        write!(f, "timezone_{}_tstz", self.0)
694    }
695}
696
697#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, MzReflect)]
698pub struct ToCharTimestamp {
699    pub format_string: String,
700    pub format: DateTimeFormat,
701}
702
703impl<'a> EagerUnaryFunc<'a> for ToCharTimestamp {
704    type Input = CheckedTimestamp<NaiveDateTime>;
705    type Output = String;
706
707    fn call(&self, input: CheckedTimestamp<NaiveDateTime>) -> String {
708        self.format.render(&*input)
709    }
710
711    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
712        SqlScalarType::String.nullable(input.nullable)
713    }
714}
715
716impl fmt::Display for ToCharTimestamp {
717    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
718        write!(f, "tocharts[{}]", self.format_string)
719    }
720}
721
722#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, MzReflect)]
723pub struct ToCharTimestampTz {
724    pub format_string: String,
725    pub format: DateTimeFormat,
726}
727
728impl<'a> EagerUnaryFunc<'a> for ToCharTimestampTz {
729    type Input = CheckedTimestamp<DateTime<Utc>>;
730    type Output = String;
731
732    fn call(&self, input: CheckedTimestamp<DateTime<Utc>>) -> String {
733        self.format.render(&*input)
734    }
735
736    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
737        SqlScalarType::String.nullable(input.nullable)
738    }
739}
740
741impl fmt::Display for ToCharTimestampTz {
742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743        write!(f, "tochartstz[{}]", self.format_string)
744    }
745}