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