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