mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-01 07:24:19 +01:00
added support for parsing timezones
This commit is contained in:
187
core/datetime.rs
187
core/datetime.rs
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user