mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-04 17:04:18 +01:00
Merge 'core: add datetime modifiers helpers.' from Sonny
Adding the foundational helpers for integrating with supporting datetime Functions modifiers later.
part of https://github.com/penberg/limbo/issues/158.
Integrating with date time functions in `vdbe` `Program.step` will come in other PRs later.
reference to sqlite eaa560f3fc/src/date.c (L723)
Closes #277
This commit is contained in:
@@ -34,6 +34,8 @@ pub enum LimboError {
|
||||
InvalidDate(String),
|
||||
#[error("Parse error: {0}")]
|
||||
InvalidTime(String),
|
||||
#[error("Modifier parsing error: {0}")]
|
||||
InvalidModifier(String),
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use chrono::{
|
||||
DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, TimeZone, Timelike, Utc,
|
||||
};
|
||||
|
||||
use crate::types::OwnedValue;
|
||||
use crate::LimboError::InvalidModifier;
|
||||
use crate::Result;
|
||||
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
|
||||
|
||||
/// Implementation of the date() SQL function.
|
||||
pub fn exec_date(time_value: &OwnedValue) -> Result<String> {
|
||||
@@ -20,6 +24,58 @@ pub fn exec_time(time_value: &OwnedValue) -> Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modifier(dt: &mut NaiveDateTime, modifier: &str) -> Result<()> {
|
||||
let parsed_modifier = parse_modifier(modifier)?;
|
||||
|
||||
match parsed_modifier {
|
||||
Modifier::Days(days) => *dt += TimeDelta::days(days),
|
||||
Modifier::Hours(hours) => *dt += TimeDelta::hours(hours),
|
||||
Modifier::Minutes(minutes) => *dt += TimeDelta::minutes(minutes),
|
||||
Modifier::Seconds(seconds) => *dt += TimeDelta::seconds(seconds),
|
||||
Modifier::Months(months) => todo!(),
|
||||
Modifier::Years(years) => todo!(),
|
||||
Modifier::TimeOffset(offset) => *dt += offset,
|
||||
Modifier::DateOffset {
|
||||
years,
|
||||
months,
|
||||
days,
|
||||
} => {
|
||||
*dt = dt
|
||||
.checked_add_months(chrono::Months::new((years * 12 + months) as u32))
|
||||
.ok_or_else(|| InvalidModifier("Invalid date offset".to_string()))?;
|
||||
*dt += TimeDelta::days(days as i64);
|
||||
}
|
||||
Modifier::DateTimeOffset { date, time } => todo!(),
|
||||
Modifier::Ceiling => todo!(),
|
||||
Modifier::Floor => todo!(),
|
||||
Modifier::StartOfMonth => todo!(),
|
||||
Modifier::StartOfYear => {
|
||||
*dt = NaiveDate::from_ymd_opt(dt.year(), 1, 1)
|
||||
.unwrap()
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap();
|
||||
}
|
||||
Modifier::StartOfDay => {
|
||||
*dt = dt.date().and_hms_opt(0, 0, 0).unwrap();
|
||||
}
|
||||
Modifier::Weekday(day) => todo!(),
|
||||
Modifier::UnixEpoch => todo!(),
|
||||
Modifier::JulianDay => todo!(),
|
||||
Modifier::Auto => todo!(),
|
||||
Modifier::Localtime => {
|
||||
let utc_dt = DateTime::<Utc>::from_utc(*dt, Utc);
|
||||
*dt = utc_dt.with_timezone(&chrono::Local).naive_local();
|
||||
}
|
||||
Modifier::Utc => {
|
||||
let local_dt = chrono::Local.from_local_datetime(dt).unwrap();
|
||||
*dt = local_dt.with_timezone(&Utc).naive_utc();
|
||||
}
|
||||
Modifier::Subsec => todo!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_naive_date_time(time_value: &OwnedValue) -> Option<NaiveDateTime> {
|
||||
match time_value {
|
||||
OwnedValue::Text(s) => get_date_time_from_time_value_string(s),
|
||||
@@ -92,7 +148,7 @@ fn parse_datetime_with_optional_tz(value: &str, format: &str) -> Option<NaiveDat
|
||||
}
|
||||
|
||||
let mut value_without_tz = value;
|
||||
if value.ends_with("Z") {
|
||||
if value.ends_with('Z') {
|
||||
value_without_tz = &value[0..value.len() - 1];
|
||||
}
|
||||
|
||||
@@ -151,11 +207,156 @@ fn get_max_datetime_exclusive() -> NaiveDateTime {
|
||||
)
|
||||
}
|
||||
|
||||
/// Modifier doc https://www.sqlite.org/lang_datefunc.html#modifiers
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Modifier {
|
||||
Days(i64),
|
||||
Hours(i64),
|
||||
Minutes(i64),
|
||||
Seconds(i64),
|
||||
Months(i32),
|
||||
Years(i32),
|
||||
TimeOffset(TimeDelta),
|
||||
DateOffset {
|
||||
years: i32,
|
||||
months: i32,
|
||||
days: i32,
|
||||
},
|
||||
DateTimeOffset {
|
||||
date: NaiveDate,
|
||||
time: Option<NaiveTime>,
|
||||
},
|
||||
Ceiling,
|
||||
Floor,
|
||||
StartOfMonth,
|
||||
StartOfYear,
|
||||
StartOfDay,
|
||||
Weekday(u32),
|
||||
UnixEpoch,
|
||||
JulianDay,
|
||||
Auto,
|
||||
Localtime,
|
||||
Utc,
|
||||
Subsec,
|
||||
}
|
||||
|
||||
fn parse_modifier_number(s: &str) -> Result<i64> {
|
||||
s.trim()
|
||||
.parse::<i64>()
|
||||
.map_err(|_| InvalidModifier(format!("Invalid number: {}", s)))
|
||||
}
|
||||
|
||||
/// supports YYYY-MM-DD format for time shift modifiers
|
||||
fn parse_modifier_date(s: &str) -> Result<NaiveDate> {
|
||||
NaiveDate::parse_from_str(s, "%Y-%m-%d")
|
||||
.map_err(|_| InvalidModifier("Invalid date format".to_string()))
|
||||
}
|
||||
|
||||
/// supports following formats for time shift modifiers
|
||||
/// - HH:MM
|
||||
/// - HH:MM:SS
|
||||
/// - HH:MM:SS.SSS
|
||||
fn parse_modifier_time(s: &str) -> Result<NaiveTime> {
|
||||
match s.len() {
|
||||
5 => NaiveTime::parse_from_str(s, "%H:%M"),
|
||||
8 => NaiveTime::parse_from_str(s, "%H:%M:%S"),
|
||||
12 => NaiveTime::parse_from_str(s, "%H:%M:%S.%3f"),
|
||||
_ => return Err(InvalidModifier(format!("Invalid time format: {}", s))),
|
||||
}
|
||||
.map_err(|_| InvalidModifier(format!("Invalid time format: {}", s)))
|
||||
}
|
||||
|
||||
fn parse_modifier(modifier: &str) -> Result<Modifier> {
|
||||
let modifier = modifier.trim().to_lowercase();
|
||||
|
||||
match modifier.as_str() {
|
||||
s if s.ends_with(" days") => Ok(Modifier::Days(parse_modifier_number(&s[..s.len() - 5])?)),
|
||||
s if s.ends_with(" hours") => {
|
||||
Ok(Modifier::Hours(parse_modifier_number(&s[..s.len() - 6])?))
|
||||
}
|
||||
s if s.ends_with(" minutes") => {
|
||||
Ok(Modifier::Minutes(parse_modifier_number(&s[..s.len() - 8])?))
|
||||
}
|
||||
s if s.ends_with(" seconds") => {
|
||||
Ok(Modifier::Seconds(parse_modifier_number(&s[..s.len() - 8])?))
|
||||
}
|
||||
s if s.ends_with(" months") => Ok(Modifier::Months(
|
||||
parse_modifier_number(&s[..s.len() - 7])? as i32,
|
||||
)),
|
||||
s if s.ends_with(" years") => Ok(Modifier::Years(
|
||||
parse_modifier_number(&s[..s.len() - 6])? as i32,
|
||||
)),
|
||||
s if s.starts_with('+') || s.starts_with('-') => {
|
||||
// Parse as DateOffset or DateTimeOffset
|
||||
let parts: Vec<&str> = s[1..].split(' ').collect();
|
||||
match parts.len() {
|
||||
1 => {
|
||||
// first part can be either date ±YYYY-MM-DD or 3 types of time modifiers
|
||||
let date = parse_modifier_date(parts[0]);
|
||||
if date.is_ok() {
|
||||
Ok(Modifier::DateTimeOffset {
|
||||
date: date.unwrap(),
|
||||
time: None,
|
||||
})
|
||||
} else {
|
||||
// try to parse time if error parsing date
|
||||
let time = parse_modifier_time(parts[0])?;
|
||||
// TODO handle nanoseconds
|
||||
let time_delta;
|
||||
if s.starts_with('-') {
|
||||
time_delta =
|
||||
TimeDelta::seconds(-(time.num_seconds_from_midnight() as i64));
|
||||
} else {
|
||||
time_delta =
|
||||
TimeDelta::seconds(time.num_seconds_from_midnight() as i64);
|
||||
}
|
||||
Ok(Modifier::TimeOffset(time_delta))
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let date = parse_modifier_date(parts[0])?;
|
||||
let time = parse_modifier_time(parts[1])?;
|
||||
Ok(Modifier::DateTimeOffset {
|
||||
date,
|
||||
time: Some(time),
|
||||
})
|
||||
}
|
||||
_ => Err(InvalidModifier(
|
||||
"Invalid date/time offset format".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
"ceiling" => Ok(Modifier::Ceiling),
|
||||
"floor" => Ok(Modifier::Floor),
|
||||
"start of month" => Ok(Modifier::StartOfMonth),
|
||||
"start of year" => Ok(Modifier::StartOfYear),
|
||||
"start of day" => Ok(Modifier::StartOfDay),
|
||||
s if s.starts_with("weekday ") => {
|
||||
let day = parse_modifier_number(&s[8..])?;
|
||||
if !(0..=6).contains(&day) {
|
||||
Err(InvalidModifier(
|
||||
"Weekday must be between 0 and 6".to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(Modifier::Weekday(day as u32))
|
||||
}
|
||||
}
|
||||
"unixepoch" => Ok(Modifier::UnixEpoch),
|
||||
"julianday" => Ok(Modifier::JulianDay),
|
||||
"auto" => Ok(Modifier::Auto),
|
||||
"localtime" => Ok(Modifier::Localtime),
|
||||
"utc" => Ok(Modifier::Utc),
|
||||
"subsec" | "subsecond" => Ok(Modifier::Subsec),
|
||||
_ => Err(InvalidModifier(format!("Unknown modifier: {}", modifier))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_valid_get_date_from_time_value() {
|
||||
let now = chrono::Local::now().to_utc().format("%Y-%m-%d").to_string();
|
||||
@@ -646,4 +847,293 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_days() {
|
||||
assert_eq!(parse_modifier("5 days").unwrap(), Modifier::Days(5));
|
||||
assert_eq!(parse_modifier("-3 days").unwrap(), Modifier::Days(-3));
|
||||
assert_eq!(parse_modifier("+2 days").unwrap(), Modifier::Days(2));
|
||||
assert_eq!(parse_modifier("4 days").unwrap(), Modifier::Days(4));
|
||||
assert_eq!(parse_modifier("6 DAYS").unwrap(), Modifier::Days(6));
|
||||
assert_eq!(parse_modifier("+5 DAYS").unwrap(), Modifier::Days(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_hours() {
|
||||
assert_eq!(parse_modifier("12 hours").unwrap(), Modifier::Hours(12));
|
||||
assert_eq!(parse_modifier("-2 hours").unwrap(), Modifier::Hours(-2));
|
||||
assert_eq!(parse_modifier("+3 HOURS").unwrap(), Modifier::Hours(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_minutes() {
|
||||
assert_eq!(parse_modifier("30 minutes").unwrap(), Modifier::Minutes(30));
|
||||
assert_eq!(
|
||||
parse_modifier("-15 minutes").unwrap(),
|
||||
Modifier::Minutes(-15)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("+45 MINUTES").unwrap(),
|
||||
Modifier::Minutes(45)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_seconds() {
|
||||
assert_eq!(parse_modifier("45 seconds").unwrap(), Modifier::Seconds(45));
|
||||
assert_eq!(
|
||||
parse_modifier("-10 seconds").unwrap(),
|
||||
Modifier::Seconds(-10)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("+20 SECONDS").unwrap(),
|
||||
Modifier::Seconds(20)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_months() {
|
||||
assert_eq!(parse_modifier("3 months").unwrap(), Modifier::Months(3));
|
||||
assert_eq!(parse_modifier("-1 months").unwrap(), Modifier::Months(-1));
|
||||
assert_eq!(parse_modifier("+6 MONTHS").unwrap(), Modifier::Months(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_years() {
|
||||
assert_eq!(parse_modifier("2 years").unwrap(), Modifier::Years(2));
|
||||
assert_eq!(parse_modifier("-1 years").unwrap(), Modifier::Years(-1));
|
||||
assert_eq!(parse_modifier("+10 YEARS").unwrap(), Modifier::Years(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_time_offset() {
|
||||
assert_eq!(
|
||||
parse_modifier("+01:30").unwrap(),
|
||||
Modifier::TimeOffset(TimeDelta::hours(1) + TimeDelta::minutes(30))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("-00:45").unwrap(),
|
||||
Modifier::TimeOffset(TimeDelta::minutes(-45))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("+02:15:30").unwrap(),
|
||||
Modifier::TimeOffset(
|
||||
TimeDelta::hours(2) + TimeDelta::minutes(15) + TimeDelta::seconds(30)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("+02:15:30.250").unwrap(),
|
||||
Modifier::TimeOffset(
|
||||
TimeDelta::hours(2) + TimeDelta::minutes(15) + TimeDelta::seconds(30)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_date_offset() {
|
||||
let expected_date = NaiveDate::from_ymd_opt(2023, 5, 15).unwrap();
|
||||
assert_eq!(
|
||||
parse_modifier("+2023-05-15").unwrap(),
|
||||
Modifier::DateTimeOffset {
|
||||
date: expected_date,
|
||||
time: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("-2023-05-15").unwrap(),
|
||||
Modifier::DateTimeOffset {
|
||||
date: expected_date,
|
||||
time: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_date_time_offset() {
|
||||
let expected_date = NaiveDate::from_ymd_opt(2023, 5, 15).unwrap();
|
||||
let expected_time = NaiveTime::from_hms_opt(14, 30, 0).unwrap();
|
||||
assert_eq!(
|
||||
parse_modifier("+2023-05-15 14:30").unwrap(),
|
||||
Modifier::DateTimeOffset {
|
||||
date: expected_date,
|
||||
time: Some(expected_time),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("-2023-05-15 14:30").unwrap(),
|
||||
Modifier::DateTimeOffset {
|
||||
date: expected_date,
|
||||
time: Some(expected_time),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_start_of() {
|
||||
assert_eq!(
|
||||
parse_modifier("start of month").unwrap(),
|
||||
Modifier::StartOfMonth
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("START OF MONTH").unwrap(),
|
||||
Modifier::StartOfMonth
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("start of year").unwrap(),
|
||||
Modifier::StartOfYear
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("START OF YEAR").unwrap(),
|
||||
Modifier::StartOfYear
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("start of day").unwrap(),
|
||||
Modifier::StartOfDay
|
||||
);
|
||||
assert_eq!(
|
||||
parse_modifier("START OF DAY").unwrap(),
|
||||
Modifier::StartOfDay
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_weekday() {
|
||||
assert_eq!(parse_modifier("weekday 0").unwrap(), Modifier::Weekday(0));
|
||||
assert_eq!(parse_modifier("WEEKDAY 6").unwrap(), Modifier::Weekday(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_other_modifiers() {
|
||||
assert_eq!(parse_modifier("unixepoch").unwrap(), Modifier::UnixEpoch);
|
||||
assert_eq!(parse_modifier("UNIXEPOCH").unwrap(), Modifier::UnixEpoch);
|
||||
assert_eq!(parse_modifier("julianday").unwrap(), Modifier::JulianDay);
|
||||
assert_eq!(parse_modifier("JULIANDAY").unwrap(), Modifier::JulianDay);
|
||||
assert_eq!(parse_modifier("auto").unwrap(), Modifier::Auto);
|
||||
assert_eq!(parse_modifier("AUTO").unwrap(), Modifier::Auto);
|
||||
assert_eq!(parse_modifier("localtime").unwrap(), Modifier::Localtime);
|
||||
assert_eq!(parse_modifier("LOCALTIME").unwrap(), Modifier::Localtime);
|
||||
assert_eq!(parse_modifier("utc").unwrap(), Modifier::Utc);
|
||||
assert_eq!(parse_modifier("UTC").unwrap(), Modifier::Utc);
|
||||
assert_eq!(parse_modifier("subsec").unwrap(), Modifier::Subsec);
|
||||
assert_eq!(parse_modifier("SUBSEC").unwrap(), Modifier::Subsec);
|
||||
assert_eq!(parse_modifier("subsecond").unwrap(), Modifier::Subsec);
|
||||
assert_eq!(parse_modifier("SUBSECOND").unwrap(), Modifier::Subsec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_modifier() {
|
||||
assert!(parse_modifier("invalid modifier").is_err());
|
||||
assert!(parse_modifier("5").is_err());
|
||||
assert!(parse_modifier("days").is_err());
|
||||
assert!(parse_modifier("++5 days").is_err());
|
||||
assert!(parse_modifier("weekday 7").is_err());
|
||||
}
|
||||
|
||||
fn create_datetime(
|
||||
year: i32,
|
||||
month: u32,
|
||||
day: u32,
|
||||
hour: u32,
|
||||
min: u32,
|
||||
sec: u32,
|
||||
) -> NaiveDateTime {
|
||||
NaiveDate::from_ymd_opt(year, month, day)
|
||||
.unwrap()
|
||||
.and_hms_opt(hour, min, sec)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn setup_datetime() -> NaiveDateTime {
|
||||
create_datetime(2023, 6, 15, 12, 30, 45)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifier_days() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "5 days").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 20, 12, 30, 45));
|
||||
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "-3 days").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 12, 12, 30, 45));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifier_hours() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "6 hours").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 18, 30, 45));
|
||||
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "-2 hours").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 10, 30, 45));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifier_minutes() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "45 minutes").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 13, 15, 45));
|
||||
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "-15 minutes").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 12, 15, 45));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifier_seconds() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "30 seconds").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 12, 31, 15));
|
||||
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "-20 seconds").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 12, 30, 25));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifier_time_offset() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "+01:30").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 14, 0, 45));
|
||||
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "-00:45").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 11, 45, 45));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // enable when implemented this modifier
|
||||
fn test_apply_modifier_date_time_offset() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "+01-01-01 01:01").unwrap();
|
||||
assert_eq!(dt, create_datetime(2024, 7, 16, 13, 31, 45));
|
||||
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "-01-01-01 01:01").unwrap();
|
||||
assert_eq!(dt, create_datetime(2022, 5, 14, 11, 29, 45));
|
||||
|
||||
// Test with larger offsets
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "+02-03-04 05:06").unwrap();
|
||||
assert_eq!(dt, create_datetime(2025, 9, 19, 17, 36, 45));
|
||||
|
||||
dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "-02-03-04 05:06").unwrap();
|
||||
assert_eq!(dt, create_datetime(2021, 3, 11, 7, 24, 45));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifier_start_of_year() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "start of year").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 1, 1, 0, 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifier_start_of_day() {
|
||||
let mut dt = setup_datetime();
|
||||
apply_modifier(&mut dt, "start of day").unwrap();
|
||||
assert_eq!(dt, create_datetime(2023, 6, 15, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user