Skip to main content

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_expr_derive::sqlfunc;
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::{SqlColumnType, SqlScalarType, strconv};
20use serde::{Deserialize, Serialize};
21
22use crate::EvalError;
23use crate::scalar::func::EagerUnaryFunc;
24
25#[sqlfunc(
26    sqlname = "time_to_text",
27    preserves_uniqueness = true,
28    inverse = to_unary!(super::CastStringToTime)
29)]
30fn 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#[sqlfunc(
37    sqlname = "time_to_interval",
38    preserves_uniqueness = true,
39    inverse = to_unary!(super::CastIntervalToTime)
40)]
41fn cast_time_to_interval(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
54pub fn date_part_time_inner<D>(units: DateTimeUnits, time: NaiveTime) -> Result<D, EvalError>
55where
56    D: DecimalLike,
57{
58    match units {
59        DateTimeUnits::Epoch => Ok(time.extract_epoch()),
60        DateTimeUnits::Hour => Ok(D::from(time.hour())),
61        DateTimeUnits::Minute => Ok(D::from(time.minute())),
62        DateTimeUnits::Second => Ok(time.extract_second()),
63        DateTimeUnits::Milliseconds => Ok(time.extract_millisecond()),
64        DateTimeUnits::Microseconds => Ok(time.extract_microsecond()),
65        DateTimeUnits::Millennium
66        | DateTimeUnits::Century
67        | DateTimeUnits::Decade
68        | DateTimeUnits::Year
69        | DateTimeUnits::Quarter
70        | DateTimeUnits::Month
71        | DateTimeUnits::Week
72        | DateTimeUnits::Day
73        | DateTimeUnits::DayOfYear
74        | DateTimeUnits::DayOfWeek
75        | DateTimeUnits::IsoDayOfYear
76        | DateTimeUnits::IsoDayOfWeek => Err(EvalError::UnsupportedUnits(
77            format!("{}", units).into(),
78            "time".into(),
79        )),
80        DateTimeUnits::Timezone | DateTimeUnits::TimezoneHour | DateTimeUnits::TimezoneMinute => {
81            Err(EvalError::Unsupported {
82                feature: format!("'{}' timestamp units", units).into(),
83                discussion_no: None,
84            })
85        }
86    }
87}
88
89#[derive(
90    Ord,
91    PartialOrd,
92    Clone,
93    Debug,
94    Eq,
95    PartialEq,
96    Serialize,
97    Deserialize,
98    Hash
99)]
100pub struct ExtractTime(pub DateTimeUnits);
101
102impl EagerUnaryFunc for ExtractTime {
103    type Input<'a> = NaiveTime;
104    type Output<'a> = Result<Numeric, EvalError>;
105
106    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
107        date_part_time_inner(self.0, a)
108    }
109
110    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
111        SqlScalarType::Numeric { max_scale: None }.nullable(input.nullable)
112    }
113}
114
115impl fmt::Display for ExtractTime {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        write!(f, "extract_{}_t", self.0)
118    }
119}
120
121#[derive(
122    Ord,
123    PartialOrd,
124    Clone,
125    Debug,
126    Eq,
127    PartialEq,
128    Serialize,
129    Deserialize,
130    Hash
131)]
132pub struct DatePartTime(pub DateTimeUnits);
133
134impl EagerUnaryFunc for DatePartTime {
135    type Input<'a> = NaiveTime;
136    type Output<'a> = Result<f64, EvalError>;
137
138    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
139        date_part_time_inner(self.0, a)
140    }
141
142    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
143        SqlScalarType::Float64.nullable(input.nullable)
144    }
145}
146
147impl fmt::Display for DatePartTime {
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        write!(f, "date_part_{}_t", self.0)
150    }
151}
152
153/// Converts the time `t`, which is assumed to be in UTC, to the timezone `tz`.
154/// For example, `EST` and `17:39:14` would return `12:39:14`.
155pub fn timezone_time(tz: Timezone, t: NaiveTime, wall_time: &NaiveDateTime) -> NaiveTime {
156    let offset = match tz {
157        Timezone::FixedOffset(offset) => offset,
158        Timezone::Tz(tz) => tz.offset_from_utc_datetime(wall_time).fix(),
159    };
160    t + offset
161}
162
163#[derive(
164    Ord,
165    PartialOrd,
166    Clone,
167    Debug,
168    Eq,
169    PartialEq,
170    Serialize,
171    Deserialize,
172    Hash
173)]
174pub struct TimezoneTime {
175    pub tz: Timezone,
176    pub wall_time: NaiveDateTime,
177}
178
179impl EagerUnaryFunc for TimezoneTime {
180    type Input<'a> = NaiveTime;
181    type Output<'a> = NaiveTime;
182
183    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
184        timezone_time(self.tz, a, &self.wall_time)
185    }
186
187    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
188        SqlScalarType::Time.nullable(input.nullable)
189    }
190}
191
192impl fmt::Display for TimezoneTime {
193    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
194        write!(f, "timezone_{}_t", self.tz)
195    }
196}