use std::sync::Arc;
use arrow::array::ArrayRef;
use arrow::datatypes::*;
use arrow::error::Result;
use arrow_convert::deserialize::TryIntoCollection;
use arrow_convert::field::{ArrowField, DEFAULT_FIELD_NAME};
use arrow_convert::serialize::TryIntoArrow;
use arrow_convert::{ArrowDeserialize, ArrowField, ArrowSerialize};
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
use pretty_assertions::assert_eq;
#[test]
fn test_datetime_utc_roundtrip() {
let original_array = vec![
Utc.timestamp_opt(1000, 0).unwrap(),
Utc.timestamp_opt(10000, 123_456_789).unwrap(),
Utc.timestamp_opt(-1000, 0).unwrap(),
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let round_trip: Vec<DateTime<Utc>> = arrow_array.try_into_collection().unwrap();
assert_eq!(original_array, round_trip);
}
#[test]
fn test_datetime_utc_optional_roundtrip() {
let original_array = vec![
Some(Utc.timestamp_opt(1000, 0).unwrap()),
None,
Some(Utc.timestamp_opt(10000, 123_456_789).unwrap()),
None,
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let round_trip: Vec<Option<DateTime<Utc>>> = arrow_array.try_into_collection().unwrap();
assert_eq!(original_array, round_trip);
}
#[test]
fn test_datetime_utc_vec_roundtrip() {
let original_array = vec![
vec![Utc.timestamp_opt(1000, 0).unwrap(), Utc.timestamp_opt(2000, 0).unwrap()],
vec![Utc.timestamp_opt(3000, 0).unwrap()],
vec![],
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let round_trip: Vec<Vec<DateTime<Utc>>> = arrow_array.try_into_collection().unwrap();
assert_eq!(original_array, round_trip);
}
#[test]
fn test_datetime_utc_schema() {
assert_eq!(
<DateTime<Utc> as ArrowField>::data_type(),
DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into()))
);
assert!(!<DateTime<Utc> as ArrowField>::is_nullable());
assert!(<Option<DateTime<Utc>> as ArrowField>::is_nullable());
}
#[test]
fn test_datetime_utc_vec_schema() {
assert_eq!(
<Vec<DateTime<Utc>> as ArrowField>::data_type(),
DataType::List(Arc::new(Field::new(
DEFAULT_FIELD_NAME,
DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into())),
false
)))
);
}
#[test]
fn test_naive_vs_utc_schema_differs() {
let naive_dt = <NaiveDateTime as ArrowField>::data_type();
let utc_dt = <DateTime<Utc> as ArrowField>::data_type();
assert_ne!(naive_dt, utc_dt);
assert_eq!(naive_dt, DataType::Timestamp(TimeUnit::Nanosecond, None));
assert_eq!(
utc_dt,
DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into()))
);
}
fn data_mismatch_error<Expected: ArrowField, Actual: ArrowField>() -> arrow::error::ArrowError {
arrow::error::ArrowError::InvalidArgumentError(format!(
"Data type mismatch. Expected type={:#?} is_nullable={}, but was type={:#?} is_nullable={}",
Expected::data_type(),
Expected::is_nullable(),
Actual::data_type(),
Actual::is_nullable()
))
}
#[test]
fn test_datetime_utc_to_naive_mismatch_error() {
let original_array = vec![Utc.timestamp_opt(1000, 0).unwrap(), Utc.timestamp_opt(2000, 0).unwrap()];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let result: Result<Vec<NaiveDateTime>> = arrow_array.try_into_collection();
assert_eq!(
result.unwrap_err().to_string(),
data_mismatch_error::<NaiveDateTime, DateTime<Utc>>().to_string()
);
}
#[test]
fn test_naive_to_datetime_utc_mismatch_error() {
let original_array = vec![
DateTime::from_timestamp(1000, 0).unwrap().naive_utc(),
DateTime::from_timestamp(2000, 0).unwrap().naive_utc(),
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let result: Result<Vec<DateTime<Utc>>> = arrow_array.try_into_collection();
assert_eq!(
result.unwrap_err().to_string(),
data_mismatch_error::<DateTime<Utc>, NaiveDateTime>().to_string()
);
}
#[test]
fn test_datetime_utc_in_struct() {
#[derive(Debug, Clone, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)]
struct Event {
id: i64,
timestamp: DateTime<Utc>,
description: String,
}
let original_array = vec![
Event {
id: 1,
timestamp: Utc.timestamp_opt(1000, 0).unwrap(),
description: "first".to_string(),
},
Event {
id: 2,
timestamp: Utc.timestamp_opt(2000, 500_000_000).unwrap(),
description: "second".to_string(),
},
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let round_trip: Vec<Event> = arrow_array.try_into_collection().unwrap();
assert_eq!(original_array, round_trip);
}
#[test]
fn test_datetime_utc_optional_in_struct() {
#[derive(Debug, Clone, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)]
struct MaybeEvent {
id: i64,
timestamp: Option<DateTime<Utc>>,
}
let original_array = vec![
MaybeEvent {
id: 1,
timestamp: Some(Utc.timestamp_opt(1000, 0).unwrap()),
},
MaybeEvent { id: 2, timestamp: None },
MaybeEvent {
id: 3,
timestamp: Some(Utc.timestamp_opt(3000, 0).unwrap()),
},
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let round_trip: Vec<MaybeEvent> = arrow_array.try_into_collection().unwrap();
assert_eq!(original_array, round_trip);
}
#[test]
fn test_datetime_utc_vec_in_struct() {
#[derive(Debug, Clone, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)]
struct EventLog {
name: String,
timestamps: Vec<DateTime<Utc>>,
}
let original_array = vec![
EventLog {
name: "log1".to_string(),
timestamps: vec![Utc.timestamp_opt(1000, 0).unwrap(), Utc.timestamp_opt(2000, 0).unwrap()],
},
EventLog {
name: "log2".to_string(),
timestamps: vec![],
},
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let round_trip: Vec<EventLog> = arrow_array.try_into_collection().unwrap();
assert_eq!(original_array, round_trip);
}
#[test]
fn test_datetime_utc_struct_schema() {
#[derive(Debug, ArrowField)]
#[allow(dead_code)]
struct Event {
naive_ts: NaiveDateTime,
utc_ts: DateTime<Utc>,
optional_utc_ts: Option<DateTime<Utc>>,
utc_ts_list: Vec<DateTime<Utc>>,
}
assert_eq!(
<Event as ArrowField>::data_type(),
DataType::Struct(Fields::from(vec![
Field::new(
"naive_ts",
DataType::Timestamp(TimeUnit::Nanosecond, None),
false
),
Field::new(
"utc_ts",
DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into())),
false
),
Field::new(
"optional_utc_ts",
DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into())),
true
),
Field::new(
"utc_ts_list",
DataType::List(Arc::new(Field::new(
DEFAULT_FIELD_NAME,
DataType::Timestamp(TimeUnit::Nanosecond, Some("UTC".into())),
false
))),
false
),
]))
);
}
#[test]
fn test_struct_datetime_mismatch() {
#[derive(Debug, Clone, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)]
struct WithUtc {
ts: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)]
struct WithNaive {
ts: NaiveDateTime,
}
let original = vec![WithUtc {
ts: Utc.timestamp_opt(1000, 0).unwrap(),
}];
let arrow_array: ArrayRef = original.try_into_arrow().unwrap();
let result: Result<Vec<WithNaive>> = arrow_array.try_into_collection();
assert_eq!(
result.unwrap_err().to_string(),
data_mismatch_error::<WithNaive, WithUtc>().to_string()
);
}
#[test]
fn test_datetime_utc_nanosecond_precision() {
let original_array = vec![
Utc.timestamp_nanos(1_000_000_000_123_456_789),
Utc.timestamp_nanos(1_000_000_000_000_000_001),
Utc.timestamp_nanos(1_000_000_000_999_999_999),
];
let arrow_array: ArrayRef = original_array.try_into_arrow().unwrap();
let round_trip: Vec<DateTime<Utc>> = arrow_array.try_into_collection().unwrap();
assert_eq!(original_array, round_trip);
assert_eq!(original_array[0].timestamp_subsec_nanos(), 123_456_789);
assert_eq!(round_trip[0].timestamp_subsec_nanos(), 123_456_789);
}