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