use std::hash::{Hash, Hasher};
use std::sync::Arc;
use crate::color::Color;
use crate::scales::geometry::Geometry;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Date(pub i32);
impl Date {
pub const fn from_days(days: i32) -> Self {
Date(days)
}
pub const fn to_days(self) -> i32 {
self.0
}
pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
Date(days_from_civil(year, month, day))
}
pub fn to_ymd(self) -> (i32, u8, u8) {
civil_from_days(self.0)
}
pub const fn add_days(self, n: i32) -> Self {
Date(self.0.wrapping_add(n))
}
pub fn add_months(self, n: i32) -> Self {
let (y, m, d) = self.to_ymd();
let total = (y as i64) * 12 + (m as i64 - 1) + n as i64;
let new_year = total.div_euclid(12) as i32;
let new_month = (total.rem_euclid(12) + 1) as u8;
let dom = days_in_month(new_year, new_month);
let new_day = d.min(dom);
Date::from_ymd(new_year, new_month, new_day)
}
pub fn start_of_month(self) -> Self {
let (y, m, _) = self.to_ymd();
Date::from_ymd(y, m, 1)
}
pub fn start_of_quarter(self) -> Self {
let (y, m, _) = self.to_ymd();
let qm = (((m - 1) / 3) * 3) + 1;
Date::from_ymd(y, qm, 1)
}
pub fn start_of_year(self) -> Self {
let (y, _, _) = self.to_ymd();
Date::from_ymd(y, 1, 1)
}
pub fn day_of_week(self) -> u8 {
((self.0.rem_euclid(7) + 3) % 7) as u8
}
pub fn start_of_week(self) -> Self {
Date(self.0 - self.day_of_week() as i32)
}
}
fn days_in_month(year: i32, month: u8) -> u8 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
let leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
if leap {
29
} else {
28
}
}
_ => panic!("days_in_month: month {month} out of 1..=12"),
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct DateTime(pub i64);
impl DateTime {
pub const fn from_micros(us: i64) -> Self {
DateTime(us)
}
pub const fn to_micros(self) -> i64 {
self.0
}
pub fn from_ymd_hms_micros(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
micros: u32,
) -> Self {
let days = days_from_civil(year, month, day) as i64;
let time_us = (hour as i64) * 3_600_000_000
+ (minute as i64) * 60_000_000
+ (second as i64) * 1_000_000
+ (micros as i64);
DateTime(days * 86_400_000_000 + time_us)
}
pub fn split(self) -> (Date, i64) {
let day_us = 86_400_000_000_i64;
let mut days = self.0.div_euclid(day_us);
let mut us = self.0.rem_euclid(day_us);
if us < 0 {
us += day_us;
days -= 1;
}
(Date(days as i32), us)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Time(pub i64);
impl Time {
pub const fn from_nanos(ns: i64) -> Self {
Time(ns)
}
pub const fn to_nanos(self) -> i64 {
self.0
}
pub const fn from_micros(us: i64) -> Self {
Time(us * 1_000)
}
pub const fn to_micros(self) -> i64 {
self.0 / 1_000
}
pub fn from_hms_nanos(hour: u8, minute: u8, second: u8, nanos: u32) -> Self {
Time(
(hour as i64) * 3_600_000_000_000
+ (minute as i64) * 60_000_000_000
+ (second as i64) * 1_000_000_000
+ (nanos as i64),
)
}
pub fn from_hms_micros(hour: u8, minute: u8, second: u8, micros: u32) -> Self {
Self::from_hms_nanos(hour, minute, second, micros.saturating_mul(1_000))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Duration(pub i64);
impl Duration {
pub const fn from_micros(us: i64) -> Self {
Duration(us)
}
pub const fn to_micros(self) -> i64 {
self.0
}
pub fn from_seconds(s: i64) -> Self {
Duration(s.saturating_mul(1_000_000))
}
}
pub use crate::style_vocab::LinetypeStep;
#[derive(Clone, Debug)]
pub enum Value {
Null,
Number(f64),
String(Arc<str>),
Bool(bool),
Color(Color),
Date(i32),
DateTime(i64),
Time(i64),
Duration(i64),
Linetype(Arc<[LinetypeStep]>),
Geometry(Arc<Geometry>),
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn is_finite(&self) -> bool {
match self {
Value::Number(n) => n.is_finite(),
Value::Date(_) | Value::DateTime(_) | Value::Time(_) | Value::Duration(_) => true,
_ => false,
}
}
pub fn as_number(&self) -> Option<f64> {
match *self {
Value::Number(n) => Some(n),
Value::Date(d) => Some(d as f64),
Value::DateTime(us) => Some(us as f64),
Value::Time(ns) => Some(ns as f64),
Value::Duration(us) => Some(us as f64),
_ => None,
}
}
pub fn as_temporal_f64(&self) -> Option<f64> {
self.as_number()
}
pub fn as_color(&self) -> Option<Color> {
if let Value::Color(c) = *self {
Some(c)
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
if let Value::String(s) = self {
Some(s)
} else {
None
}
}
pub fn as_bool(&self) -> Option<bool> {
if let Value::Bool(b) = *self {
Some(b)
} else {
None
}
}
pub fn as_linetype(&self) -> Option<&[LinetypeStep]> {
if let Value::Linetype(p) = self {
Some(p)
} else {
None
}
}
pub fn as_geometry(&self) -> Option<&Geometry> {
if let Value::Geometry(g) = self {
Some(g)
} else {
None
}
}
pub fn key_eq(&self, other: &Value) -> bool {
use Value::*;
match (self, other) {
(Null, Null) => true,
(Number(a), Number(b)) => canonical_f64_bits(*a) == canonical_f64_bits(*b),
(String(a), String(b)) => **a == **b,
(Bool(a), Bool(b)) => a == b,
(Color(a), Color(b)) => *a == *b,
(Date(a), Date(b)) => a == b,
(DateTime(a), DateTime(b)) => a == b,
(Time(a), Time(b)) => a == b,
(Duration(a), Duration(b)) => a == b,
(Linetype(a), Linetype(b)) => {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|(x, y)| linetype_step_key_eq(x, y))
}
(Geometry(a), Geometry(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
pub fn key_hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Value::Null => {}
Value::Number(n) => canonical_f64_bits(*n).hash(state),
Value::String(s) => (**s).hash(state),
Value::Bool(b) => b.hash(state),
Value::Color(c) => {
let [r, g, b, a] = c.components;
canonical_f32_bits(r).hash(state);
canonical_f32_bits(g).hash(state);
canonical_f32_bits(b).hash(state);
canonical_f32_bits(a).hash(state);
}
Value::Date(d) => d.hash(state),
Value::DateTime(us) => us.hash(state),
Value::Time(us) => us.hash(state),
Value::Duration(us) => us.hash(state),
Value::Linetype(p) => {
(p.len() as u64).hash(state);
for step in p.iter() {
linetype_step_key_hash(step, state);
}
}
Value::Geometry(g) => {
(Arc::as_ptr(g) as usize).hash(state);
}
}
}
}
pub(crate) fn linetype_step_key_eq(a: &LinetypeStep, b: &LinetypeStep) -> bool {
use LinetypeStep::*;
match (a, b) {
(Dash(x), Dash(y)) | (Gap(x), Gap(y)) => canonical_f64_bits(*x) == canonical_f64_bits(*y),
(Marker(x), Marker(y)) => **x == **y,
_ => false,
}
}
pub(crate) fn linetype_step_key_hash<H: Hasher>(step: &LinetypeStep, state: &mut H) {
std::mem::discriminant(step).hash(state);
match step {
LinetypeStep::Dash(f) | LinetypeStep::Gap(f) => canonical_f64_bits(*f).hash(state),
LinetypeStep::Marker(s) => (**s).hash(state),
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::Number(v)
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Value::Number(v as f64)
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Value::Number(v as f64)
}
}
impl From<i64> for Value {
fn from(v: i64) -> Self {
Value::Number(v as f64)
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Bool(v)
}
}
impl From<&'static str> for Value {
fn from(v: &'static str) -> Self {
Value::String(Arc::from(v))
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::String(Arc::from(v))
}
}
impl From<Arc<str>> for Value {
fn from(v: Arc<str>) -> Self {
Value::String(v)
}
}
impl From<Color> for Value {
fn from(v: Color) -> Self {
Value::Color(v)
}
}
impl From<Geometry> for Value {
fn from(g: Geometry) -> Self {
Value::Geometry(Arc::new(g))
}
}
impl From<Arc<Geometry>> for Value {
fn from(g: Arc<Geometry>) -> Self {
Value::Geometry(g)
}
}
impl From<Date> for Value {
fn from(v: Date) -> Self {
Value::Date(v.0)
}
}
impl From<DateTime> for Value {
fn from(v: DateTime) -> Self {
Value::DateTime(v.0)
}
}
impl From<Time> for Value {
fn from(v: Time) -> Self {
Value::Time(v.0)
}
}
impl From<Duration> for Value {
fn from(v: Duration) -> Self {
Value::Duration(v.0)
}
}
#[derive(Clone, Debug)]
pub enum DataColumn {
F64(Vec<f64>),
F32(Vec<f32>),
I32(Vec<i32>),
I64(Vec<i64>),
Bool(Vec<bool>),
String(Vec<Arc<str>>),
Color(Vec<Color>),
Date(Vec<i32>),
DateTime(Vec<i64>),
Time(Vec<i64>),
Duration(Vec<i64>),
Linetype(Vec<Arc<[LinetypeStep]>>),
Geometry(Vec<Arc<Geometry>>),
}
impl DataColumn {
pub fn len(&self) -> usize {
match self {
DataColumn::F64(v) => v.len(),
DataColumn::F32(v) => v.len(),
DataColumn::I32(v) => v.len(),
DataColumn::I64(v) => v.len(),
DataColumn::Bool(v) => v.len(),
DataColumn::String(v) => v.len(),
DataColumn::Color(v) => v.len(),
DataColumn::Date(v) => v.len(),
DataColumn::DateTime(v) => v.len(),
DataColumn::Time(v) => v.len(),
DataColumn::Duration(v) => v.len(),
DataColumn::Linetype(v) => v.len(),
DataColumn::Geometry(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, i: usize) -> Value {
match self {
DataColumn::F64(v) => Value::Number(v[i]),
DataColumn::F32(v) => Value::Number(v[i] as f64),
DataColumn::I32(v) => Value::Number(v[i] as f64),
DataColumn::I64(v) => Value::Number(v[i] as f64),
DataColumn::Bool(v) => Value::Bool(v[i]),
DataColumn::String(v) => Value::String(v[i].clone()),
DataColumn::Color(v) => Value::Color(v[i]),
DataColumn::Date(v) => Value::Date(v[i]),
DataColumn::DateTime(v) => Value::DateTime(v[i]),
DataColumn::Time(v) => Value::Time(v[i]),
DataColumn::Duration(v) => Value::Duration(v[i]),
DataColumn::Linetype(v) => Value::Linetype(v[i].clone()),
DataColumn::Geometry(v) => Value::Geometry(v[i].clone()),
}
}
#[allow(unused)] pub(crate) fn key_hash_at<H: Hasher>(&self, i: usize, state: &mut H) {
match self {
DataColumn::String(v) => {
let s: &Arc<str> = &v[i];
let value_discr = std::mem::discriminant(&Value::String(s.clone()));
value_discr.hash(state);
(**s).hash(state);
}
_ => {
let v = self.get(i);
v.key_hash(state);
}
}
}
#[allow(unused)] pub(crate) fn key_eq_at(&self, i: usize, other: &DataColumn, j: usize) -> bool {
use DataColumn::*;
match (self, other) {
(F64(a), F64(b)) => canonical_f64_bits(a[i]) == canonical_f64_bits(b[j]),
(F32(a), F32(b)) => canonical_f32_bits(a[i]) == canonical_f32_bits(b[j]),
(I32(a), I32(b)) => a[i] == b[j],
(I64(a), I64(b)) => a[i] == b[j],
(Bool(a), Bool(b)) => a[i] == b[j],
(String(a), String(b)) => *a[i] == *b[j],
(Color(a), Color(b)) => a[i] == b[j],
(Date(a), Date(b)) => a[i] == b[j],
(DateTime(a), DateTime(b)) => a[i] == b[j],
(Time(a), Time(b)) => a[i] == b[j],
(Duration(a), Duration(b)) => a[i] == b[j],
(Linetype(a), Linetype(b)) => {
let pa = &a[i];
let pb = &b[j];
pa.len() == pb.len()
&& pa
.iter()
.zip(pb.iter())
.all(|(x, y)| linetype_step_key_eq(x, y))
}
(Geometry(a), Geometry(b)) => Arc::ptr_eq(&a[i], &b[j]),
_ => false,
}
}
}
impl From<Vec<f64>> for DataColumn {
fn from(v: Vec<f64>) -> Self {
DataColumn::F64(v)
}
}
impl From<Vec<f32>> for DataColumn {
fn from(v: Vec<f32>) -> Self {
DataColumn::F32(v)
}
}
impl From<Vec<i32>> for DataColumn {
fn from(v: Vec<i32>) -> Self {
DataColumn::I32(v)
}
}
impl From<Vec<i64>> for DataColumn {
fn from(v: Vec<i64>) -> Self {
DataColumn::I64(v)
}
}
impl From<Vec<bool>> for DataColumn {
fn from(v: Vec<bool>) -> Self {
DataColumn::Bool(v)
}
}
impl From<Vec<&'static str>> for DataColumn {
fn from(v: Vec<&'static str>) -> Self {
DataColumn::String(v.into_iter().map(Arc::from).collect())
}
}
impl From<Vec<String>> for DataColumn {
fn from(v: Vec<String>) -> Self {
DataColumn::String(v.into_iter().map(Arc::from).collect())
}
}
impl From<Vec<Arc<str>>> for DataColumn {
fn from(v: Vec<Arc<str>>) -> Self {
DataColumn::String(v)
}
}
impl From<Vec<Color>> for DataColumn {
fn from(v: Vec<Color>) -> Self {
DataColumn::Color(v)
}
}
impl From<Vec<Date>> for DataColumn {
fn from(v: Vec<Date>) -> Self {
DataColumn::Date(v.into_iter().map(|d| d.0).collect())
}
}
impl From<Vec<DateTime>> for DataColumn {
fn from(v: Vec<DateTime>) -> Self {
DataColumn::DateTime(v.into_iter().map(|d| d.0).collect())
}
}
impl From<Vec<Time>> for DataColumn {
fn from(v: Vec<Time>) -> Self {
DataColumn::Time(v.into_iter().map(|d| d.0).collect())
}
}
impl From<Vec<Duration>> for DataColumn {
fn from(v: Vec<Duration>) -> Self {
DataColumn::Duration(v.into_iter().map(|d| d.0).collect())
}
}
impl From<std::ops::Range<i64>> for DataColumn {
fn from(r: std::ops::Range<i64>) -> Self {
DataColumn::I64(r.collect())
}
}
impl From<Vec<Arc<[LinetypeStep]>>> for DataColumn {
fn from(v: Vec<Arc<[LinetypeStep]>>) -> Self {
DataColumn::Linetype(v)
}
}
impl From<Vec<Geometry>> for DataColumn {
fn from(v: Vec<Geometry>) -> Self {
DataColumn::Geometry(v.into_iter().map(Arc::new).collect())
}
}
impl From<Vec<Arc<Geometry>>> for DataColumn {
fn from(v: Vec<Arc<Geometry>>) -> Self {
DataColumn::Geometry(v)
}
}
fn canonical_f64_bits(n: f64) -> u64 {
if n.is_nan() {
f64::NAN.to_bits()
} else if n == 0.0 {
0u64
} else {
n.to_bits()
}
}
fn canonical_f32_bits(n: f32) -> u32 {
if n.is_nan() {
f32::NAN.to_bits()
} else if n == 0.0 {
0u32
} else {
n.to_bits()
}
}
fn days_from_civil(year: i32, month: u8, day: u8) -> i32 {
let y = if month <= 2 { year - 1 } else { year };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = (y - era * 400) as u32; let m = month as i32;
let d = day as i32;
let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) as u32 + 2) / 5 + d as u32 - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; (era * 146097 + doe as i32) - 719468
}
fn civil_from_days(days: i32) -> (i32, u8, u8) {
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe as i32 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = (doy - (153 * mp + 2) / 5 + 1) as u8;
let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u8;
let y_out = if m <= 2 { y + 1 } else { y };
(y_out, m, d)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_from_f64() {
let v: Value = 1.5_f64.into();
assert_eq!(v.as_number(), Some(1.5));
}
#[test]
fn value_from_i32() {
let v: Value = 42_i32.into();
assert_eq!(v.as_number(), Some(42.0));
}
#[test]
fn value_from_bool() {
let v: Value = true.into();
assert_eq!(v.as_bool(), Some(true));
assert_eq!(v.as_number(), None);
}
#[test]
fn value_from_static_str() {
let v: Value = "hello".into();
assert_eq!(v.as_str(), Some("hello"));
}
#[test]
fn value_from_string() {
let v: Value = String::from("world").into();
assert_eq!(v.as_str(), Some("world"));
}
#[test]
fn value_from_color() {
let c = Color::new([1.0, 0.5, 0.25, 1.0]);
let v: Value = c.into();
assert_eq!(v.as_color(), Some(c));
}
#[test]
fn date_ymd_round_trip() {
let cases = [
(2024, 1, 1),
(2024, 12, 31),
(1970, 1, 1),
(1969, 12, 31),
(2000, 2, 29),
(1900, 3, 1),
(-1, 12, 31),
];
for (y, m, d) in cases {
let date = Date::from_ymd(y, m, d);
assert_eq!(date.to_ymd(), (y, m, d), "round-trip {y}-{m}-{d}");
}
}
#[test]
fn date_epoch_is_zero() {
assert_eq!(Date::from_ymd(1970, 1, 1).to_days(), 0);
}
#[test]
fn date_known_offsets() {
assert_eq!(Date::from_ymd(2024, 1, 1).to_days(), 19723);
assert_eq!(Date::from_ymd(1969, 12, 31).to_days(), -1);
}
#[test]
fn date_value_round_trip() {
let d = Date::from_ymd(2024, 6, 15);
let v: Value = d.into();
match v {
Value::Date(days) => assert_eq!(days, d.to_days()),
_ => panic!("expected Value::Date"),
}
}
#[test]
fn datetime_split() {
let dt = DateTime::from_ymd_hms_micros(2024, 1, 1, 12, 34, 56, 789012);
let (date, us) = dt.split();
assert_eq!(date, Date::from_ymd(2024, 1, 1));
let expected_us = 12 * 3_600_000_000_i64 + 34 * 60_000_000 + 56 * 1_000_000 + 789012;
assert_eq!(us, expected_us);
}
#[test]
fn datetime_pre_epoch_split() {
let dt = DateTime::from_ymd_hms_micros(1969, 6, 15, 6, 0, 0, 0);
let (date, us) = dt.split();
assert_eq!(date, Date::from_ymd(1969, 6, 15));
assert_eq!(us, 6 * 3_600_000_000_i64);
}
#[test]
fn time_round_trip() {
let t = Time::from_hms_micros(23, 59, 59, 999_999);
assert_eq!(t.to_micros(), 86_399_999_999);
}
#[test]
fn duration_from_seconds_saturates() {
let d = Duration::from_seconds(i64::MAX);
assert_eq!(d.to_micros(), i64::MAX);
}
#[test]
fn temporal_as_temporal_f64() {
assert_eq!(Value::Date(100).as_temporal_f64(), Some(100.0));
assert_eq!(Value::DateTime(123_456).as_temporal_f64(), Some(123_456.0));
assert_eq!(Value::Time(42).as_temporal_f64(), Some(42.0));
assert_eq!(Value::Duration(-7).as_temporal_f64(), Some(-7.0));
}
#[test]
fn null_is_not_finite() {
assert!(!Value::Null.is_finite());
assert!(Value::Number(1.0).is_finite());
assert!(!Value::Number(f64::NAN).is_finite());
assert!(!Value::Number(f64::INFINITY).is_finite());
assert!(Value::Date(0).is_finite());
}
fn hash_of(v: &Value) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut h = DefaultHasher::new();
v.key_hash(&mut h);
h.finish()
}
#[test]
fn key_eq_nan_equals_nan() {
let a = Value::Number(f64::NAN);
let b = Value::Number(f64::NAN);
assert!(a.key_eq(&b));
assert_eq!(hash_of(&a), hash_of(&b));
}
#[test]
fn key_eq_minus_zero_equals_zero() {
let a = Value::Number(-0.0);
let b = Value::Number(0.0);
assert!(a.key_eq(&b));
assert_eq!(hash_of(&a), hash_of(&b));
}
#[test]
fn key_eq_distinguishes_variants() {
let n = Value::Number(1.0);
let d = Value::Date(1);
assert!(!n.key_eq(&d));
assert!(!n.key_eq(&d));
}
#[test]
fn key_eq_distinguishes_strings() {
let a: Value = "abc".into();
let b: Value = "abc".into();
let c: Value = "xyz".into();
assert!(a.key_eq(&b));
assert!(!a.key_eq(&c));
assert_eq!(hash_of(&a), hash_of(&b));
}
#[test]
fn datacolumn_from_vec_f64() {
let col: DataColumn = vec![1.0_f64, 2.0, 3.0].into();
assert!(matches!(col, DataColumn::F64(_)));
assert_eq!(col.len(), 3);
assert!(!col.is_empty());
}
#[test]
fn datacolumn_get_f64() {
let col: DataColumn = vec![1.0_f64, 2.0].into();
assert!(col.get(0).key_eq(&Value::Number(1.0)));
assert!(col.get(1).key_eq(&Value::Number(2.0)));
}
#[test]
fn datacolumn_get_i32_projects_to_number() {
let col: DataColumn = vec![42_i32].into();
assert!(col.get(0).key_eq(&Value::Number(42.0)));
}
#[test]
fn datacolumn_get_color() {
let c = Color::new([0.1, 0.2, 0.3, 1.0]);
let col: DataColumn = vec![c].into();
assert!(matches!(col, DataColumn::Color(_)));
assert_eq!(col.get(0).as_color(), Some(c));
}
#[test]
fn datacolumn_from_vec_date() {
let col: DataColumn = vec![Date::from_ymd(2024, 1, 1), Date::from_ymd(2024, 1, 2)].into();
assert!(matches!(col, DataColumn::Date(_)));
assert_eq!(col.len(), 2);
if let DataColumn::Date(v) = &col {
assert_eq!(v[0], 19723);
assert_eq!(v[1], 19724);
}
}
#[test]
fn datacolumn_get_datetime() {
let dt = DateTime::from_ymd_hms_micros(2024, 1, 1, 0, 0, 0, 0);
let col: DataColumn = vec![dt].into();
match col.get(0) {
Value::DateTime(us) => assert_eq!(us, dt.0),
_ => panic!("expected Value::DateTime"),
}
}
#[test]
fn datacolumn_from_string_collects() {
let col: DataColumn = vec![String::from("a"), String::from("b")].into();
assert!(matches!(col, DataColumn::String(_)));
assert_eq!(col.get(0).as_str(), Some("a"));
assert_eq!(col.get(1).as_str(), Some("b"));
}
#[test]
fn datacolumn_from_static_strs() {
let col: DataColumn = vec!["alpha", "beta"].into();
assert!(matches!(col, DataColumn::String(_)));
assert_eq!(col.get(1).as_str(), Some("beta"));
}
#[test]
fn datacolumn_from_range() {
let col: DataColumn = (0_i64..5).into();
assert!(matches!(col, DataColumn::I64(_)));
assert_eq!(col.len(), 5);
assert!(col.get(0).key_eq(&Value::Number(0.0)));
assert!(col.get(4).key_eq(&Value::Number(4.0)));
}
#[test]
fn datacolumn_key_eq_at_same_variant() {
let a: DataColumn = vec![1.0_f64, 2.0, 3.0].into();
let b: DataColumn = vec![3.0_f64, 1.0].into();
assert!(a.key_eq_at(0, &b, 1)); assert!(a.key_eq_at(2, &b, 0)); assert!(!a.key_eq_at(0, &b, 0)); }
#[test]
fn datacolumn_key_eq_at_mismatched_variant_is_false() {
let a: DataColumn = vec![1_i32].into();
let b: DataColumn = vec![Date::from_days(1)].into();
assert!(!a.key_eq_at(0, &b, 0));
}
fn dash_gap(d: f64, g: f64) -> Arc<[LinetypeStep]> {
Arc::from(vec![LinetypeStep::Dash(d), LinetypeStep::Gap(g)])
}
#[test]
fn value_linetype_round_trip() {
let p = dash_gap(8.0, 4.0);
let v = Value::Linetype(p.clone());
let steps = v.as_linetype().expect("linetype");
assert_eq!(steps.len(), 2);
assert!(matches!(steps[0], LinetypeStep::Dash(d) if (d - 8.0).abs() < 1e-12));
assert!(matches!(steps[1], LinetypeStep::Gap(g) if (g - 4.0).abs() < 1e-12));
assert!(v.as_number().is_none());
assert!(v.as_color().is_none());
assert!(v.as_str().is_none());
assert!(!v.is_finite());
}
#[test]
fn value_linetype_key_eq_element_wise() {
let a = Value::Linetype(dash_gap(8.0, 4.0));
let b = Value::Linetype(dash_gap(8.0, 4.0));
let c = Value::Linetype(Arc::from(vec![
LinetypeStep::Dash(8.0),
LinetypeStep::Gap(4.0),
LinetypeStep::Dash(1.0),
LinetypeStep::Gap(2.0),
]));
let d = Value::Linetype(dash_gap(6.0, 4.0));
assert!(a.key_eq(&b));
assert!(!a.key_eq(&c));
assert!(!a.key_eq(&d));
assert_eq!(hash_of(&a), hash_of(&b));
}
#[test]
fn value_linetype_marker_key_eq() {
let a = Value::Linetype(Arc::from(vec![
LinetypeStep::Marker(Arc::from("circle")),
LinetypeStep::Gap(5.0),
]));
let b = Value::Linetype(Arc::from(vec![
LinetypeStep::Marker(Arc::from("circle")),
LinetypeStep::Gap(5.0),
]));
let c = Value::Linetype(Arc::from(vec![
LinetypeStep::Marker(Arc::from("square")),
LinetypeStep::Gap(5.0),
]));
assert!(a.key_eq(&b));
assert!(!a.key_eq(&c));
assert_eq!(hash_of(&a), hash_of(&b));
}
#[test]
fn value_linetype_empty_is_solid() {
let solid = Value::Linetype(Arc::from(Vec::<LinetypeStep>::new()));
assert_eq!(solid.as_linetype().map(|p| p.len()), Some(0));
}
#[test]
fn datacolumn_linetype_get() {
let col: DataColumn =
vec![dash_gap(8.0, 4.0), Arc::from(Vec::<LinetypeStep>::new())].into();
assert!(matches!(col, DataColumn::Linetype(_)));
assert_eq!(col.len(), 2);
assert!(col.get(0).key_eq(&Value::Linetype(dash_gap(8.0, 4.0))));
assert!(col
.get(1)
.key_eq(&Value::Linetype(Arc::from(Vec::<LinetypeStep>::new()))));
}
#[test]
fn datacolumn_linetype_key_eq_at() {
let a: DataColumn = vec![dash_gap(8.0, 4.0)].into();
let b: DataColumn = vec![dash_gap(8.0, 4.0)].into();
let c: DataColumn = vec![dash_gap(2.0, 3.0)].into();
assert!(a.key_eq_at(0, &b, 0));
assert!(!a.key_eq_at(0, &c, 0));
}
#[test]
fn datacolumn_key_hash_at_strings() {
let a: DataColumn = vec![String::from("foo")].into();
let b: DataColumn = vec![String::from("foo")].into();
let c: DataColumn = vec![String::from("bar")].into();
use std::collections::hash_map::DefaultHasher;
let mut h_a = DefaultHasher::new();
a.key_hash_at(0, &mut h_a);
let mut h_b = DefaultHasher::new();
b.key_hash_at(0, &mut h_b);
let mut h_c = DefaultHasher::new();
c.key_hash_at(0, &mut h_c);
assert_eq!(h_a.finish(), h_b.finish());
assert_ne!(h_a.finish(), h_c.finish());
}
}