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