use crate::error::{ArrowError, Result};
use chrono::{prelude::*, LocalResult};
#[inline]
pub fn string_to_timestamp_nanos(s: &str) -> Result<i64> {
if let Ok(ts) = DateTime::parse_from_rfc3339(s) {
return Ok(ts.timestamp_nanos());
}
if let Ok(ts) = DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f%:z") {
return Ok(ts.timestamp_nanos());
}
if let Ok(ts) = Utc.datetime_from_str(s, "%Y-%m-%d %H:%M:%S%.fZ") {
return Ok(ts.timestamp_nanos());
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
return naive_datetime_to_timestamp(s, ts);
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
return naive_datetime_to_timestamp(s, ts);
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") {
return naive_datetime_to_timestamp(s, ts);
}
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
return naive_datetime_to_timestamp(s, ts);
}
Err(ArrowError::CastError(format!(
"Error parsing '{}' as timestamp",
s
)))
}
fn naive_datetime_to_timestamp(s: &str, datetime: NaiveDateTime) -> Result<i64> {
let l = Local {};
match l.from_local_datetime(&datetime) {
LocalResult::None => Err(ArrowError::CastError(format!(
"Error parsing '{}' as timestamp: local time representation is invalid",
s
))),
LocalResult::Single(local_datetime) => {
Ok(local_datetime.with_timezone(&Utc).timestamp_nanos())
}
LocalResult::Ambiguous(local_datetime, _) => {
Ok(local_datetime.with_timezone(&Utc).timestamp_nanos())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_to_timestamp_timezone() -> Result<()> {
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08T13:42:29.190855+00:00")?
);
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08T13:42:29.190855Z")?
);
assert_eq!(
1599572549000000000,
parse_timestamp("2020-09-08T13:42:29Z")?
); assert_eq!(
1599590549190855000,
parse_timestamp("2020-09-08T13:42:29.190855-05:00")?
);
Ok(())
}
#[test]
fn string_to_timestamp_timezone_space() -> Result<()> {
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08 13:42:29.190855+00:00")?
);
assert_eq!(
1599572549190855000,
parse_timestamp("2020-09-08 13:42:29.190855Z")?
);
assert_eq!(
1599572549000000000,
parse_timestamp("2020-09-08 13:42:29Z")?
); assert_eq!(
1599590549190855000,
parse_timestamp("2020-09-08 13:42:29.190855-05:00")?
);
Ok(())
}
fn naive_datetime_to_timestamp(naive_datetime: &NaiveDateTime) -> i64 {
let utc_offset_secs = match Local.offset_from_local_datetime(naive_datetime) {
LocalResult::Single(local_offset) => {
local_offset.fix().local_minus_utc() as i64
}
_ => panic!("Unexpected failure converting to local datetime"),
};
let utc_offset_nanos = utc_offset_secs * 1_000_000_000;
naive_datetime.timestamp_nanos() - utc_offset_nanos
}
#[test]
#[cfg_attr(miri, ignore)] fn string_to_timestamp_no_timezone() -> Result<()> {
let naive_datetime = NaiveDateTime::new(
NaiveDate::from_ymd(2020, 9, 8),
NaiveTime::from_hms_nano(13, 42, 29, 190855000),
);
assert_eq!(
naive_datetime_to_timestamp(&naive_datetime),
parse_timestamp("2020-09-08T13:42:29.190855")?
);
assert_eq!(
naive_datetime_to_timestamp(&naive_datetime),
parse_timestamp("2020-09-08 13:42:29.190855")?
);
let naive_datetime_whole_secs = NaiveDateTime::new(
NaiveDate::from_ymd(2020, 9, 8),
NaiveTime::from_hms(13, 42, 29),
);
assert_eq!(
naive_datetime_to_timestamp(&naive_datetime_whole_secs),
parse_timestamp("2020-09-08T13:42:29")?
);
assert_eq!(
naive_datetime_to_timestamp(&naive_datetime_whole_secs),
parse_timestamp("2020-09-08 13:42:29")?
);
Ok(())
}
#[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> {
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 '{}' while parsing '{}', but parsed {} instead",
expected_err, s, v
),
Err(e) => {
assert!(e.to_string().contains(expected_err),
"Can not find expected error '{}' while parsing '{}'. Actual error '{}'",
expected_err, s, e);
}
}
}
}