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_lowertest::MzReflect;
15use mz_pgtz::timezone::Timezone;
16use mz_repr::adt::datetime::{DateTimeField, DateTimeUnits};
17use mz_repr::adt::interval::Interval;
18use mz_repr::adt::numeric::{DecimalLike, Numeric};
19use mz_repr::adt::timestamp::TimeLike;
20use mz_repr::{SqlColumnType, SqlScalarType, strconv};
21use serde::{Deserialize, Serialize};
22
23use crate::EvalError;
24use crate::scalar::func::EagerUnaryFunc;
25
26#[sqlfunc(
27    sqlname = "time_to_text",
28    preserves_uniqueness = true,
29    inverse = to_unary!(super::CastStringToTime)
30)]
31fn cast_time_to_string(a: NaiveTime) -> String {
32    let mut buf = String::new();
33    strconv::format_time(&mut buf, a);
34    buf
35}
36
37#[sqlfunc(
38    sqlname = "time_to_interval",
39    preserves_uniqueness = true,
40    inverse = to_unary!(super::CastIntervalToTime)
41)]
42fn cast_time_to_interval(t: NaiveTime) -> Interval {
43    // 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
44    let micros: i64 = Interval::convert_date_time_unit(
45        DateTimeField::Second,
46        DateTimeField::Microseconds,
47        i64::from(t.num_seconds_from_midnight()),
48    )
49    .unwrap()
50        + i64::from(t.nanosecond()) / i64::from(Interval::NANOSECOND_PER_MICROSECOND);
51
52    Interval::new(0, 0, micros)
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    Ord,
92    PartialOrd,
93    Clone,
94    Debug,
95    Eq,
96    PartialEq,
97    Serialize,
98    Deserialize,
99    Hash,
100    MzReflect
101)]
102pub struct ExtractTime(pub DateTimeUnits);
103
104impl EagerUnaryFunc for ExtractTime {
105    type Input<'a> = NaiveTime;
106    type Output<'a> = Result<Numeric, EvalError>;
107
108    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
109        date_part_time_inner(self.0, a)
110    }
111
112    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
113        SqlScalarType::Numeric { max_scale: None }.nullable(input.nullable)
114    }
115}
116
117impl fmt::Display for ExtractTime {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        write!(f, "extract_{}_t", self.0)
120    }
121}
122
123#[derive(
124    Ord,
125    PartialOrd,
126    Clone,
127    Debug,
128    Eq,
129    PartialEq,
130    Serialize,
131    Deserialize,
132    Hash,
133    MzReflect
134)]
135pub struct DatePartTime(pub DateTimeUnits);
136
137impl EagerUnaryFunc for DatePartTime {
138    type Input<'a> = NaiveTime;
139    type Output<'a> = Result<f64, EvalError>;
140
141    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
142        date_part_time_inner(self.0, a)
143    }
144
145    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
146        SqlScalarType::Float64.nullable(input.nullable)
147    }
148}
149
150impl fmt::Display for DatePartTime {
151    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152        write!(f, "date_part_{}_t", self.0)
153    }
154}
155
156/// Converts the time `t`, which is assumed to be in UTC, to the timezone `tz`.
157/// For example, `EST` and `17:39:14` would return `12:39:14`.
158pub fn timezone_time(tz: Timezone, t: NaiveTime, wall_time: &NaiveDateTime) -> NaiveTime {
159    let offset = match tz {
160        Timezone::FixedOffset(offset) => offset,
161        Timezone::Tz(tz) => tz.offset_from_utc_datetime(wall_time).fix(),
162    };
163    t + offset
164}
165
166#[derive(
167    Ord,
168    PartialOrd,
169    Clone,
170    Debug,
171    Eq,
172    PartialEq,
173    Serialize,
174    Deserialize,
175    Hash,
176    MzReflect
177)]
178pub struct TimezoneTime {
179    pub tz: Timezone,
180    pub wall_time: NaiveDateTime,
181}
182
183impl EagerUnaryFunc for TimezoneTime {
184    type Input<'a> = NaiveTime;
185    type Output<'a> = NaiveTime;
186
187    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
188        timezone_time(self.tz, a, &self.wall_time)
189    }
190
191    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
192        SqlScalarType::Time.nullable(input.nullable)
193    }
194}
195
196impl fmt::Display for TimezoneTime {
197    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198        write!(f, "timezone_{}_t", self.tz)
199    }
200}