diff --git a/src/query/functions/src/scalars/datetime.rs b/src/query/functions/src/scalars/datetime.rs index 8216c74e619d..e5f8d727bc92 100644 --- a/src/query/functions/src/scalars/datetime.rs +++ b/src/query/functions/src/scalars/datetime.rs @@ -60,6 +60,7 @@ use databend_common_expression::vectorize_1_arg; use databend_common_expression::vectorize_2_arg; use databend_common_expression::vectorize_with_builder_1_arg; use databend_common_expression::vectorize_with_builder_2_arg; +use databend_common_expression::vectorize_with_builder_3_arg; use databend_common_expression::EvalContext; use databend_common_expression::FunctionDomain; use databend_common_expression::FunctionProperty; @@ -108,6 +109,9 @@ pub fn register(registry: &mut FunctionRegistry) { // [date | timestamp] +/- number register_timestamp_add_sub(registry); + + // convert_timezone( target_timezone, date, src_timezone) + register_convert_timezone(registry); } /// Check if timestamp is within range, and return the timestamp in micros. @@ -131,6 +135,109 @@ fn int64_domain_to_timestamp_domain>( }) } +fn register_convert_timezone(registry: &mut FunctionRegistry) { + // 2 arguments function [target_timezone, src_timestamp] + registry.register_passthrough_nullable_2_arg::( + "convert_timezone", + |_, _, _| FunctionDomain::MayThrow, + eval_convert_timezone, + ); + + // 3 arguments function [src_timezone, target_timezone, src_timestamp] + registry.register_passthrough_nullable_3_arg::( + "convert_timezone", + |_, _, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_3_arg::( + |src_tz, target_tz, src_timestamp, output, ctx| { + // Parsing parameters + let t_tz: Tz = match target_tz.parse() { + Ok(tz) => tz, + Err(e) => { + return ctx.set_error( + output.len(), + format!("cannot parse target `timezone`. {}", e), + ); + } + }; + let s_tz: Tz = match src_tz.parse() { + Ok(tz) => tz, + Err(e) => { + return ctx.set_error( + output.len(), + format!("cannot parse src `timezone`. {}", e), + ); + } + }; + + let p_src_timestamp: i64 = match Some(src_timestamp){ + Ok(timestamp) => { + timestamp.Utc.timestamp_opt(src_timestamp, 0).unwrap(); + }, + Err(e) => { + return ctx.set_error( + output.len(), + format!("cannot parse target `timezone`. {}", e), + ); + } + }; + + + // Create dummy timezone + let utc_now: DateTime = Utc::now(); + + let src_time = utc_now.with_timezone(&s_tz); + let target_time = utc_now.with_timezone(&t_tz); + + // Calculate the difference in seconds + let delta = target_time.signed_duration_since(src_time); + let result_timestamp = p_src_timestamp + delta.num_seconds(); + + output.push(result_timestamp) + }, + ), + ); + + fn eval_convert_timezone( + target_tz: ValueRef, + src_timestamp: ValueRef, + ctx: &mut EvalContext, + ) -> Value { + vectorize_with_builder_2_arg::( + |target_tz, src_timestamp, output, ctx| { + // Parse the target timezone + let t_tz: Tz = match target_tz.parse() { + Ok(tz) => tz, + Err(e) => { + return ctx.set_error( + output.len(), + format!("cannot parse target `timezone`. {}", e), + ); + } + }; + + // Assume the source timestamp is in UTC + match Some(src_timestamp) { + Ok(src_timestamp) => { + let timestamp: i64 = src_timestamp; + let datetime: DateTime = Utc.timestamp_opt(src_timestamp, 0).unwrap(); + + // Convert the UTC time to the specified target timezone + let target_datetime: DateTime = datetime.with_timezone(&t_tz); + let result_timestamp = target_datetime.timestamp(); + // Return the adjusted timestamp as a Unix timestamp in seconds + output.push(result_timestamp) + } + Err(e) => { + return ctx.set_error( + output.len(), + format!("cannot parse target `timezone`. {}", e), + ); + } + }; + }, + )(target_tz, src_timestamp, ctx) + } +} fn register_string_to_timestamp(registry: &mut FunctionRegistry) { registry.register_aliases("to_date", &["str_to_date", "date"]); registry.register_aliases("to_year", &["str_to_year", "year"]); diff --git a/src/query/functions/tests/it/scalars/datetime.rs b/src/query/functions/tests/it/scalars/datetime.rs index fa6fc8700835..f8f560ce3b5f 100644 --- a/src/query/functions/tests/it/scalars/datetime.rs +++ b/src/query/functions/tests/it/scalars/datetime.rs @@ -35,6 +35,42 @@ fn test_datetime() { test_timestamp_arith(file); test_to_number(file); test_rounder_functions(file); + test_convert_timezone(file); +} + +fn test_convert_timezone(file: &mut impl Write) { + run_ast( + file, + "convert_timezone('America/New_York', to_timestamp(100))", + &[], + ); + run_ast( + file, + "convert_timezone('America/New_York', to_timestamp(-100))", + &[], + ); + run_ast( + file, + "convert_timezone('America/New_York', to_timestamp(0))", + &[], + ); + run_ast( + file, + "convert_timezone('America/New_York', to_timestamp(315360000000))", + &[], + ); + + run_ast( + file, + "convert_timezone('America/New_York', 'Europe/Simferopol', to_timestamp(100))", + &[], + ); + + run_ast( + file, + "convert_timezone('America/New_York', 'Europe/Simferopol', to_timestamp(0))", + &[], + ); } fn test_to_timestamp(file: &mut impl Write) { diff --git a/src/query/functions/tests/it/scalars/testdata/datetime.txt b/src/query/functions/tests/it/scalars/testdata/datetime.txt index 7691517b5e8c..c2a7d689e766 100644 --- a/src/query/functions/tests/it/scalars/testdata/datetime.txt +++ b/src/query/functions/tests/it/scalars/testdata/datetime.txt @@ -3344,4 +3344,10 @@ output type : Timestamp output domain : {1630812366000000..=1630812366000000} output : '2021-09-05 03:26:06.000000' - +ast : convert_timezone('2024-08-06 14:30:00 +02:00','America/New_York') +raw expr : convert_timezone('2024-08-06 14:30:00 +02:00','America/New_York') +checked expr : convert_timezone ('2024-08-06T14:30:00+02:00','America/New_York') +optimized expr : '2024-08-06T08:30:00.000000' +output type : Timestamp +output domain : {'2024-08-06T08:30:00.000000'..='2024-08-06T08:30:00.000000'} +output : '2024-08-06 08:30:00.000000' \ No newline at end of file