From 4878280d0c41ab37f0f75086bffb2136f8cef41e Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Mon, 30 Mar 2026 13:01:08 -0400 Subject: [PATCH 1/2] Add missing datetime functions: make_time, current_timestamp, date_format Closes #1451. Adds make_time Rust binding and Python wrapper, and adds current_timestamp (alias for now) and date_format (alias for to_char) Python functions. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/core/src/functions.rs | 2 ++ python/datafusion/functions.py | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/crates/core/src/functions.rs b/crates/core/src/functions.rs index c32134054..6996dca94 100644 --- a/crates/core/src/functions.rs +++ b/crates/core/src/functions.rs @@ -616,6 +616,7 @@ expr_fn!(date_part, part date); expr_fn!(date_trunc, part date); expr_fn!(date_bin, stride source origin); expr_fn!(make_date, year month day); +expr_fn!(make_time, hour minute second); expr_fn!(to_char, datetime format); expr_fn!(translate, string from to, "Replaces each character in string that matches a character in the from set with the corresponding character in the to set. If from is longer than to, occurrences of the extra characters in from are deleted."); @@ -974,6 +975,7 @@ pub(crate) fn init_module(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(date_part))?; m.add_wrapped(wrap_pyfunction!(date_trunc))?; m.add_wrapped(wrap_pyfunction!(make_date))?; + m.add_wrapped(wrap_pyfunction!(make_time))?; m.add_wrapped(wrap_pyfunction!(digest))?; m.add_wrapped(wrap_pyfunction!(ends_with))?; m.add_wrapped(wrap_pyfunction!(exp))?; diff --git a/python/datafusion/functions.py b/python/datafusion/functions.py index f062cbfce..3c8d2bcee 100644 --- a/python/datafusion/functions.py +++ b/python/datafusion/functions.py @@ -128,7 +128,9 @@ "cume_dist", "current_date", "current_time", + "current_timestamp", "date_bin", + "date_format", "date_part", "date_trunc", "datepart", @@ -200,6 +202,7 @@ "make_array", "make_date", "make_list", + "make_time", "max", "md5", "mean", @@ -1948,6 +1951,15 @@ def now() -> Expr: return Expr(f.now()) +def current_timestamp() -> Expr: + """Returns the current timestamp in nanoseconds. + + See Also: + This is an alias for :py:func:`now`. + """ + return now() + + def to_char(arg: Expr, formatter: Expr) -> Expr: """Returns a string representation of a date, time, timestamp or duration. @@ -1970,6 +1982,15 @@ def to_char(arg: Expr, formatter: Expr) -> Expr: return Expr(f.to_char(arg.expr, formatter.expr)) +def date_format(arg: Expr, formatter: Expr) -> Expr: + """Returns a string representation of a date, time, timestamp or duration. + + See Also: + This is an alias for :py:func:`to_char`. + """ + return to_char(arg, formatter) + + def _unwrap_exprs(args: tuple[Expr, ...]) -> list: return [arg.expr for arg in args] @@ -2270,6 +2291,21 @@ def make_date(year: Expr, month: Expr, day: Expr) -> Expr: return Expr(f.make_date(year.expr, month.expr, day.expr)) +def make_time(hour: Expr, minute: Expr, second: Expr) -> Expr: + """Make a time from hour, minute and second component parts. + + Examples: + >>> ctx = dfn.SessionContext() + >>> df = ctx.from_pydict({"h": [12], "m": [30], "s": [0]}) + >>> result = df.select( + ... dfn.functions.make_time(dfn.col("h"), dfn.col("m"), + ... dfn.col("s")).alias("t")) + >>> result.collect_column("t")[0].as_py() + datetime.time(12, 30) + """ + return Expr(f.make_time(hour.expr, minute.expr, second.expr)) + + def translate(string: Expr, from_val: Expr, to_val: Expr) -> Expr: """Replaces the characters in ``from_val`` with the counterpart in ``to_val``. From cba5990774f0d62bd85a979708bef2fc8ec9c01e Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Mon, 30 Mar 2026 13:11:54 -0400 Subject: [PATCH 2/2] Add unit tests for make_time, current_timestamp, and date_format Co-Authored-By: Claude Opus 4.6 (1M context) --- python/tests/test_functions.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/python/tests/test_functions.py b/python/tests/test_functions.py index 37d349c58..08420826d 100644 --- a/python/tests/test_functions.py +++ b/python/tests/test_functions.py @@ -1107,6 +1107,39 @@ def test_today_alias_matches_current_date(df): assert result.column(0) == result.column(1) +def test_current_timestamp_alias_matches_now(df): + result = df.select( + f.now().alias("now"), + f.current_timestamp().alias("current_timestamp"), + ).collect()[0] + + assert result.column(0) == result.column(1) + + +def test_date_format_alias_matches_to_char(df): + result = df.select( + f.to_char( + f.to_timestamp(literal("2021-01-01T00:00:00")), literal("%Y/%m/%d") + ).alias("to_char"), + f.date_format( + f.to_timestamp(literal("2021-01-01T00:00:00")), literal("%Y/%m/%d") + ).alias("date_format"), + ).collect()[0] + + assert result.column(0) == result.column(1) + assert result.column(0)[0].as_py() == "2021/01/01" + + +def test_make_time(df): + ctx = SessionContext() + df_time = ctx.from_pydict({"h": [12], "m": [30], "s": [0]}) + result = df_time.select( + f.make_time(column("h"), column("m"), column("s")).alias("t") + ).collect()[0] + + assert result.column(0)[0].as_py() == time(12, 30) + + def test_arrow_cast(df): df = df.select( # we use `string_literal` to return utf8 instead of `literal` which returns