Skip to main content

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