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