use arrow_array::types::*;
use arrow_array::ArrowPrimitiveType;
use arrow_schema::ArrowError;
use chrono::prelude::*;
#[inline]
pub fn string_to_timestamp_nanos(s: &str) -> Result<i64, ArrowError> {
if let Ok(ts) = DateTime::parse_from_rfc3339(s) {
return Ok(ts.timestamp_nanos());
}
let supported_formats = vec![
"%Y-%m-%d %H:%M:%S%.f%:z", "%Y-%m-%d %H%M%S%.3f%:z", ];
for f in supported_formats.iter() {
if let Ok(ts) = DateTime::parse_from_str(s, f) {
return to_timestamp_nanos(ts.naive_utc());
}
}
if let Ok(ts) = Utc.datetime_from_str(s, "%Y-%m-%d %H:%M:%S%.fZ") {
return to_timestamp_nanos(ts.naive_utc());
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
return to_timestamp_nanos(ts);
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
return Ok(ts.timestamp_nanos());
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") {
return to_timestamp_nanos(ts);
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
return Ok(ts.timestamp_nanos());
}
if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
if let Some(ts) = dt.and_hms_opt(0, 0, 0) {
return Ok(ts.timestamp_nanos());
}
}
Err(ArrowError::CastError(format!(
"Error parsing '{s}' as timestamp"
)))
}
#[inline]
fn to_timestamp_nanos(dt: NaiveDateTime) -> Result<i64, ArrowError> {
if dt.timestamp().checked_mul(1_000_000_000).is_none() {
return Err(ArrowError::ParseError(
ERR_NANOSECONDS_NOT_SUPPORTED.to_string(),
));
}
Ok(dt.timestamp_nanos())
}
pub fn string_to_time_nanoseconds(s: &str) -> Result<i64, ArrowError> {
fn preprocess_time_string(string: &str) -> (usize, bool, bool) {
string
.as_bytes()
.iter()
.fold((0, false, false), |tup, char| match char {
b':' => (tup.0 + 1, tup.1, tup.2),
b'.' => (tup.0, true, tup.2),
b' ' => (tup.0, tup.1, true),
_ => tup,
})
}
let formats: &[&str] = match preprocess_time_string(s.trim()) {
(2, true, false) => &["%H:%M:%S%.f", "%k:%M:%S%.f"],
(2, true, true) => &[
"%I:%M:%S%.f %P",
"%I:%M:%S%.f %p",
"%l:%M:%S%.f %P",
"%l:%M:%S%.f %p",
],
(2, false, false) => &["%H:%M:%S", "%k:%M:%S"],
(2, false, true) => &["%I:%M:%S %P", "%I:%M:%S %p", "%l:%M:%S %P", "%l:%M:%S %p"],
(1, false, false) => &["%H:%M", "%k:%M"],
(1, false, true) => &["%I:%M %P", "%I:%M %p", "%l:%M %P", "%l:%M %p"],
_ => &[],
};
formats
.iter()
.find_map(|f| NaiveTime::parse_from_str(s, f).ok())
.map(|nt| {
nt.num_seconds_from_midnight() as i64 * 1_000_000_000 + nt.nanosecond() as i64
})
.ok_or_else(|| ArrowError::CastError(format!("Error parsing '{s}' as time")))
}
pub trait Parser: ArrowPrimitiveType {
fn parse(string: &str) -> Option<Self::Native>;
fn parse_formatted(string: &str, _format: &str) -> Option<Self::Native> {
Self::parse(string)
}
}
impl Parser for Float32Type {
fn parse(string: &str) -> Option<f32> {
lexical_core::parse(string.as_bytes()).ok()
}
}
impl Parser for Float64Type {
fn parse(string: &str) -> Option<f64> {
lexical_core::parse(string.as_bytes()).ok()
}
}
macro_rules! parser_primitive {
($t:ty) => {
impl Parser for $t {
fn parse(string: &str) -> Option<Self::Native> {
string.parse::<Self::Native>().ok()
}
}
};
}
parser_primitive!(UInt64Type);
parser_primitive!(UInt32Type);
parser_primitive!(UInt16Type);
parser_primitive!(UInt8Type);
parser_primitive!(Int64Type);
parser_primitive!(Int32Type);
parser_primitive!(Int16Type);
parser_primitive!(Int8Type);
impl Parser for TimestampNanosecondType {
fn parse(string: &str) -> Option<i64> {
string_to_timestamp_nanos(string).ok()
}
}
impl Parser for TimestampMicrosecondType {
fn parse(string: &str) -> Option<i64> {
let nanos = string_to_timestamp_nanos(string).ok();
nanos.map(|x| x / 1000)
}
}
impl Parser for TimestampMillisecondType {
fn parse(string: &str) -> Option<i64> {
let nanos = string_to_timestamp_nanos(string).ok();
nanos.map(|x| x / 1_000_000)
}
}
impl Parser for TimestampSecondType {
fn parse(string: &str) -> Option<i64> {
let nanos = string_to_timestamp_nanos(string).ok();
nanos.map(|x| x / 1_000_000_000)
}
}
impl Parser for Time64NanosecondType {
fn parse(string: &str) -> Option<Self::Native> {
string_to_time_nanoseconds(string)
.ok()
.or_else(|| string.parse::<Self::Native>().ok())
}
fn parse_formatted(string: &str, format: &str) -> Option<Self::Native> {
let nt = NaiveTime::parse_from_str(string, format).ok()?;
Some(
nt.num_seconds_from_midnight() as i64 * 1_000_000_000
+ nt.nanosecond() as i64,
)
}
}
impl Parser for Time64MicrosecondType {
fn parse(string: &str) -> Option<Self::Native> {
string_to_time_nanoseconds(string)
.ok()
.map(|nanos| nanos / 1_000)
.or_else(|| string.parse::<Self::Native>().ok())
}
fn parse_formatted(string: &str, format: &str) -> Option<Self::Native> {
let nt = NaiveTime::parse_from_str(string, format).ok()?;
Some(
nt.num_seconds_from_midnight() as i64 * 1_000_000
+ nt.nanosecond() as i64 / 1_000,
)
}
}
impl Parser for Time32MillisecondType {
fn parse(string: &str) -> Option<Self::Native> {
string_to_time_nanoseconds(string)
.ok()
.map(|nanos| (nanos / 1_000_000) as i32)
.or_else(|| string.parse::<Self::Native>().ok())
}
fn parse_formatted(string: &str, format: &str) -> Option<Self::Native> {
let nt = NaiveTime::parse_from_str(string, format).ok()?;
Some(
nt.num_seconds_from_midnight() as i32 * 1_000
+ nt.nanosecond() as i32 / 1_000_000,
)
}
}
impl Parser for Time32SecondType {
fn parse(string: &str) -> Option<Self::Native> {
string_to_time_nanoseconds(string)
.ok()
.map(|nanos| (nanos / 1_000_000_000) as i32)
.or_else(|| string.parse::<Self::Native>().ok())
}
fn parse_formatted(string: &str, format: &str) -> Option<Self::Native> {
let nt = NaiveTime::parse_from_str(string, format).ok()?;
Some(
nt.num_seconds_from_midnight() as i32
+ nt.nanosecond() as i32 / 1_000_000_000,
)
}
}
const EPOCH_DAYS_FROM_CE: i32 = 719_163;
const ERR_NANOSECONDS_NOT_SUPPORTED: &str = "The dates that can be represented as nanoseconds have to be between 1677-09-21T00:12:44.0 and 2262-04-11T23:47:16.854775804";
impl Parser for Date32Type {
fn parse(string: &str) -> Option<i32> {
let date = string.parse::<chrono::NaiveDate>().ok()?;
Some(date.num_days_from_ce() - EPOCH_DAYS_FROM_CE)
}
fn parse_formatted(string: &str, format: &str) -> Option<i32> {
let date = chrono::NaiveDate::parse_from_str(string, format).ok()?;
Some(date.num_days_from_ce() - EPOCH_DAYS_FROM_CE)
}
}
impl Parser for Date64Type {
fn parse(string: &str) -> Option<i64> {
let date_time = string.parse::<NaiveDateTime>().ok()?;
Some(date_time.timestamp_millis())
}
fn parse_formatted(string: &str, format: &str) -> Option<i64> {
use chrono::format::Fixed;
use chrono::format::StrftimeItems;
let fmt = StrftimeItems::new(format);
let has_zone = fmt.into_iter().any(|item| match item {
chrono::format::Item::Fixed(fixed_item) => matches!(
fixed_item,
Fixed::RFC2822
| Fixed::RFC3339
| Fixed::TimezoneName
| Fixed::TimezoneOffsetColon
| Fixed::TimezoneOffsetColonZ
| Fixed::TimezoneOffset
| Fixed::TimezoneOffsetZ
),
_ => false,
});
if has_zone {
let date_time = chrono::DateTime::parse_from_str(string, format).ok()?;
Some(date_time.timestamp_millis())
} else {
let date_time = NaiveDateTime::parse_from_str(string, format).ok()?;
Some(date_time.timestamp_millis())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_to_timestamp_timezone() {
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08T13:42:29.190855+00:00").unwrap()
);
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08T13:42:29.190855Z").unwrap()
);
assert_eq!(
1599572549000000000,
parse_timestamp("2020-09-08T13:42:29Z").unwrap()
); assert_eq!(
1599590549190855000,
parse_timestamp("2020-09-08T13:42:29.190855-05:00").unwrap()
);
}
#[test]
fn string_to_timestamp_timezone_space() {
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08 13:42:29.190855+00:00").unwrap()
);
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08 13:42:29.190855Z").unwrap()
);
assert_eq!(
1599572549000000000,
parse_timestamp("2020-09-08 13:42:29Z").unwrap()
); assert_eq!(
1599590549190855000,
parse_timestamp("2020-09-08 13:42:29.190855-05:00").unwrap()
);
}
#[test]
#[cfg_attr(miri, ignore)] fn string_to_timestamp_no_timezone() {
let naive_datetime = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 9, 8).unwrap(),
NaiveTime::from_hms_nano_opt(13, 42, 29, 190855000).unwrap(),
);
assert_eq!(
naive_datetime.timestamp_nanos(),
parse_timestamp("2020-09-08T13:42:29.190855").unwrap()
);
assert_eq!(
naive_datetime.timestamp_nanos(),
parse_timestamp("2020-09-08 13:42:29.190855").unwrap()
);
let naive_datetime_whole_secs = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 9, 8).unwrap(),
NaiveTime::from_hms_opt(13, 42, 29).unwrap(),
);
assert_eq!(
naive_datetime_whole_secs.timestamp_nanos(),
parse_timestamp("2020-09-08T13:42:29").unwrap()
);
assert_eq!(
naive_datetime_whole_secs.timestamp_nanos(),
parse_timestamp("2020-09-08 13:42:29").unwrap()
);
let naive_datetime_no_time = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 9, 8).unwrap(),
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
);
assert_eq!(
naive_datetime_no_time.timestamp_nanos(),
parse_timestamp("2020-09-08").unwrap()
)
}
#[test]
fn string_to_timestamp_invalid() {
expect_timestamp_parse_error("", "Error parsing '' as timestamp");
expect_timestamp_parse_error("SS", "Error parsing 'SS' as timestamp");
expect_timestamp_parse_error(
"Wed, 18 Feb 2015 23:16:09 GMT",
"Error parsing 'Wed, 18 Feb 2015 23:16:09 GMT' as timestamp",
);
}
fn parse_timestamp(s: &str) -> Result<i64, ArrowError> {
let result = string_to_timestamp_nanos(s);
if let Err(e) = &result {
eprintln!("Error parsing timestamp '{s}': {e:?}");
}
result
}
fn expect_timestamp_parse_error(s: &str, expected_err: &str) {
match string_to_timestamp_nanos(s) {
Ok(v) => panic!(
"Expected error '{expected_err}' while parsing '{s}', but parsed {v} instead"
),
Err(e) => {
assert!(e.to_string().contains(expected_err),
"Can not find expected error '{expected_err}' while parsing '{s}'. Actual error '{e}'");
}
}
}
#[test]
fn string_without_timezone_to_timestamp() {
let naive_datetime = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 9, 8).unwrap(),
NaiveTime::from_hms_nano_opt(13, 42, 29, 190855000).unwrap(),
);
assert_eq!(
naive_datetime.timestamp_nanos(),
parse_timestamp("2020-09-08T13:42:29.190855").unwrap()
);
assert_eq!(
naive_datetime.timestamp_nanos(),
parse_timestamp("2020-09-08 13:42:29.190855").unwrap()
);
let naive_datetime = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 9, 8).unwrap(),
NaiveTime::from_hms_nano_opt(13, 42, 29, 0).unwrap(),
);
assert_eq!(
naive_datetime.timestamp_nanos(),
parse_timestamp("2020-09-08T13:42:29").unwrap()
);
assert_eq!(
naive_datetime.timestamp_nanos(),
parse_timestamp("2020-09-08 13:42:29").unwrap()
);
}
#[test]
fn parse_time64_nanos() {
assert_eq!(
Time64NanosecondType::parse("02:10:01.1234567899999999"),
Some(7_801_123_456_789)
);
assert_eq!(
Time64NanosecondType::parse("02:10:01.1234567"),
Some(7_801_123_456_700)
);
assert_eq!(
Time64NanosecondType::parse("2:10:01.1234567"),
Some(7_801_123_456_700)
);
assert_eq!(
Time64NanosecondType::parse("12:10:01.123456789 AM"),
Some(601_123_456_789)
);
assert_eq!(
Time64NanosecondType::parse("12:10:01.123456789 am"),
Some(601_123_456_789)
);
assert_eq!(
Time64NanosecondType::parse("2:10:01.12345678 PM"),
Some(51_001_123_456_780)
);
assert_eq!(
Time64NanosecondType::parse("2:10:01.12345678 pm"),
Some(51_001_123_456_780)
);
assert_eq!(
Time64NanosecondType::parse("02:10:01"),
Some(7_801_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("2:10:01"),
Some(7_801_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("12:10:01 AM"),
Some(601_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("12:10:01 am"),
Some(601_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("2:10:01 PM"),
Some(51_001_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("2:10:01 pm"),
Some(51_001_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("02:10"),
Some(7_800_000_000_000)
);
assert_eq!(Time64NanosecondType::parse("2:10"), Some(7_800_000_000_000));
assert_eq!(
Time64NanosecondType::parse("12:10 AM"),
Some(600_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("12:10 am"),
Some(600_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("2:10 PM"),
Some(51_000_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse("2:10 pm"),
Some(51_000_000_000_000)
);
assert_eq!(Time64NanosecondType::parse("1"), Some(1));
assert_eq!(
Time64NanosecondType::parse("23:59:60"),
Some(86_400_000_000_000)
);
assert_eq!(
Time64NanosecondType::parse_formatted(
"02 - 10 - 01 - .1234567",
"%H - %M - %S - %.f"
),
Some(7_801_123_456_700)
);
}
#[test]
fn parse_time64_micros() {
assert_eq!(
Time64MicrosecondType::parse("02:10:01.1234"),
Some(7_801_123_400)
);
assert_eq!(
Time64MicrosecondType::parse("2:10:01.1234"),
Some(7_801_123_400)
);
assert_eq!(
Time64MicrosecondType::parse("12:10:01.123456 AM"),
Some(601_123_456)
);
assert_eq!(
Time64MicrosecondType::parse("12:10:01.123456 am"),
Some(601_123_456)
);
assert_eq!(
Time64MicrosecondType::parse("2:10:01.12345 PM"),
Some(51_001_123_450)
);
assert_eq!(
Time64MicrosecondType::parse("2:10:01.12345 pm"),
Some(51_001_123_450)
);
assert_eq!(
Time64MicrosecondType::parse("02:10:01"),
Some(7_801_000_000)
);
assert_eq!(Time64MicrosecondType::parse("2:10:01"), Some(7_801_000_000));
assert_eq!(
Time64MicrosecondType::parse("12:10:01 AM"),
Some(601_000_000)
);
assert_eq!(
Time64MicrosecondType::parse("12:10:01 am"),
Some(601_000_000)
);
assert_eq!(
Time64MicrosecondType::parse("2:10:01 PM"),
Some(51_001_000_000)
);
assert_eq!(
Time64MicrosecondType::parse("2:10:01 pm"),
Some(51_001_000_000)
);
assert_eq!(Time64MicrosecondType::parse("02:10"), Some(7_800_000_000));
assert_eq!(Time64MicrosecondType::parse("2:10"), Some(7_800_000_000));
assert_eq!(Time64MicrosecondType::parse("12:10 AM"), Some(600_000_000));
assert_eq!(Time64MicrosecondType::parse("12:10 am"), Some(600_000_000));
assert_eq!(
Time64MicrosecondType::parse("2:10 PM"),
Some(51_000_000_000)
);
assert_eq!(
Time64MicrosecondType::parse("2:10 pm"),
Some(51_000_000_000)
);
assert_eq!(Time64MicrosecondType::parse("1"), Some(1));
assert_eq!(
Time64MicrosecondType::parse("23:59:60"),
Some(86_400_000_000)
);
assert_eq!(
Time64MicrosecondType::parse_formatted(
"02 - 10 - 01 - .1234",
"%H - %M - %S - %.f"
),
Some(7_801_123_400)
);
}
#[test]
fn parse_time32_millis() {
assert_eq!(Time32MillisecondType::parse("02:10:01.1"), Some(7_801_100));
assert_eq!(Time32MillisecondType::parse("2:10:01.1"), Some(7_801_100));
assert_eq!(
Time32MillisecondType::parse("12:10:01.123 AM"),
Some(601_123)
);
assert_eq!(
Time32MillisecondType::parse("12:10:01.123 am"),
Some(601_123)
);
assert_eq!(
Time32MillisecondType::parse("2:10:01.12 PM"),
Some(51_001_120)
);
assert_eq!(
Time32MillisecondType::parse("2:10:01.12 pm"),
Some(51_001_120)
);
assert_eq!(Time32MillisecondType::parse("02:10:01"), Some(7_801_000));
assert_eq!(Time32MillisecondType::parse("2:10:01"), Some(7_801_000));
assert_eq!(Time32MillisecondType::parse("12:10:01 AM"), Some(601_000));
assert_eq!(Time32MillisecondType::parse("12:10:01 am"), Some(601_000));
assert_eq!(Time32MillisecondType::parse("2:10:01 PM"), Some(51_001_000));
assert_eq!(Time32MillisecondType::parse("2:10:01 pm"), Some(51_001_000));
assert_eq!(Time32MillisecondType::parse("02:10"), Some(7_800_000));
assert_eq!(Time32MillisecondType::parse("2:10"), Some(7_800_000));
assert_eq!(Time32MillisecondType::parse("12:10 AM"), Some(600_000));
assert_eq!(Time32MillisecondType::parse("12:10 am"), Some(600_000));
assert_eq!(Time32MillisecondType::parse("2:10 PM"), Some(51_000_000));
assert_eq!(Time32MillisecondType::parse("2:10 pm"), Some(51_000_000));
assert_eq!(Time32MillisecondType::parse("1"), Some(1));
assert_eq!(Time32MillisecondType::parse("23:59:60"), Some(86_400_000));
assert_eq!(
Time32MillisecondType::parse_formatted(
"02 - 10 - 01 - .1",
"%H - %M - %S - %.f"
),
Some(7_801_100)
);
}
#[test]
fn parse_time32_secs() {
assert_eq!(Time32SecondType::parse("02:10:01.1"), Some(7_801));
assert_eq!(Time32SecondType::parse("02:10:01"), Some(7_801));
assert_eq!(Time32SecondType::parse("2:10:01"), Some(7_801));
assert_eq!(Time32SecondType::parse("12:10:01 AM"), Some(601));
assert_eq!(Time32SecondType::parse("12:10:01 am"), Some(601));
assert_eq!(Time32SecondType::parse("2:10:01 PM"), Some(51_001));
assert_eq!(Time32SecondType::parse("2:10:01 pm"), Some(51_001));
assert_eq!(Time32SecondType::parse("02:10"), Some(7_800));
assert_eq!(Time32SecondType::parse("2:10"), Some(7_800));
assert_eq!(Time32SecondType::parse("12:10 AM"), Some(600));
assert_eq!(Time32SecondType::parse("12:10 am"), Some(600));
assert_eq!(Time32SecondType::parse("2:10 PM"), Some(51_000));
assert_eq!(Time32SecondType::parse("2:10 pm"), Some(51_000));
assert_eq!(Time32SecondType::parse("1"), Some(1));
assert_eq!(Time32SecondType::parse("23:59:60"), Some(86400));
assert_eq!(
Time32SecondType::parse_formatted("02 - 10 - 01", "%H - %M - %S"),
Some(7_801)
);
}
#[test]
fn string_to_timestamp_old() {
parse_timestamp("1677-06-14T07:29:01.256")
.map_err(|e| assert!(e.to_string().ends_with(ERR_NANOSECONDS_NOT_SUPPORTED)))
.unwrap_err();
}
}