Skip to content

Commit 2cf15e3

Browse files
committed
feat(functions): refactor from_parts registration and add comprehensive DST tests
1 parent c7199e3 commit 2cf15e3

File tree

3 files changed

+92
-36
lines changed

3 files changed

+92
-36
lines changed

src/query/expression/src/utils/date_helper.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,8 +2069,8 @@ pub fn pg_format_to_strftime(pg_format_string: &str) -> String {
20692069
}
20702070

20712071
/// Normalize month/day values that may be outside normal ranges.
2072-
/// Snowflake allows e.g. month=0 (meaning Dec of previous year),
2073-
/// month=13 (meaning Jan of next year), day=100 (100th day from month start), etc.
2072+
/// Supports values like month=0 (meaning Dec of previous year),
2073+
/// month=13 (meaning Jan of next year), and day=100 (100th day from month start).
20742074
pub fn normalize_date_parts(year: i64, month: i64, day: i64) -> std::result::Result<Date, String> {
20752075
// Normalize month: month is 1-based, so month=0 means Dec of previous year,
20762076
// month=-1 means Nov of previous year, month=13 means Jan of next year.

src/query/functions/src/scalars/timestamp/src/datetime.rs

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2786,8 +2786,16 @@ fn register_timestamp_from_parts(registry: &mut FunctionRegistry) {
27862786
Vec<DataType>,
27872787
fn(&[Value<AnyType>], &mut EvalContext) -> Value<AnyType>,
27882788
) = match args_type.len() {
2789-
6 => (vec![int64_type; 6], timestamp_from_parts_6 as _),
2790-
7 => (vec![int64_type; 7], timestamp_from_parts_7 as _),
2789+
// 6 args: year, month, day, hour, minute, second
2790+
6 => (vec![int64_type; 6], |args, ctx| {
2791+
timestamp_from_parts_fn(args, ctx, false)
2792+
}),
2793+
2794+
// 7 args: includes nanosecond
2795+
7 => (vec![int64_type; 7], |args, ctx| {
2796+
timestamp_from_parts_fn(args, ctx, true)
2797+
}),
2798+
27912799
_ => return None,
27922800
};
27932801

@@ -2809,14 +2817,6 @@ fn register_timestamp_from_parts(registry: &mut FunctionRegistry) {
28092817
registry.register_function_factory("timestamp_from_parts", factory);
28102818
}
28112819

2812-
fn timestamp_from_parts_6(args: &[Value<AnyType>], ctx: &mut EvalContext) -> Value<AnyType> {
2813-
timestamp_from_parts_fn(args, ctx, false)
2814-
}
2815-
2816-
fn timestamp_from_parts_7(args: &[Value<AnyType>], ctx: &mut EvalContext) -> Value<AnyType> {
2817-
timestamp_from_parts_fn(args, ctx, true)
2818-
}
2819-
28202820
fn register_timestamp_tz_from_parts(registry: &mut FunctionRegistry) {
28212821
registry.register_aliases("timestamp_tz_from_parts", &["timestamptzfromparts"]);
28222822

@@ -2828,20 +2828,36 @@ fn register_timestamp_tz_from_parts(registry: &mut FunctionRegistry) {
28282828
Vec<DataType>,
28292829
fn(&[Value<AnyType>], &mut EvalContext) -> Value<AnyType>,
28302830
) = match args_type.len() {
2831-
6 => (vec![int64_type; 6], timestamp_tz_from_parts_6 as _),
2831+
// 6 args: no nanoseconds, no timezone
2832+
6 => (vec![int64_type; 6], |args, ctx| {
2833+
timestamp_tz_from_parts_fn(args, ctx, false, false)
2834+
}),
28322835

2836+
// 7 args: timezone provided
28332837
7 if args_type[6].remove_nullable() == DataType::String => {
28342838
let mut v = vec![int64_type; 6];
28352839
v.push(DataType::String);
2836-
(v, timestamp_tz_from_parts_7_tz as _)
2840+
2841+
// year, month, day, hour, minute, second, timezone
2842+
(v, |args, ctx| {
2843+
timestamp_tz_from_parts_fn(args, ctx, false, true)
2844+
})
28372845
}
28382846

2839-
7 => (vec![int64_type; 7], timestamp_tz_from_parts_7_nano as _),
2847+
// 7 args: nanoseconds
2848+
7 => (vec![int64_type; 7], |args, ctx| {
2849+
timestamp_tz_from_parts_fn(args, ctx, true, false)
2850+
}),
28402851

2852+
// 8 args: nanoseconds + timezone
28412853
8 => {
28422854
let mut v = vec![int64_type; 7];
28432855
v.push(DataType::String);
2844-
(v, timestamp_tz_from_parts_8 as _)
2856+
2857+
// year, month, day, hour, minute, second, nanosecond, timezone
2858+
(v, |args, ctx| {
2859+
timestamp_tz_from_parts_fn(args, ctx, true, true)
2860+
})
28452861
}
28462862

28472863
_ => return None,
@@ -2865,25 +2881,6 @@ fn register_timestamp_tz_from_parts(registry: &mut FunctionRegistry) {
28652881
registry.register_function_factory("timestamp_tz_from_parts", factory);
28662882
}
28672883

2868-
fn timestamp_tz_from_parts_6(args: &[Value<AnyType>], ctx: &mut EvalContext) -> Value<AnyType> {
2869-
timestamp_tz_from_parts_fn(args, ctx, false, false)
2870-
}
2871-
2872-
fn timestamp_tz_from_parts_7_nano(
2873-
args: &[Value<AnyType>],
2874-
ctx: &mut EvalContext,
2875-
) -> Value<AnyType> {
2876-
timestamp_tz_from_parts_fn(args, ctx, true, false)
2877-
}
2878-
2879-
fn timestamp_tz_from_parts_7_tz(args: &[Value<AnyType>], ctx: &mut EvalContext) -> Value<AnyType> {
2880-
timestamp_tz_from_parts_fn(args, ctx, false, true)
2881-
}
2882-
2883-
fn timestamp_tz_from_parts_8(args: &[Value<AnyType>], ctx: &mut EvalContext) -> Value<AnyType> {
2884-
timestamp_tz_from_parts_fn(args, ctx, true, true)
2885-
}
2886-
28872884
fn build_timestamp_parts(
28882885
args: &[Value<AnyType>],
28892886
ctx: &mut EvalContext,

tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1842,7 +1842,6 @@ select timestamp_from_parts(2010, 13, 1, 0, 0, 0);
18421842
----
18431843
2011-01-01 00:00:00.000000
18441844

1845-
18461845
## Additional TIMESTAMP_TZ_FROM_PARTS tests
18471846

18481847
statement ok
@@ -1893,3 +1892,63 @@ select timestamp_tz_from_parts(2013, 13, 1, 0, 0, 0, 'UTC');
18931892
# Invalid timezone should produce error
18941893
query error
18951894
select timestamp_tz_from_parts(2013, 4, 5, 12, 0, 0, 'INVALID_TZ');
1895+
1896+
# America/Los_Angeles winter (PST)
1897+
query ?
1898+
select timestamp_tz_from_parts(2023, 1, 1, 12, 0, 0, 'America/Los_Angeles');
1899+
----
1900+
2023-01-01 12:00:00.000000 -0800
1901+
1902+
# America/Los_Angeles summer (PDT)
1903+
query ?
1904+
select timestamp_tz_from_parts(2023, 7, 1, 12, 0, 0, 'America/Los_Angeles');
1905+
----
1906+
2023-07-01 12:00:00.000000 -0700
1907+
1908+
# DST start boundary (spring forward)
1909+
query ?
1910+
select timestamp_tz_from_parts(2023, 3, 12, 3, 0, 0, 'America/Los_Angeles');
1911+
----
1912+
2023-03-12 03:00:00.000000 -0700
1913+
1914+
# Before DST transition
1915+
query ?
1916+
select timestamp_tz_from_parts(2023, 3, 12, 1, 30, 0, 'America/Los_Angeles');
1917+
----
1918+
2023-03-12 01:30:00.000000 -0800
1919+
1920+
# After DST transition
1921+
query ?
1922+
select timestamp_tz_from_parts(2023, 3, 12, 4, 0, 0, 'America/Los_Angeles');
1923+
----
1924+
2023-03-12 04:00:00.000000 -0700
1925+
1926+
# DST fall back (overlap hour)
1927+
query ?
1928+
select timestamp_tz_from_parts(2023, 11, 5, 1, 30, 0, 'America/Los_Angeles');
1929+
----
1930+
2023-11-05 01:30:00.000000 -0700
1931+
1932+
# After DST fallback
1933+
query ?
1934+
select timestamp_tz_from_parts(2023, 11, 5, 2, 30, 0, 'America/Los_Angeles');
1935+
----
1936+
2023-11-05 02:30:00.000000 -0800
1937+
1938+
# Asia/Shanghai (no DST behavior)
1939+
query ?
1940+
select timestamp_tz_from_parts(2023, 6, 1, 12, 0, 0, 'Asia/Shanghai');
1941+
----
1942+
2023-06-01 12:00:00.000000 +0800
1943+
1944+
# Nanosecond overflow with timezone
1945+
query ?
1946+
select timestamp_tz_from_parts(2023, 1, 1, 12, 0, 0, 2000000000, 'UTC');
1947+
----
1948+
2023-01-01 12:00:02.000000 +0000
1949+
1950+
# Negative nanoseconds
1951+
query ?
1952+
select timestamp_tz_from_parts(2023, 1, 1, 12, 0, 0, -1000000000, 'UTC');
1953+
----
1954+
2023-01-01 11:59:59.000000 +0000

0 commit comments

Comments
 (0)