Merge 'support modifiers for julianday()' from meteorgan

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #1321
This commit is contained in:
Jussi Saurio 2025-04-14 11:52:43 +03:00
commit d20782350d
5 changed files with 46 additions and 67 deletions

View file

@ -325,7 +325,7 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
| date() | Yes | partially supports modifiers |
| time() | Yes | partially supports modifiers |
| datetime() | Yes | partially supports modifiers |
| julianday() | Partial | does not support modifiers |
| julianday() | Yes | partially support modifiers |
| unixepoch() | Partial | does not support modifiers |
| strftime() | Yes | partially supports modifiers |
| timediff() | Yes | partially supports modifiers |

View file

@ -46,21 +46,13 @@ enum DateTimeOutput {
DateTime,
// Holds the format string
StrfTime(String),
JuliaDay,
}
fn exec_datetime(values: &[Register], output_type: DateTimeOutput) -> OwnedValue {
if values.is_empty() {
let now = parse_naive_date_time(&OwnedValue::build_text("now")).unwrap();
let formatted_str = match output_type {
DateTimeOutput::DateTime => now.format("%Y-%m-%d %H:%M:%S").to_string(),
DateTimeOutput::Time => now.format("%H:%M:%S").to_string(),
DateTimeOutput::Date => now.format("%Y-%m-%d").to_string(),
DateTimeOutput::StrfTime(ref format_str) => strftime_format(&now, format_str),
};
// Parse here
return OwnedValue::build_text(&formatted_str);
return format_dt(now, output_type, false);
}
if let Some(mut dt) = parse_naive_date_time(values[0].get_owned_value()) {
// if successful, treat subsequent entries as modifiers
@ -91,28 +83,32 @@ fn modify_dt(dt: &mut NaiveDateTime, mods: &[Register], output_type: DateTimeOut
if is_leap_second(dt) || *dt > get_max_datetime_exclusive() {
return OwnedValue::build_text("");
}
let formatted = format_dt(*dt, output_type, subsec_requested);
OwnedValue::build_text(&formatted)
format_dt(*dt, output_type, subsec_requested)
}
fn format_dt(dt: NaiveDateTime, output_type: DateTimeOutput, subsec: bool) -> String {
fn format_dt(dt: NaiveDateTime, output_type: DateTimeOutput, subsec: bool) -> OwnedValue {
match output_type {
DateTimeOutput::Date => dt.format("%Y-%m-%d").to_string(),
DateTimeOutput::Date => OwnedValue::from_text(dt.format("%Y-%m-%d").to_string().as_str()),
DateTimeOutput::Time => {
if subsec {
let t = if subsec {
dt.format("%H:%M:%S%.3f").to_string()
} else {
dt.format("%H:%M:%S").to_string()
}
};
OwnedValue::from_text(t.as_str())
}
DateTimeOutput::DateTime => {
if subsec {
let t = if subsec {
dt.format("%Y-%m-%d %H:%M:%S%.3f").to_string()
} else {
dt.format("%Y-%m-%d %H:%M:%S").to_string()
}
};
OwnedValue::from_text(t.as_str())
}
DateTimeOutput::StrfTime(format_str) => strftime_format(&dt, &format_str),
DateTimeOutput::StrfTime(format_str) => {
OwnedValue::from_text(strftime_format(&dt, &format_str).as_str())
}
DateTimeOutput::JuliaDay => OwnedValue::Float(to_julian_day_exact(&dt)),
}
}
@ -325,14 +321,8 @@ fn last_day_in_month(year: i32, month: u32) -> u32 {
28
}
pub fn exec_julianday(time_value: &OwnedValue) -> Result<String> {
let dt = parse_naive_date_time(time_value);
match dt {
// if we did something heinous like: parse::<f64>().unwrap().to_string()
// that would solve the precision issue, but dear lord...
Some(dt) => Ok(format!("{:.1$}", to_julian_day_exact(&dt), 8)),
None => Ok(String::new()),
}
pub fn exec_julianday(values: &[Register]) -> OwnedValue {
exec_datetime(values, DateTimeOutput::JuliaDay)
}
fn to_julian_day_exact(dt: &NaiveDateTime) -> f64 {

View file

@ -1159,7 +1159,7 @@ pub fn translate_expr(
});
Ok(target_register)
}
ScalarFunc::Date | ScalarFunc::DateTime => {
ScalarFunc::Date | ScalarFunc::DateTime | ScalarFunc::JulianDay => {
let start_reg = program
.alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1));
if let Some(args) = args {
@ -1259,11 +1259,11 @@ pub fn translate_expr(
});
Ok(target_register)
}
ScalarFunc::UnixEpoch | ScalarFunc::JulianDay => {
ScalarFunc::UnixEpoch => {
let mut start_reg = 0;
match args {
Some(args) if args.len() > 1 => {
crate::bail_parse_error!("epoch or julianday function with > 1 arguments. Modifiers are not yet supported.");
crate::bail_parse_error!("epoch function with > 1 arguments. Modifiers are not yet supported.");
}
Some(args) if args.len() == 1 => {
let arg_reg = program.alloc_register();

View file

@ -3341,26 +3341,8 @@ pub fn op_function(
state.registers[*dest] = Register::OwnedValue(result);
}
ScalarFunc::JulianDay => {
if *start_reg == 0 {
let julianday: String = exec_julianday(&OwnedValue::build_text("now"))?;
state.registers[*dest] =
Register::OwnedValue(OwnedValue::build_text(&julianday));
} else {
let datetime_value = &state.registers[*start_reg];
let julianday = exec_julianday(datetime_value.get_owned_value());
match julianday {
Ok(time) => {
state.registers[*dest] =
Register::OwnedValue(OwnedValue::build_text(&time))
}
Err(e) => {
return Err(LimboError::ParseError(format!(
"Error encountered while parsing datetime value: {}",
e
)));
}
}
}
let result = exec_julianday(&state.registers[*start_reg..*start_reg + arg_count]);
state.registers[*dest] = Register::OwnedValue(result);
}
ScalarFunc::UnixEpoch => {
if *start_reg == 0 {

View file

@ -423,26 +423,33 @@ do_execsql_test julianday-time-only {
SELECT julianday('15:30:45');
} {2451545.14635417}
#
# TODO: fix precision issue
#
#do_execsql_test julianday-midnight {
# SELECT julianday('2023-05-18 00:00:00');
#} {2460082.5}
do_execsql_test julianday-midnight {
SELECT julianday('2023-05-18 00:00:00');
} {2460082.5}
#do_execsql_test julianday-noon {
# SELECT julianday('2023-05-18 12:00:00');
#} {2460083.0}
do_execsql_test julianday-noon {
SELECT julianday('2023-05-18 12:00:00');
} {2460083.0}
#do_execsql_test julianday-fractional-zero {
# SELECT julianday('2023-05-18 00:00:00.000');
#} {2460082.5}
do_execsql_test julianday-fractional-zero {
SELECT julianday('2023-05-18 00:00:00.000');
} {2460082.5}
# same issue as above, we return .5000000 because we are using fmt precision
#do_execsql_test julianday-date-only {
# SELECT julianday('2023-05-18');
#} {2460082.5}
do_execsql_test julianday-date-only {
SELECT julianday('2023-05-18');
} {2460082.5}
do_execsql_test julianday-with-modifier-day {
SELECT julianday(2454832.5,'+1 day');
} {2454833.5}
do_execsql_test julianday-with-modifier-hour {
SELECT julianday(2454832.5,'-3 hours');
} {2454832.375}
do_execsql_test julianday-max-day {
SELECT julianday('9999-12-31 23:59:59');
} {5373484.49998843}