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

shenghang pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 41abb7d036 [improve] the substring and substr functions support 
Date,LocalDate,L… (#9969)
41abb7d036 is described below

commit 41abb7d036b5dd9a557263d8948b7b7a8e800bd7
Author: misi <[email protected]>
AuthorDate: Sat Nov 1 14:51:36 2025 +0800

    [improve] the substring and substr functions support Date,LocalDate,L… 
(#9969)
    
    Co-authored-by: misi <[email protected]>
---
 .../seatunnel/common/utils/DateTimeUtils.java      |  16 +++
 .../apache/seatunnel/common/utils/DateUtils.java   |   5 +
 .../sql/zeta/functions/StringFunction.java         |  74 +++++++++++-
 .../sql/zeta/functions/StringFunctionTest.java     | 124 +++++++++++++++++++++
 4 files changed, 217 insertions(+), 2 deletions(-)

diff --git 
a/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateTimeUtils.java
 
b/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateTimeUtils.java
index b1e0856e1f..7c78e7e674 100644
--- 
a/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateTimeUtils.java
+++ 
b/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateTimeUtils.java
@@ -21,10 +21,12 @@ import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.OffsetDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.time.format.SignStyle;
+import java.time.temporal.Temporal;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalQueries;
 import java.util.HashMap;
@@ -314,6 +316,20 @@ public class DateTimeUtils {
         return dateTime.format(FORMATTER_MAP.get(formatter));
     }
 
+    public static String toString(OffsetDateTime offsetDateTime, Formatter 
formatter) {
+        return toString(offsetDateTime.toLocalDateTime(), formatter);
+    }
+
+    public static String toString(Temporal temporal, Formatter formatter) {
+        if (temporal instanceof OffsetDateTime) {
+            return toString(((OffsetDateTime) temporal).toLocalDateTime(), 
formatter);
+        } else if (temporal instanceof java.time.ZonedDateTime) {
+            return toString(((java.time.ZonedDateTime) 
temporal).toLocalDateTime(), formatter);
+        } else {
+            return FORMATTER_MAP.get(formatter).format(temporal);
+        }
+    }
+
     public static String toString(long timestamp, Formatter formatter) {
         Instant instant = Instant.ofEpochMilli(timestamp);
         return toString(LocalDateTime.ofInstant(instant, 
ZoneId.systemDefault()), formatter);
diff --git 
a/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateUtils.java
 
b/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateUtils.java
index 04105cfdf8..81385cb1ad 100644
--- 
a/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateUtils.java
+++ 
b/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/DateUtils.java
@@ -21,6 +21,7 @@ import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.time.format.SignStyle;
+import java.time.temporal.Temporal;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Pattern;
@@ -189,6 +190,10 @@ public class DateUtils {
         return date.format(FORMATTER_MAP.get(formatter));
     }
 
+    public static String toString(Temporal temporal, Formatter formatter) {
+        return FORMATTER_MAP.get(formatter).format(temporal);
+    }
+
     public enum Formatter {
         YYYY_MM_DD("yyyy-MM-dd"),
         YYYY_M_D("yyyy/M/d"),
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/StringFunction.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/StringFunction.java
index f2a6762cfd..dec3948f1b 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/StringFunction.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/sql/zeta/functions/StringFunction.java
@@ -20,6 +20,8 @@ package org.apache.seatunnel.transform.sql.zeta.functions;
 import org.apache.seatunnel.shade.com.google.common.hash.Hashing;
 
 import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated;
+import org.apache.seatunnel.common.utils.DateTimeUtils;
+import org.apache.seatunnel.common.utils.DateUtils;
 import org.apache.seatunnel.transform.exception.TransformException;
 import org.apache.seatunnel.transform.sql.zeta.ZetaSQLFunction;
 
@@ -27,9 +29,14 @@ import org.apache.groovy.parser.antlr4.util.StringUtils;
 
 import java.lang.reflect.Array;
 import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
 import java.time.temporal.Temporal;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 import java.util.regex.Matcher;
@@ -625,11 +632,74 @@ public class StringFunction {
         return new String(chars, StandardCharsets.ISO_8859_1);
     }
 
+    /**
+     * Convert date/time objects to standardized string format
+     *
+     * @param obj the object to convert
+     * @return standardized string representation of the date/time object
+     */
+    private static String convertDateToString(Object obj) {
+        if (obj == null) {
+            return null;
+        }
+
+        // Handle java.util.Date and subclasses (java.sql.Date, 
java.sql.Timestamp)
+        if (obj instanceof Date) {
+            Date date = (Date) obj;
+            LocalDateTime localDateTime = 
LocalDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC);
+            return DateTimeUtils.toString(
+                    localDateTime, 
DateTimeUtils.Formatter.YYYY_MM_DD_HH_MM_SS);
+        }
+
+        // Handle java.time types
+        if (obj instanceof LocalDate) {
+            LocalDate localDate = (LocalDate) obj;
+            return DateUtils.toString(localDate, 
DateUtils.Formatter.YYYY_MM_DD);
+        }
+
+        if (obj instanceof LocalDateTime) {
+            LocalDateTime localDateTime = (LocalDateTime) obj;
+            return DateTimeUtils.toString(
+                    localDateTime, 
DateTimeUtils.Formatter.YYYY_MM_DD_HH_MM_SS);
+        }
+
+        if (obj instanceof OffsetDateTime) {
+            OffsetDateTime offsetDateTime = (OffsetDateTime) obj;
+            return DateTimeUtils.toString(
+                    offsetDateTime, 
DateTimeUtils.Formatter.YYYY_MM_DD_HH_MM_SS);
+        }
+
+        // For Temporal objects that are not specifically handled above
+        if (obj instanceof Temporal) {
+            Temporal temporal = (Temporal) obj;
+            try {
+                // Try to format as timestamp first
+                return DateTimeUtils.toString(
+                        temporal, DateTimeUtils.Formatter.YYYY_MM_DD_HH_MM_SS);
+            } catch (Exception e) {
+                try {
+                    // Fallback to date-only format
+                    return DateUtils.toString(temporal, 
DateUtils.Formatter.YYYY_MM_DD);
+                } catch (Exception ex) {
+                    // If all else fails, use toString
+                    return obj.toString();
+                }
+            }
+        }
+
+        // For non-date objects, convert to string directly
+        return obj.toString();
+    }
+
     public static String substring(List<Object> args) {
-        String s = (String) args.get(0);
-        if (s == null) {
+        Object input = args.get(0);
+        if (input == null) {
             return null;
         }
+
+        // Convert date types to standardized string format
+        String s = convertDateToString(input);
+
         int sl = s.length();
         int start = ((Number) args.get(1)).intValue();
         Object v3 = null;
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/StringFunctionTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/StringFunctionTest.java
new file mode 100644
index 0000000000..038e56042a
--- /dev/null
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/sql/zeta/functions/StringFunctionTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.seatunnel.transform.sql.zeta.functions;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class StringFunctionTest {
+
+    @Test
+    public void testSubstringWithString() {
+        List<Object> args = new ArrayList<>();
+        args.add("Hello World");
+        args.add(1);
+        Assertions.assertEquals("Hello World", StringFunction.substring(args));
+
+        args.clear();
+        args.add("Hello World");
+        args.add(7);
+        Assertions.assertEquals("World", StringFunction.substring(args));
+
+        args.clear();
+        args.add("Hello World");
+        args.add(1);
+        args.add(5);
+        Assertions.assertEquals("Hello", StringFunction.substring(args));
+    }
+
+    @Test
+    public void testSubstringWithLocalDate() {
+        List<Object> args = new ArrayList<>();
+
+        // Test LocalDate
+        LocalDate date = LocalDate.of(2023, 12, 25);
+        args.add(date);
+        args.add(1);
+        args.add(4);
+        Assertions.assertEquals("2023", StringFunction.substring(args));
+    }
+
+    @Test
+    public void testSubstringWithLocalDateTime() {
+        List<Object> args = new ArrayList<>();
+
+        // Test LocalDateTime
+        LocalDateTime dateTime = LocalDateTime.of(2023, 12, 25, 15, 30, 45);
+        args.add(dateTime);
+        args.add(2);
+        args.add(6);
+        Assertions.assertEquals("023-12", StringFunction.substring(args));
+    }
+
+    @Test
+    public void testSubstringWithOffsetDateTime() {
+        List<Object> args = new ArrayList<>();
+
+        // Test OffsetDateTime
+        OffsetDateTime offsetDateTime =
+                LocalDateTime.of(2023, 12, 25, 15, 30, 
45).atOffset(ZoneOffset.UTC);
+        args.add(offsetDateTime);
+        args.add(1);
+        args.add(4);
+        Assertions.assertEquals("2023", StringFunction.substring(args));
+    }
+
+    @Test
+    public void testSubstringWithUtilDate() {
+        List<Object> args = new ArrayList<>();
+
+        // Test java.util.Date
+        Date utilDate = new Date(123, 11, 25); // Year 2023 (123 + 1900), 
Month 12, Day 25
+        args.add(utilDate);
+        args.add(1);
+        args.add(4);
+        // Should extract year part from formatted string "2023-12-25 00:00:00"
+        Assertions.assertEquals("2023", StringFunction.substring(args));
+    }
+
+    @Test
+    public void testSubstringWithNullInput() {
+        List<Object> args = new ArrayList<>();
+        args.add(null);
+        args.add(1);
+        Assertions.assertNull(StringFunction.substring(args));
+    }
+
+    @Test
+    public void testSubstringWithTemporal() {
+        List<Object> args = new ArrayList<>();
+
+        // Test LocalTime (as a Temporal implementation not explicitly handled)
+        Temporal time = LocalTime.of(15, 30, 45);
+        args.add(time);
+        args.add(1);
+        args.add(5);
+        // Should extract time part from formatted string "15:30:45"
+        Assertions.assertEquals("15:30", StringFunction.substring(args));
+    }
+}

Reply via email to