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