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