mz_expr/scalar/func/impls/
time.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::{NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike};
13use mz_lowertest::MzReflect;
14use mz_pgtz::timezone::Timezone;
15use mz_repr::adt::datetime::{DateTimeField, DateTimeUnits};
16use mz_repr::adt::interval::Interval;
17use mz_repr::adt::numeric::{DecimalLike, Numeric};
18use mz_repr::adt::timestamp::TimeLike;
19use mz_repr::{ColumnType, ScalarType, strconv};
20use proptest_derive::Arbitrary;
21use serde::{Deserialize, Serialize};
22
23use crate::EvalError;
24use crate::scalar::func::EagerUnaryFunc;
25
26sqlfunc!(
27    #[sqlname = "time_to_text"]
28    #[preserves_uniqueness = true]
29    #[inverse = to_unary!(super::CastStringToTime)]
30    fn cast_time_to_string(a: NaiveTime) -> String {
31        let mut buf = String::new();
32        strconv::format_time(&mut buf, a);
33        buf
34    }
35);
36
37sqlfunc!(
38    #[sqlname = "time_to_interval"]
39    #[preserves_uniqueness = true]
40    #[inverse = to_unary!(super::CastIntervalToTime)]
41    fn cast_time_to_interval<'a>(t: NaiveTime) -> Interval {
42        // wont overflow because value can't exceed 24 hrs + 1_000_000 ns = 86_400 seconds + 1_000_000 ns = 86_400_001_000 us
43        let micros: i64 = Interval::convert_date_time_unit(
44            DateTimeField::Second,
45            DateTimeField::Microseconds,
46            i64::from(t.num_seconds_from_midnight()),
47        )
48        .unwrap()
49            + i64::from(t.nanosecond()) / i64::from(Interval::NANOSECOND_PER_MICROSECOND);
50
51        Interval::new(0, 0, micros)
52    }
53);
54
55pub fn date_part_time_inner<D>(units: DateTimeUnits, time: NaiveTime) -> Result<D, EvalError>
56where
57    D: DecimalLike,
58{
59    match units {
60        DateTimeUnits::Epoch => Ok(time.extract_epoch()),
61        DateTimeUnits::Hour => Ok(D::from(time.hour())),
62        DateTimeUnits::Minute => Ok(D::from(time.minute())),
63        DateTimeUnits::Second => Ok(time.extract_second()),
64        DateTimeUnits::Milliseconds => Ok(time.extract_millisecond()),
65        DateTimeUnits::Microseconds => Ok(time.extract_microsecond()),
66        DateTimeUnits::Millennium
67        | DateTimeUnits::Century
68        | DateTimeUnits::Decade
69        | DateTimeUnits::Year
70        | DateTimeUnits::Quarter
71        | DateTimeUnits::Month
72        | DateTimeUnits::Week
73        | DateTimeUnits::Day
74        | DateTimeUnits::DayOfYear
75        | DateTimeUnits::DayOfWeek
76        | DateTimeUnits::IsoDayOfYear
77        | DateTimeUnits::IsoDayOfWeek => Err(EvalError::UnsupportedUnits(
78            format!("{}", units).into(),
79            "time".into(),
80        )),
81        DateTimeUnits::Timezone | DateTimeUnits::TimezoneHour | DateTimeUnits::TimezoneMinute => {
82            Err(EvalError::Unsupported {
83                feature: format!("'{}' timestamp units", units).into(),
84                discussion_no: None,
85            })
86        }
87    }
88}
89
90#[derive(
91    Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
92)]
93pub struct ExtractTime(pub DateTimeUnits);
94
95impl<'a> EagerUnaryFunc<'a> for ExtractTime {
96    type Input = NaiveTime;
97    type Output = Result<Numeric, EvalError>;
98
99    fn call(&self, a: NaiveTime) -> Result<Numeric, EvalError> {
100        date_part_time_inner(self.0, a)
101    }
102
103    fn output_type(&self, input: ColumnType) -> ColumnType {
104        ScalarType::Numeric { max_scale: None }.nullable(input.nullable)
105    }
106}
107
108impl fmt::Display for ExtractTime {
109    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110        write!(f, "extract_{}_t", self.0)
111    }
112}
113
114#[derive(
115    Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
116)]
117pub struct DatePartTime(pub DateTimeUnits);
118
119impl<'a> EagerUnaryFunc<'a> for DatePartTime {
120    type Input = NaiveTime;
121    type Output = Result<f64, EvalError>;
122
123    fn call(&self, a: NaiveTime) -> Result<f64, EvalError> {
124        date_part_time_inner(self.0, a)
125    }
126
127    fn output_type(&self, input: ColumnType) -> ColumnType {
128        ScalarType::Float64.nullable(input.nullable)
129    }
130}
131
132impl fmt::Display for DatePartTime {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        write!(f, "date_part_{}_t", self.0)
135    }
136}
137
138/// Converts the time `t`, which is assumed to be in UTC, to the timezone `tz`.
139/// For example, `EST` and `17:39:14` would return `12:39:14`.
140pub fn timezone_time(tz: Timezone, t: NaiveTime, wall_time: &NaiveDateTime) -> NaiveTime {
141    let offset = match tz {
142        Timezone::FixedOffset(offset) => offset,
143        Timezone::Tz(tz) => tz.offset_from_utc_datetime(wall_time).fix(),
144    };
145    t + offset
146}
147
148#[derive(
149    Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
150)]
151pub struct TimezoneTime {
152    pub tz: Timezone,
153    #[proptest(strategy = "crate::func::any_naive_datetime()")]
154    pub wall_time: NaiveDateTime,
155}
156
157impl<'a> EagerUnaryFunc<'a> for TimezoneTime {
158    type Input = NaiveTime;
159    type Output = NaiveTime;
160
161    fn call(&self, a: NaiveTime) -> NaiveTime {
162        timezone_time(self.tz, a, &self.wall_time)
163    }
164
165    fn output_type(&self, input: ColumnType) -> ColumnType {
166        ScalarType::Time.nullable(input.nullable)
167    }
168}
169
170impl fmt::Display for TimezoneTime {
171    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172        write!(f, "timezone_{}_t", self.tz)
173    }
174}