added support for parsing timezones

This commit is contained in:
gandeevanr
2024-07-23 08:49:28 -07:00
parent 7bb3412e66
commit dc01fa4a6f

View File

@@ -1,6 +1,7 @@
use crate::types::OwnedValue;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, Timelike};
use anyhow;
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
use log::trace;
use std::{error::Error, fmt::Display};
#[derive(Debug)]
@@ -99,8 +100,14 @@ pub fn get_date_from_time_value(time_value: &OwnedValue) -> anyhow::Result<Strin
return Ok(get_date_from_naive_datetime(dt.unwrap()));
} else {
match dt.unwrap_err() {
DateTimeError::InvalidArgument(_) => Ok(String::new()),
DateTimeError::Other(s) => anyhow::bail!(s),
DateTimeError::InvalidArgument(_) => {
trace!("Invalid time value: {}", time_value);
Ok(String::new())
}
DateTimeError::Other(s) => {
trace!("Other date time error: {}", s);
anyhow::bail!(s)
}
}
}
}
@@ -147,12 +154,12 @@ fn get_date_time_from_time_value_string(value: &str) -> Result<NaiveDateTime, Da
if let Ok(dt) = if format.starts_with("%H") {
// For time-only formats, assume date 2000-01-01
// Ref: https://sqlite.org/lang_datefunc.html#tmval
NaiveDateTime::parse_from_str(
parse_datetime_with_optional_tz(
&format!("2000-01-01 {}", value),
&format!("%Y-%m-%d {}", format),
)
} else {
NaiveDateTime::parse_from_str(value, format)
parse_datetime_with_optional_tz(value, format)
} {
return Ok(dt);
}
@@ -164,6 +171,26 @@ fn get_date_time_from_time_value_string(value: &str) -> Result<NaiveDateTime, Da
)));
}
fn parse_datetime_with_optional_tz(
value: &str,
format: &str,
) -> Result<NaiveDateTime, DateTimeError> {
// Try parsing with timezone
let with_tz_format = format.to_owned() + "%:z";
if let Ok(dt) = DateTime::parse_from_str(value, &with_tz_format) {
return Ok(dt.with_timezone(&Utc).naive_utc());
}
let mut value_without_tz = value;
if value.ends_with("Z") {
value_without_tz = &value[0..value.len() - 1];
}
// Parse without timezone
NaiveDateTime::parse_from_str(value_without_tz, format)
.map_err(|_| DateTimeError::InvalidArgument(format!("Invalid time value: {}", value)))
}
fn get_date_time_from_time_value_integer(value: i64) -> Result<NaiveDateTime, DateTimeError> {
i32::try_from(value).map_or_else(
|_| {
@@ -212,49 +239,85 @@ mod tests {
#[test]
fn test_valid_get_date_from_time_value() {
let test_date_str = "2024-07-21";
let now = chrono::Local::now().to_utc().format("%Y-%m-%d").to_string();
let prev_date_str = "2024-07-20";
let test_date_str = "2024-07-21";
let next_date_str = "2024-07-22";
// Test all specified formats
let test_cases = [
(
OwnedValue::Text(Rc::new("2024-07-21".to_string())),
test_date_str,
), // Format 1: YYYY-MM-DD
(
OwnedValue::Text(Rc::new("2024-07-21 14:30".to_string())),
test_date_str,
), // Format 2: YYYY-MM-DD HH:MM
(
OwnedValue::Text(Rc::new("2024-07-21 14:30:45".to_string())),
test_date_str,
), // Format 3: YYYY-MM-DD HH:MM:SS
(
OwnedValue::Text(Rc::new("2024-07-21 14:30:45.123".to_string())),
test_date_str,
), // Format 4: YYYY-MM-DD HH:MM:SS.SSS
(
OwnedValue::Text(Rc::new("2024-07-21T14:30".to_string())),
test_date_str,
), // Format 5: YYYY-MM-DDTHH:MM
(
OwnedValue::Text(Rc::new("2024-07-21T14:30:45".to_string())),
test_date_str,
), // Format 6: YYYY-MM-DDTHH:MM:SS
(
OwnedValue::Text(Rc::new("2024-07-21T14:30:45.123".to_string())),
test_date_str,
), // Format 7: YYYY-MM-DDTHH:MM:SS.SSS
(OwnedValue::Text(Rc::new("14:30".to_string())), "2000-01-01"), // Format 8: HH:MM
(
OwnedValue::Text(Rc::new("14:30:45".to_string())),
"2000-01-01",
), // Format 9: HH:MM:SS
(
OwnedValue::Text(Rc::new("14:30:45.123".to_string())),
"2000-01-01",
), // Format 10: HH:MM:SS.SSS
(OwnedValue::Float(2460512.5), test_date_str), // Format 12: DDDDDDDDDD (Julian date as float)
(OwnedValue::Integer(2460513), test_date_str), // Format 12: DDDDDDDDDD (Julian date as integer)
// Format 1: YYYY-MM-DD (no timezone applicable)
(OwnedValue::Text(Rc::new("2024-07-21".to_string())), test_date_str),
// Format 2: YYYY-MM-DD HH:MM
(OwnedValue::Text(Rc::new("2024-07-21 22:30".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30+02:00".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30-05:00".to_string())), next_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 01:30+05:00".to_string())), prev_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30Z".to_string())), test_date_str),
// Format 3: YYYY-MM-DD HH:MM:SS
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45+02:00".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45-05:00".to_string())), next_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 01:30:45+05:00".to_string())), prev_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45Z".to_string())), test_date_str),
// Format 4: YYYY-MM-DD HH:MM:SS.SSS
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45.123".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45.123+02:00".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45.123-05:00".to_string())), next_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 01:30:45.123+05:00".to_string())), prev_date_str),
(OwnedValue::Text(Rc::new("2024-07-21 22:30:45.123Z".to_string())), test_date_str),
// Format 5: YYYY-MM-DDTHH:MM
(OwnedValue::Text(Rc::new("2024-07-21T22:30".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30+02:00".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30-05:00".to_string())), next_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T01:30+05:00".to_string())), prev_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30Z".to_string())), test_date_str),
// Format 6: YYYY-MM-DDTHH:MM:SS
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45+02:00".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45-05:00".to_string())), next_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T01:30:45+05:00".to_string())), prev_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45Z".to_string())), test_date_str),
// Format 7: YYYY-MM-DDTHH:MM:SS.SSS
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45.123".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45.123+02:00".to_string())), test_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45.123-05:00".to_string())), next_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T01:30:45.123+05:00".to_string())), prev_date_str),
(OwnedValue::Text(Rc::new("2024-07-21T22:30:45.123Z".to_string())), test_date_str),
// Format 8: HH:MM
(OwnedValue::Text(Rc::new("22:30".to_string())), "2000-01-01"),
(OwnedValue::Text(Rc::new("22:30+02:00".to_string())), "2000-01-01"),
(OwnedValue::Text(Rc::new("22:30-05:00".to_string())), "2000-01-02"),
(OwnedValue::Text(Rc::new("01:30+05:00".to_string())), "1999-12-31"),
(OwnedValue::Text(Rc::new("22:30Z".to_string())), "2000-01-01"),
// Format 9: HH:MM:SS
(OwnedValue::Text(Rc::new("22:30:45".to_string())), "2000-01-01"),
(OwnedValue::Text(Rc::new("22:30:45+02:00".to_string())), "2000-01-01"),
(OwnedValue::Text(Rc::new("22:30:45-05:00".to_string())), "2000-01-02"),
(OwnedValue::Text(Rc::new("01:30:45+05:00".to_string())), "1999-12-31"),
(OwnedValue::Text(Rc::new("22:30:45Z".to_string())), "2000-01-01"),
// Format 10: HH:MM:SS.SSS
(OwnedValue::Text(Rc::new("22:30:45.123".to_string())), "2000-01-01"),
(OwnedValue::Text(Rc::new("22:30:45.123+02:00".to_string())), "2000-01-01"),
(OwnedValue::Text(Rc::new("22:30:45.123-05:00".to_string())), "2000-01-02"),
(OwnedValue::Text(Rc::new("01:30:45.123+05:00".to_string())), "1999-12-31"),
(OwnedValue::Text(Rc::new("22:30:45.123Z".to_string())), "2000-01-01"),
// Test Format 11: 'now'
(OwnedValue::Text(Rc::new("now".to_string())), &now),
// Format 12: DDDDDDDDDD (Julian date as float or integer)
(OwnedValue::Float(2460512.5), test_date_str),
(OwnedValue::Integer(2460513), test_date_str),
];
for (input, expected) in test_cases {
@@ -265,25 +328,18 @@ mod tests {
input
);
}
// Test Format 11: 'now'
let now = chrono::Local::now().to_utc().format("%Y-%m-%d").to_string();
assert_eq!(
get_date_from_time_value(&OwnedValue::Text(Rc::new("now".to_string()))).unwrap(),
now
);
}
#[test]
fn test_invalid_get_date_from_time_value() {
let invalid_cases = [
OwnedValue::Text(Rc::new("2024-07-21 25:00".to_string())), // Invalid hour
OwnedValue::Text(Rc::new("2024-07-21 24:00:00".to_string())), // Invalid hour
OwnedValue::Text(Rc::new("2024-07-21 23:60:00".to_string())), // Invalid minute
OwnedValue::Text(Rc::new("2024-07-21 22:58:60".to_string())), // Invalid second
OwnedValue::Text(Rc::new("2024-07-32".to_string())), // Invalid day
OwnedValue::Text(Rc::new("2024-13-01".to_string())), // Invalid month
OwnedValue::Text(Rc::new("invalid_date".to_string())), // Completely invalid string
OwnedValue::Text(Rc::new("2024-07-21 25:00".to_string())), // Invalid hour
OwnedValue::Text(Rc::new("2024-07-21 24:00:00".to_string())), // Invalid hour
OwnedValue::Text(Rc::new("2024-07-21 23:60:00".to_string())), // Invalid minute
OwnedValue::Text(Rc::new("2024-07-21 22:58:60".to_string())), // Invalid second
OwnedValue::Text(Rc::new("2024-07-32".to_string())), // Invalid day
OwnedValue::Text(Rc::new("2024-13-01".to_string())), // Invalid month
OwnedValue::Text(Rc::new("invalid_date".to_string())), // Completely invalid string
OwnedValue::Text(Rc::new("".to_string())), // Empty string
OwnedValue::Integer(i64::MAX), // Large Julian day
OwnedValue::Integer(-1), // Negative Julian day
@@ -293,6 +349,17 @@ mod tests {
OwnedValue::Float(f64::INFINITY), // Infinity
OwnedValue::Null, // Null value
OwnedValue::Blob(vec![1, 2, 3].into()), // Blob (unsupported type)
// Invalid timezone tests
OwnedValue::Text(Rc::new("2024-07-21T12:00:00+24:00".to_string())), // Invalid timezone offset (too large)
OwnedValue::Text(Rc::new("2024-07-21T12:00:00-24:00".to_string())), // Invalid timezone offset (too small)
OwnedValue::Text(Rc::new("2024-07-21T12:00:00+00:60".to_string())), // Invalid timezone minutes
OwnedValue::Text(Rc::new("2024-07-21T12:00:00+00:00:00".to_string())), // Invalid timezone format (extra seconds)
OwnedValue::Text(Rc::new("2024-07-21T12:00:00+".to_string())), // Incomplete timezone
OwnedValue::Text(Rc::new("2024-07-21T12:00:00+Z".to_string())), // Invalid timezone format
OwnedValue::Text(Rc::new("2024-07-21T12:00:00+00:00Z".to_string())), // Mixing offset and Z
OwnedValue::Text(Rc::new("2024-07-21T12:00:00UTC".to_string())), // Named timezone (not supported)
];
for case in invalid_cases.iter() {