use std::fmt;
use jiff::Span;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
use vortex_error::vortex_err;
use crate::dtype::DType;
use crate::dtype::Nullability;
use crate::dtype::PType;
use crate::dtype::extension::ExtDType;
use crate::dtype::extension::ExtId;
use crate::dtype::extension::ExtVTable;
use crate::extension::datetime::TimeUnit;
use crate::scalar::ScalarValue;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Time;
fn time_ptype(time_unit: &TimeUnit) -> Option<PType> {
Some(match time_unit {
TimeUnit::Nanoseconds | TimeUnit::Microseconds => PType::I64,
TimeUnit::Milliseconds | TimeUnit::Seconds => PType::I32,
TimeUnit::Days => return None,
})
}
impl Time {
pub fn try_new(time_unit: TimeUnit, nullability: Nullability) -> VortexResult<ExtDType<Self>> {
let ptype = time_ptype(&time_unit)
.ok_or_else(|| vortex_err!("Time type does not support time unit {}", time_unit))?;
ExtDType::try_new(time_unit, DType::Primitive(ptype, nullability))
}
pub fn new(time_unit: TimeUnit, nullability: Nullability) -> ExtDType<Self> {
Self::try_new(time_unit, nullability).vortex_expect("failed to create time dtype")
}
}
pub enum TimeValue {
Seconds(i32),
Milliseconds(i32),
Microseconds(i64),
Nanoseconds(i64),
}
impl fmt::Display for TimeValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let min = jiff::civil::Time::MIN;
let time = match self {
TimeValue::Seconds(s) => min + Span::new().seconds(*s),
TimeValue::Milliseconds(ms) => min + Span::new().milliseconds(*ms),
TimeValue::Microseconds(us) => min + Span::new().microseconds(*us),
TimeValue::Nanoseconds(ns) => min + Span::new().nanoseconds(*ns),
};
write!(f, "{}", time)
}
}
impl ExtVTable for Time {
type Metadata = TimeUnit;
type NativeValue<'a> = TimeValue;
fn id(&self) -> ExtId {
ExtId::new_ref("vortex.time")
}
fn serialize_metadata(&self, metadata: &Self::Metadata) -> VortexResult<Vec<u8>> {
Ok(vec![u8::from(*metadata)])
}
fn deserialize_metadata(&self, data: &[u8]) -> VortexResult<Self::Metadata> {
let tag = data[0];
TimeUnit::try_from(tag)
}
fn validate_dtype(&self, metadata: &Self::Metadata, storage_dtype: &DType) -> VortexResult<()> {
let ptype = time_ptype(metadata)
.ok_or_else(|| vortex_err!("Time type does not support time unit {}", metadata))?;
vortex_ensure!(
storage_dtype.as_ptype() == ptype,
"Time storage dtype for {} must be {}",
metadata,
ptype
);
Ok(())
}
fn unpack_native(
&self,
metadata: &Self::Metadata,
_storage_dtype: &DType,
storage_value: &ScalarValue,
) -> VortexResult<Self::NativeValue<'_>> {
let length_of_time = storage_value.as_primitive().cast::<i64>()?;
let (span, value) = match *metadata {
TimeUnit::Seconds => {
let v = i32::try_from(length_of_time)
.map_err(|e| vortex_err!("Time seconds value out of i32 range: {e}"))?;
(Span::new().seconds(v), TimeValue::Seconds(v))
}
TimeUnit::Milliseconds => {
let v = i32::try_from(length_of_time)
.map_err(|e| vortex_err!("Time milliseconds value out of i32 range: {e}"))?;
(Span::new().milliseconds(v), TimeValue::Milliseconds(v))
}
TimeUnit::Microseconds => (
Span::new().microseconds(length_of_time),
TimeValue::Microseconds(length_of_time),
),
TimeUnit::Nanoseconds => (
Span::new().nanoseconds(length_of_time),
TimeValue::Nanoseconds(length_of_time),
),
d @ TimeUnit::Days => vortex_bail!("Time type does not support time unit {d}"),
};
jiff::civil::Time::MIN
.checked_add(span)
.map_err(|e| vortex_err!("Invalid time scalar: {}", e))?;
Ok(value)
}
}
#[cfg(test)]
mod tests {
use vortex_error::VortexResult;
use crate::dtype::DType;
use crate::dtype::Nullability::Nullable;
use crate::extension::datetime::Time;
use crate::extension::datetime::TimeUnit;
use crate::scalar::PValue;
use crate::scalar::Scalar;
use crate::scalar::ScalarValue;
#[test]
fn validate_time_scalar() -> VortexResult<()> {
let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
Scalar::try_new(dtype, Some(ScalarValue::Primitive(PValue::I32(3661))))?;
Ok(())
}
#[test]
fn reject_time_out_of_range() {
let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
let result = Scalar::try_new(dtype, Some(ScalarValue::Primitive(PValue::I32(86400))));
assert!(result.is_err());
}
#[test]
fn display_time_scalar() {
let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
let scalar = Scalar::new(
dtype.clone(),
Some(ScalarValue::Primitive(PValue::I32(3661))),
);
assert_eq!(format!("{}", scalar.as_extension()), "01:01:01");
let scalar = Scalar::new(dtype, Some(ScalarValue::Primitive(PValue::I32(0))));
assert_eq!(format!("{}", scalar.as_extension()), "00:00:00");
}
}