1use 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 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 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 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 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 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 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
437pub(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 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
690pub 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
724pub 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}