You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geaflow.apache.org by lt...@apache.org on 2026/07/28 02:35:26 UTC

(geaflow) branch master updated: feat: add date functions QUARTER and DAY_OF_YEAR (#814)

This is an automated email from the ASF dual-hosted git repository.

Leomrlin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/geaflow.git


The following commit(s) were added to refs/heads/master by this push:
     new 6312f97d8 feat: add date functions QUARTER and DAY_OF_YEAR (#814)
6312f97d8 is described below

commit 6312f97d83c1f992ac52427b08380d596954fdf8
Author: YinZiBenRun <yi...@gmail.com>
AuthorDate: Tue Jul 28 10:35:21 2026 +0800

    feat: add date functions QUARTER and DAY_OF_YEAR (#814)
    
    * feat: add date functions QUARTER and DAY_OF_YEAR
    
    Add two new date UDFs:
    - quarter(date): returns the quarter of the year (1-4)
    - day_of_year(date): returns the day of the year (1-366)
    
    Both follow the existing WeekOfYear pattern using Joda-Time.
    Registered in BuildInSqlFunctionTable.
    Tests cover quarter boundaries, leap years, year boundaries, and null inputs.
    
    Closes #791
    
    * test: add end-to-end SQL test for QUARTER/DAY_OF_YEAR
    
    The existing UDFDateTest verifies the Java eval() methods directly but
    does not exercise SQL registration, overload resolution, type inference,
    or runtime invocation. Add an E2E SQL test via QueryTester that runs the
    functions through the full query pipeline.
    
    Covers quarter boundaries (Q1-Q4), leap-year day_of_year values
    (2020-12-31=366, 2020-02-29=60), non-leap year (2019-12-31=365), and
    year start/end boundaries.
    
    Verified locally: Tests run: 1, Failures: 0, Errors: 0.
    
    ---------
    
    Co-authored-by: 1+5 <29...@qq.com>
---
 .../schema/function/BuildInSqlFunctionTable.java   |  4 ++
 .../geaflow/dsl/udf/table/date/DayOfYear.java      | 59 +++++++++++++++++++++
 .../apache/geaflow/dsl/udf/table/date/Quarter.java | 61 ++++++++++++++++++++++
 .../apache/geaflow/dsl/udf/date/UDFDateTest.java   | 49 +++++++++++++++++
 .../query/udf/DateQuarterDayOfYearTest.java        | 44 ++++++++++++++++
 .../test/resources/data/date_quarter_dayofyear.txt | 12 +++++
 .../expect/date_quarter_dayofyear_001.txt          | 12 +++++
 .../resources/query/date_quarter_dayofyear_001.sql | 55 +++++++++++++++++++
 8 files changed, 296 insertions(+)

diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java
index 4de0a1ae5..9656d9299 100644
--- a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java
+++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java
@@ -68,6 +68,7 @@ import org.apache.geaflow.dsl.udf.table.date.DateSub;
 import org.apache.geaflow.dsl.udf.table.date.DateTrunc;
 import org.apache.geaflow.dsl.udf.table.date.Day;
 import org.apache.geaflow.dsl.udf.table.date.DayOfMonth;
+import org.apache.geaflow.dsl.udf.table.date.DayOfYear;
 import org.apache.geaflow.dsl.udf.table.date.FromUnixTime;
 import org.apache.geaflow.dsl.udf.table.date.FromUnixTimeMillis;
 import org.apache.geaflow.dsl.udf.table.date.Hour;
@@ -76,6 +77,7 @@ import org.apache.geaflow.dsl.udf.table.date.LastDay;
 import org.apache.geaflow.dsl.udf.table.date.Minute;
 import org.apache.geaflow.dsl.udf.table.date.Month;
 import org.apache.geaflow.dsl.udf.table.date.Now;
+import org.apache.geaflow.dsl.udf.table.date.Quarter;
 import org.apache.geaflow.dsl.udf.table.date.Second;
 import org.apache.geaflow.dsl.udf.table.date.UnixTimeStamp;
 import org.apache.geaflow.dsl.udf.table.date.UnixTimeStampMillis;
@@ -150,6 +152,7 @@ public class BuildInSqlFunctionTable extends ListSqlOperatorTable {
             .add(GeaFlowFunction.of(DateTrunc.class))
             .add(GeaFlowFunction.of(Day.class))
             .add(GeaFlowFunction.of(DayOfMonth.class))
+            .add(GeaFlowFunction.of(DayOfYear.class))
             .add(GeaFlowFunction.of(FromUnixTime.class))
             .add(GeaFlowFunction.of(FromUnixTimeMillis.class))
             .add(GeaFlowFunction.of(Hour.class))
@@ -158,6 +161,7 @@ public class BuildInSqlFunctionTable extends ListSqlOperatorTable {
             .add(GeaFlowFunction.of(Minute.class))
             .add(GeaFlowFunction.of(Month.class))
             .add(GeaFlowFunction.of(Now.class))
+            .add(GeaFlowFunction.of(Quarter.class))
             .add(GeaFlowFunction.of(Second.class))
             .add(GeaFlowFunction.of(UnixTimeStamp.class))
             .add(GeaFlowFunction.of(UnixTimeStampMillis.class))
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/date/DayOfYear.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/date/DayOfYear.java
new file mode 100644
index 000000000..716423d91
--- /dev/null
+++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/date/DayOfYear.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.dsl.udf.table.date;
+
+import java.sql.Timestamp;
+import org.apache.geaflow.common.exception.GeaflowRuntimeException;
+import org.apache.geaflow.dsl.common.function.Description;
+import org.apache.geaflow.dsl.common.function.UDF;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+@Description(name = "day_of_year", description = "Returns the day of the year for the given date.")
+public class DayOfYear extends UDF {
+
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern(
+        "yyyy-MM-dd HH:mm:ss");
+    private final DateTime dateTime = new DateTime();
+
+    public Integer eval(String dateString) {
+        if (dateString == null) {
+            return null;
+        }
+        DateTimeFormatter formatter = DATE_TIME_FORMATTER;
+        if (dateString.length() <= 10) {
+            formatter = DATE_FORMATTER;
+        }
+        try {
+            return formatter.parseDateTime(dateString).getDayOfYear();
+        } catch (Exception e) {
+            throw new GeaflowRuntimeException(e);
+        }
+    }
+
+    public Integer eval(Timestamp t) {
+        if (t == null) {
+            return null;
+        }
+        return dateTime.withMillis(t.getTime()).getDayOfYear();
+    }
+}
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/date/Quarter.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/date/Quarter.java
new file mode 100644
index 000000000..ba6c0888e
--- /dev/null
+++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/date/Quarter.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.dsl.udf.table.date;
+
+import java.sql.Timestamp;
+import org.apache.geaflow.common.exception.GeaflowRuntimeException;
+import org.apache.geaflow.dsl.common.function.Description;
+import org.apache.geaflow.dsl.common.function.UDF;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+@Description(name = "quarter", description = "Returns the quarter of the year for the given date.")
+public class Quarter extends UDF {
+
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern(
+        "yyyy-MM-dd HH:mm:ss");
+    private final DateTime dateTime = new DateTime();
+
+    public Integer eval(String dateString) {
+        if (dateString == null) {
+            return null;
+        }
+        DateTimeFormatter formatter = DATE_TIME_FORMATTER;
+        if (dateString.length() <= 10) {
+            formatter = DATE_FORMATTER;
+        }
+        try {
+            int month = formatter.parseDateTime(dateString).getMonthOfYear();
+            return (month - 1) / 3 + 1;
+        } catch (Exception e) {
+            throw new GeaflowRuntimeException(e);
+        }
+    }
+
+    public Integer eval(Timestamp t) {
+        if (t == null) {
+            return null;
+        }
+        int month = dateTime.withMillis(t.getTime()).getMonthOfYear();
+        return (month - 1) / 3 + 1;
+    }
+}
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/date/UDFDateTest.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/date/UDFDateTest.java
index ba78ecc1c..dea3c6263 100644
--- a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/date/UDFDateTest.java
+++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/date/UDFDateTest.java
@@ -33,6 +33,7 @@ import org.apache.geaflow.dsl.udf.table.date.DateSub;
 import org.apache.geaflow.dsl.udf.table.date.DateTrunc;
 import org.apache.geaflow.dsl.udf.table.date.Day;
 import org.apache.geaflow.dsl.udf.table.date.DayOfMonth;
+import org.apache.geaflow.dsl.udf.table.date.DayOfYear;
 import org.apache.geaflow.dsl.udf.table.date.FromUnixTime;
 import org.apache.geaflow.dsl.udf.table.date.FromUnixTimeMillis;
 import org.apache.geaflow.dsl.udf.table.date.Hour;
@@ -41,6 +42,7 @@ import org.apache.geaflow.dsl.udf.table.date.LastDay;
 import org.apache.geaflow.dsl.udf.table.date.Minute;
 import org.apache.geaflow.dsl.udf.table.date.Month;
 import org.apache.geaflow.dsl.udf.table.date.Now;
+import org.apache.geaflow.dsl.udf.table.date.Quarter;
 import org.apache.geaflow.dsl.udf.table.date.Second;
 import org.apache.geaflow.dsl.udf.table.date.UnixTimeStamp;
 import org.apache.geaflow.dsl.udf.table.date.UnixTimeStampMillis;
@@ -280,6 +282,53 @@ public class UDFDateTest {
         assertEquals((long) test.eval(new java.sql.Timestamp(1667900725)), 4);
     }
 
+    @Test
+    public void testQuarter() {
+        Quarter test = new Quarter();
+        test.open(null);
+        // Q1: Jan-Mar
+        assertEquals((long) test.eval("1987-01-15 00:11:22"), 1);
+        assertEquals((long) test.eval("1987-03-31"), 1);
+        // Q2: Apr-Jun
+        assertEquals((long) test.eval("1987-06-05 00:11:22"), 2);
+        assertEquals((long) test.eval("1987-04-01"), 2);
+        // Q3: Jul-Sep
+        assertEquals((long) test.eval("1987-07-01 00:00:00"), 3);
+        assertEquals((long) test.eval("1987-09-30"), 3);
+        // Q4: Oct-Dec
+        assertEquals((long) test.eval("1987-10-01 00:00:00"), 4);
+        assertEquals((long) test.eval("1987-12-31"), 4);
+        // Null inputs
+        assertNull(test.eval((String) null));
+        assertNull(test.eval((java.sql.Timestamp) null));
+        // Timestamp
+        assertEquals((long) test.eval(new java.sql.Timestamp(1667900725)), 1);
+    }
+
+    @Test
+    public void testDayOfYear() {
+        DayOfYear test = new DayOfYear();
+        test.open(null);
+        // Normal date
+        assertEquals((long) test.eval("1987-06-05 00:11:22"), 156);
+        assertEquals((long) test.eval("1987-06-05"), 156);
+        // Start of year
+        assertEquals((long) test.eval("1987-01-01 00:00:00"), 1);
+        // End of non-leap year
+        assertEquals((long) test.eval("1987-12-31"), 365);
+        // Leap year: 2020-02-29 is day 60, 2020-12-31 is day 366
+        assertEquals((long) test.eval("2020-02-29"), 60);
+        assertEquals((long) test.eval("2020-12-31"), 366);
+        // Non-leap year: 2019-03-01 is day 60 (no Feb 29)
+        assertEquals((long) test.eval("2019-03-01"), 60);
+        assertEquals((long) test.eval("2019-12-31"), 365);
+        // Null inputs
+        assertNull(test.eval((String) null));
+        assertNull(test.eval((java.sql.Timestamp) null));
+        // Timestamp (1667900725ms = 1970-01-20, day 20)
+        assertEquals((long) test.eval(new java.sql.Timestamp(1667900725)), 20);
+    }
+
     @Test
     public void testYear() {
         Year test = new Year();
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/udf/DateQuarterDayOfYearTest.java b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/udf/DateQuarterDayOfYearTest.java
new file mode 100644
index 000000000..4fb1bef77
--- /dev/null
+++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/udf/DateQuarterDayOfYearTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geaflow.dsl.runtime.query.udf;
+
+import org.apache.geaflow.dsl.runtime.query.QueryTester;
+import org.testng.annotations.Test;
+
+/**
+ * End-to-end SQL tests for the QUARTER and DAY_OF_YEAR date UDFs.
+ *
+ * <p>Unlike the unit tests in {@code UDFDateTest} which invoke the Java
+ * {@code eval()} methods directly, these tests exercise the full SQL
+ * pipeline: function registration in {@code BuildInSqlFunctionTable},
+ * overload resolution, type inference, and runtime invocation through
+ * the GeaFlow query engine.</p>
+ */
+public class DateQuarterDayOfYearTest {
+
+    @Test
+    public void testQuarterDayOfYear() throws Exception {
+        QueryTester
+            .build()
+            .withQueryPath("/query/date_quarter_dayofyear_001.sql")
+            .execute()
+            .checkSinkResult();
+    }
+}
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/data/date_quarter_dayofyear.txt b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/data/date_quarter_dayofyear.txt
new file mode 100644
index 000000000..9f5165638
--- /dev/null
+++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/data/date_quarter_dayofyear.txt
@@ -0,0 +1,12 @@
+1|2020-01-15
+2|2020-03-31
+3|2020-04-01
+4|2020-06-30
+5|2020-07-01
+6|2020-09-30
+7|2020-10-01
+8|2020-12-31
+9|2020-02-29
+10|2019-03-01
+11|2019-12-31
+12|2020-01-01
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/date_quarter_dayofyear_001.txt b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/date_quarter_dayofyear_001.txt
new file mode 100644
index 000000000..089521baf
--- /dev/null
+++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/date_quarter_dayofyear_001.txt
@@ -0,0 +1,12 @@
+1|1|15
+2|1|91
+3|2|92
+4|2|182
+5|3|183
+6|3|274
+7|4|275
+8|4|366
+9|1|60
+10|1|60
+11|4|365
+12|1|1
diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/date_quarter_dayofyear_001.sql b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/date_quarter_dayofyear_001.sql
new file mode 100644
index 000000000..b59c23d6d
--- /dev/null
+++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/date_quarter_dayofyear_001.sql
@@ -0,0 +1,55 @@
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements.  See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership.  The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License.  You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied.  See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+
+-- End-to-end SQL test for the QUARTER / DAY_OF_YEAR built-in date UDFs.
+--
+-- Unlike the unit tests in UDFDateTest which invoke the Java eval()
+-- methods directly, this exercises the full SQL pipeline: function
+-- registration in BuildInSqlFunctionTable, overload resolution, type
+-- inference, and runtime invocation through the query engine.
+--
+-- Covers quarter boundaries (Q1-Q4), leap-year day_of_year values,
+-- and year start/end boundaries.
+
+set geaflow.dsl.column.separator = '|';
+
+CREATE TABLE source (
+    id bigint,
+    date_str varchar
+) WITH (
+    type='file',
+    geaflow.dsl.file.path = 'resource:///data/date_quarter_dayofyear.txt'
+);
+
+CREATE TABLE tbl_result (
+    id bigint,
+    quarter_v int,
+    dayofyear_v int
+) WITH (
+    type='file',
+    -- Use a relative forward-slash path instead of the ${target} placeholder
+    -- to avoid Windows backslash-escaping issues (see math_sign_cbrt_trunc).
+    geaflow.dsl.file.path='target/date_quarter_dayofyear_001'
+);
+
+INSERT INTO tbl_result
+SELECT
+    id,
+    quarter(date_str) AS quarter_v,
+    day_of_year(date_str) AS dayofyear_v
+FROM source
+ORDER BY id;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@geaflow.apache.org
For additional commands, e-mail: commits-help@geaflow.apache.org