use crate::error::{QuickDbError, QuickDbResult};
use crate::model::conversion::ToDataValue;
use crate::types::DataValue;
pub fn convert_string_to_datetime_with_tz<T: std::fmt::Debug + ToDataValue>(
value: &T,
timezone_offset: &str,
) -> QuickDbResult<DataValue> {
let data_value = value.to_data_value();
match data_value {
DataValue::DateTime(dt) => {
apply_timezone_to_datetime(dt, timezone_offset)
}
DataValue::String(s) => {
parse_string_to_datetime_with_tz(&s, timezone_offset)
}
DataValue::Null => {
Ok(DataValue::Null)
}
_ => {
Err(QuickDbError::ValidationError {
field: "DateTimeWithTz字段".to_string(),
message: format!(
"不支持的数据类型 {:?},期望DateTime<Utc>或RFC3339格式的字符串",
std::any::type_name_of_val(value)
),
})
}
}
}
fn apply_timezone_to_datetime(
utc_dt: chrono::DateTime<chrono::FixedOffset>,
timezone_offset: &str,
) -> QuickDbResult<DataValue> {
let tz_offset = parse_timezone_offset(timezone_offset)?;
let target_offset_seconds = tz_offset;
let target_tz = chrono::FixedOffset::east(target_offset_seconds);
let target_dt = utc_dt.with_timezone(&target_tz);
Ok(DataValue::DateTime(target_dt))
}
fn parse_string_to_datetime_with_tz(
datetime_str: &str,
timezone_offset: &str,
) -> QuickDbResult<DataValue> {
if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(datetime_str) {
return Ok(DataValue::DateTime(
dt.with_timezone(&chrono::FixedOffset::east(0)),
));
}
let local_datetime_formats = [
"%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S%.3f", "%Y-%m-%d %H:%M", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%.3f", "%Y-%m-%dT%H:%M", "%Y/%m/%d %H:%M:%S", "%Y/%m/%d %H:%M:%S%.3f", "%Y/%m/%d %H:%M", ];
for format in &local_datetime_formats {
if let Ok(naive_dt) = chrono::NaiveDateTime::parse_from_str(datetime_str, format) {
let tz_offset = parse_timezone_offset(timezone_offset)?;
if let Some(offset) = chrono::FixedOffset::west_opt(tz_offset) {
let aware_dt = naive_dt
.and_local_timezone(offset)
.single()
.ok_or_else(|| QuickDbError::ValidationError {
field: "DateTimeWithTz字段".to_string(),
message: format!(
"时间 '{}' 在时区 '{}' 下存在歧义(夏令时等)",
datetime_str, timezone_offset
),
})?;
return Ok(DataValue::DateTime(
aware_dt.with_timezone(&chrono::FixedOffset::east(0)),
));
} else {
return Err(QuickDbError::ValidationError {
field: "DateTimeWithTz字段".to_string(),
message: format!("无效的时区偏移: {}", timezone_offset),
});
}
}
}
Err(QuickDbError::ValidationError {
field: "DateTimeWithTz字段".to_string(),
message: format!(
"无法解析日期时间字符串 '{}'。支持的格式:\n\
1. RFC3339格式(推荐):2024-01-15T14:30:00+08:00\n\
2. 本地时间格式:2024-01-15 14:30:00\n\
3. 其他常见格式:2024-01-15T14:30:00、2024/01/15 14:30:00等",
datetime_str
),
})
}
pub fn parse_timezone_offset(timezone_offset: &str) -> QuickDbResult<i32> {
static TZ_REGEX: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
let regex = TZ_REGEX.get_or_init(|| regex::Regex::new(r"^([+-])(\d{2}):(\d{2})$").unwrap());
if !regex.is_match(timezone_offset) {
return Err(QuickDbError::ValidationError {
field: "时区偏移".to_string(),
message: format!(
"无效的时区偏移格式: '{}'。期望格式: [+/-]HH:MM,例如: +08:00、-05:30",
timezone_offset
),
});
}
let caps = regex.captures(timezone_offset).unwrap();
let sign = caps.get(1).unwrap().as_str();
let hours: i32 =
caps.get(2)
.unwrap()
.as_str()
.parse()
.map_err(|_| QuickDbError::ValidationError {
field: "时区偏移".to_string(),
message: format!("无效的小时数: {}", caps.get(2).unwrap().as_str()),
})?;
let minutes: i32 =
caps.get(3)
.unwrap()
.as_str()
.parse()
.map_err(|_| QuickDbError::ValidationError {
field: "时区偏移".to_string(),
message: format!("无效的分钟数: {}", caps.get(3).unwrap().as_str()),
})?;
if hours > 23 || minutes > 59 {
return Err(QuickDbError::ValidationError {
field: "时区偏移".to_string(),
message: format!(
"时区偏移超出范围: {}{}:{}.小时范围: 0-23,分钟范围: 0-59",
sign, hours, minutes
),
});
}
let total_seconds = hours * 3600 + minutes * 60;
match sign {
"+" => Ok(-total_seconds), "-" => Ok(total_seconds), _ => unreachable!(), }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_timezone_offset() {
assert_eq!(parse_timezone_offset("+08:00").unwrap(), -8 * 3600);
assert_eq!(parse_timezone_offset("-05:00").unwrap(), 5 * 3600);
assert_eq!(
parse_timezone_offset("+05:30").unwrap(),
-(5 * 3600 + 30 * 60)
);
assert_eq!(parse_timezone_offset("-09:30").unwrap(), 9 * 3600 + 30 * 60);
assert!(parse_timezone_offset("08:00").is_err()); assert!(parse_timezone_offset("+8:00").is_err()); assert!(parse_timezone_offset("+08:0").is_err()); assert!(parse_timezone_offset("+24:00").is_err()); assert!(parse_timezone_offset("+08:60").is_err()); }
}