From 0f23c5eb79365b0988f03c6e3df1d505054a7f03 Mon Sep 17 00:00:00 2001 From: Isaias Gutierrez Cruz Date: Sat, 14 Jun 2025 19:30:12 -0600 Subject: [PATCH 1/2] feat: implement time_bucket --- narwhals/_duckdb/expr_dt.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/narwhals/_duckdb/expr_dt.py b/narwhals/_duckdb/expr_dt.py index 68f0f9b182..0a5a53e98e 100644 --- a/narwhals/_duckdb/expr_dt.py +++ b/narwhals/_duckdb/expr_dt.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Sequence -from duckdb import FunctionExpression +from duckdb import FunctionExpression, SQLExpression from narwhals._duckdb.utils import UNITS_DICT, fetch_rel_time_zone, lit from narwhals._duration import parse_interval_string @@ -111,17 +111,17 @@ def total_microseconds(self) -> DuckDBExpr: def truncate(self, every: str) -> DuckDBExpr: multiple, unit = parse_interval_string(every) - if multiple != 1: - # https://github.com/duckdb/duckdb/issues/17554 - msg = f"Only multiple 1 is currently supported for DuckDB.\nGot {multiple!s}." - raise ValueError(msg) if unit == "ns": msg = "Truncating to nanoseconds is not yet supported for DuckDB." raise NotImplementedError(msg) - format = lit(UNITS_DICT[unit]) + + interval_str = f"INTERVAL '{multiple} {UNITS_DICT[unit]}'" + tz_str = "(select value from duckdb_settings() where name = 'TimeZone')" def _truncate(expr: Expression) -> Expression: - return FunctionExpression("date_trunc", format, expr) + return FunctionExpression( + "time_bucket", SQLExpression(interval_str), expr, SQLExpression(tz_str) + ) return self._compliant_expr._with_callable(_truncate) From d94e120bd6d78a462241dd52b6af1b6617e09609 Mon Sep 17 00:00:00 2001 From: Isaias Gutierrez Cruz Date: Fri, 20 Jun 2025 00:03:06 -0600 Subject: [PATCH 2/2] feat: implement another option --- narwhals/_duckdb/expr_dt.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/narwhals/_duckdb/expr_dt.py b/narwhals/_duckdb/expr_dt.py index 0a5a53e98e..7a5208dfef 100644 --- a/narwhals/_duckdb/expr_dt.py +++ b/narwhals/_duckdb/expr_dt.py @@ -4,7 +4,7 @@ from duckdb import FunctionExpression, SQLExpression -from narwhals._duckdb.utils import UNITS_DICT, fetch_rel_time_zone, lit +from narwhals._duckdb.utils import UNITS_DICT, fetch_rel_time_zone, lit, when from narwhals._duration import parse_interval_string from narwhals._utils import not_implemented @@ -119,8 +119,21 @@ def truncate(self, every: str) -> DuckDBExpr: tz_str = "(select value from duckdb_settings() where name = 'TimeZone')" def _truncate(expr: Expression) -> Expression: - return FunctionExpression( - "time_bucket", SQLExpression(interval_str), expr, SQLExpression(tz_str) + is_timestamptz = FunctionExpression("typeof", expr).cast("varchar") == lit( + "TIMESTAMP WITH TIME ZONE" + ) + return when( + is_timestamptz, + FunctionExpression( + "time_bucket", + SQLExpression(interval_str), + expr, + SQLExpression(tz_str), + ).cast("TIMESTAMP WITH TIME ZONE"), + ).otherwise( + FunctionExpression("time_bucket", SQLExpression(interval_str), expr).cast( + "timestamp" + ) ) return self._compliant_expr._with_callable(_truncate)