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