Skip to main content

mz_expr/scalar/func/impls/
date.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::{DateTime, Datelike, NaiveDate, NaiveDateTime, Utc};
13use mz_expr_derive::sqlfunc;
14use mz_repr::adt::date::Date;
15use mz_repr::adt::datetime::DateTimeUnits;
16use mz_repr::adt::numeric::Numeric;
17use mz_repr::adt::timestamp::{CheckedTimestamp, DateLike, TimestampPrecision};
18use mz_repr::{SqlScalarType, strconv};
19use serde::{Deserialize, Serialize};
20
21use crate::EvalError;
22use crate::func::most_significant_unit;
23
24#[sqlfunc(
25    sqlname = "date_to_text",
26    preserves_uniqueness = true,
27    inverse = super::CastStringToDate
28)]
29fn cast_date_to_string(a: Date) -> String {
30    let mut buf = String::new();
31    strconv::format_date(&mut buf, a);
32    buf
33}
34
35#[derive(
36    Ord,
37    PartialOrd,
38    Clone,
39    Debug,
40    Eq,
41    PartialEq,
42    Serialize,
43    Deserialize,
44    Hash
45)]
46pub struct CastDateToTimestamp(pub Option<TimestampPrecision>);
47
48#[sqlfunc(
49    CastDateToTimestamp,
50    sqlname = "date_to_timestamp",
51    output_type_expr = SqlScalarType::Timestamp { precision: self.0 }.nullable(input_type.nullable),
52    preserves_uniqueness = true,
53    inverse = super::CastTimestampToDate,
54    is_monotone = true
55)]
56fn cast_date_to_timestamp(&self, a: Date) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
57    let out =
58        CheckedTimestamp::from_timestamplike(NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap())?;
59    let updated = out.round_to_precision(self.0)?;
60    Ok(updated)
61}
62
63#[derive(
64    Ord,
65    PartialOrd,
66    Clone,
67    Debug,
68    Eq,
69    PartialEq,
70    Serialize,
71    Deserialize,
72    Hash
73)]
74pub struct CastDateToTimestampTz(pub Option<TimestampPrecision>);
75
76#[sqlfunc(
77    CastDateToTimestampTz,
78    sqlname = "date_to_timestamp_with_timezone",
79    output_type_expr = SqlScalarType::TimestampTz { precision: self.0 }
80        .nullable(input_type.nullable),
81    preserves_uniqueness = true,
82    inverse = super::CastTimestampTzToDate,
83    is_monotone = true
84)]
85fn cast_date_to_timestamp_tz(&self, a: Date) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
86    let out = CheckedTimestamp::from_timestamplike(DateTime::<Utc>::from_naive_utc_and_offset(
87        NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap(),
88        Utc,
89    ))?;
90    let updated = out.round_to_precision(self.0)?;
91    Ok(updated)
92}
93
94pub fn extract_date_inner(units: DateTimeUnits, date: NaiveDate) -> Result<Numeric, EvalError> {
95    match units {
96        DateTimeUnits::Epoch => Ok(Numeric::from(date.extract_epoch())),
97        DateTimeUnits::Millennium => Ok(Numeric::from(date.millennium())),
98        DateTimeUnits::Century => Ok(Numeric::from(date.century())),
99        DateTimeUnits::Decade => Ok(Numeric::from(date.decade())),
100        DateTimeUnits::Year => Ok(Numeric::from(date.extract_year())),
101        DateTimeUnits::Quarter => Ok(Numeric::from(date.quarter())),
102        DateTimeUnits::Week => Ok(Numeric::from(date.iso_week_number())),
103        DateTimeUnits::Month => Ok(Numeric::from(date.month())),
104        DateTimeUnits::Day => Ok(Numeric::from(date.day())),
105        DateTimeUnits::DayOfWeek => Ok(Numeric::from(date.day_of_week())),
106        DateTimeUnits::DayOfYear => Ok(Numeric::from(date.ordinal())),
107        DateTimeUnits::IsoDayOfWeek => Ok(Numeric::from(date.iso_day_of_week())),
108        DateTimeUnits::Hour
109        | DateTimeUnits::Minute
110        | DateTimeUnits::Second
111        | DateTimeUnits::Milliseconds
112        | DateTimeUnits::Microseconds => Err(EvalError::UnsupportedUnits(
113            format!("{}", units).into(),
114            "date".into(),
115        )),
116        DateTimeUnits::Timezone
117        | DateTimeUnits::TimezoneHour
118        | DateTimeUnits::TimezoneMinute
119        | DateTimeUnits::IsoDayOfYear => Err(EvalError::Unsupported {
120            feature: format!("'{}' timestamp units", units).into(),
121            discussion_no: None,
122        }),
123    }
124}
125
126#[derive(
127    Ord,
128    PartialOrd,
129    Clone,
130    Debug,
131    Eq,
132    PartialEq,
133    Serialize,
134    Deserialize,
135    Hash
136)]
137pub struct ExtractDate(pub DateTimeUnits);
138
139#[sqlfunc(
140    ExtractDate,
141    skip_display = true,
142    is_monotone = most_significant_unit(self.0)
143)]
144fn extract_date(&self, a: Date) -> Result<Numeric, EvalError> {
145    extract_date_inner(self.0, a.into())
146}
147
148impl fmt::Display for ExtractDate {
149    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        write!(f, "extract_{}_d", self.0)
151    }
152}