use std::fmt;
use chrono::{NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike};
use mz_lowertest::MzReflect;
use mz_pgtz::timezone::Timezone;
use mz_repr::adt::datetime::{DateTimeField, DateTimeUnits};
use mz_repr::adt::interval::Interval;
use mz_repr::adt::numeric::{DecimalLike, Numeric};
use mz_repr::adt::timestamp::TimeLike;
use mz_repr::{strconv, ColumnType, ScalarType};
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
use crate::scalar::func::EagerUnaryFunc;
use crate::EvalError;
sqlfunc!(
#[sqlname = "time_to_text"]
#[preserves_uniqueness = true]
#[inverse = to_unary!(super::CastStringToTime)]
fn cast_time_to_string(a: NaiveTime) -> String {
let mut buf = String::new();
strconv::format_time(&mut buf, a);
buf
}
);
sqlfunc!(
#[sqlname = "time_to_interval"]
#[preserves_uniqueness = true]
#[inverse = to_unary!(super::CastIntervalToTime)]
fn cast_time_to_interval<'a>(t: NaiveTime) -> Interval {
let micros: i64 = Interval::convert_date_time_unit(
DateTimeField::Second,
DateTimeField::Microseconds,
i64::from(t.num_seconds_from_midnight()),
)
.unwrap()
+ i64::from(t.nanosecond()) / i64::from(Interval::NANOSECOND_PER_MICROSECOND);
Interval::new(0, 0, micros)
}
);
pub fn date_part_time_inner<D>(units: DateTimeUnits, time: NaiveTime) -> Result<D, EvalError>
where
D: DecimalLike,
{
match units {
DateTimeUnits::Epoch => Ok(time.extract_epoch()),
DateTimeUnits::Hour => Ok(D::from(time.hour())),
DateTimeUnits::Minute => Ok(D::from(time.minute())),
DateTimeUnits::Second => Ok(time.extract_second()),
DateTimeUnits::Milliseconds => Ok(time.extract_millisecond()),
DateTimeUnits::Microseconds => Ok(time.extract_microsecond()),
DateTimeUnits::Millennium
| DateTimeUnits::Century
| DateTimeUnits::Decade
| DateTimeUnits::Year
| DateTimeUnits::Quarter
| DateTimeUnits::Month
| DateTimeUnits::Week
| DateTimeUnits::Day
| DateTimeUnits::DayOfYear
| DateTimeUnits::DayOfWeek
| DateTimeUnits::IsoDayOfYear
| DateTimeUnits::IsoDayOfWeek => Err(EvalError::UnsupportedUnits(
format!("{}", units).into(),
"time".into(),
)),
DateTimeUnits::Timezone | DateTimeUnits::TimezoneHour | DateTimeUnits::TimezoneMinute => {
Err(EvalError::Unsupported {
feature: format!("'{}' timestamp units", units).into(),
discussion_no: None,
})
}
}
}
#[derive(
Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
)]
pub struct ExtractTime(pub DateTimeUnits);
impl<'a> EagerUnaryFunc<'a> for ExtractTime {
type Input = NaiveTime;
type Output = Result<Numeric, EvalError>;
fn call(&self, a: NaiveTime) -> Result<Numeric, EvalError> {
date_part_time_inner(self.0, a)
}
fn output_type(&self, input: ColumnType) -> ColumnType {
ScalarType::Numeric { max_scale: None }.nullable(input.nullable)
}
}
impl fmt::Display for ExtractTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "extract_{}_t", self.0)
}
}
#[derive(
Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
)]
pub struct DatePartTime(pub DateTimeUnits);
impl<'a> EagerUnaryFunc<'a> for DatePartTime {
type Input = NaiveTime;
type Output = Result<f64, EvalError>;
fn call(&self, a: NaiveTime) -> Result<f64, EvalError> {
date_part_time_inner(self.0, a)
}
fn output_type(&self, input: ColumnType) -> ColumnType {
ScalarType::Float64.nullable(input.nullable)
}
}
impl fmt::Display for DatePartTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "date_part_{}_t", self.0)
}
}
pub fn timezone_time(tz: Timezone, t: NaiveTime, wall_time: &NaiveDateTime) -> NaiveTime {
let offset = match tz {
Timezone::FixedOffset(offset) => offset,
Timezone::Tz(tz) => tz.offset_from_utc_datetime(wall_time).fix(),
};
t + offset
}
#[derive(
Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
)]
pub struct TimezoneTime {
pub tz: Timezone,
#[proptest(strategy = "crate::func::any_naive_datetime()")]
pub wall_time: NaiveDateTime,
}
impl<'a> EagerUnaryFunc<'a> for TimezoneTime {
type Input = NaiveTime;
type Output = NaiveTime;
fn call(&self, a: NaiveTime) -> NaiveTime {
timezone_time(self.tz, a, &self.wall_time)
}
fn output_type(&self, input: ColumnType) -> ColumnType {
ScalarType::Time.nullable(input.nullable)
}
}
impl fmt::Display for TimezoneTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "timezone_{}_t", self.tz)
}
}