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

Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 7de143f4a9d Make dateTimeConvert scalar function robust to typed 
time-value input (#18814)
7de143f4a9d is described below

commit 7de143f4a9d88f3346abdf0efea8db87b656f15f
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Fri Jun 19 16:58:41 2026 -0700

    Make dateTimeConvert scalar function robust to typed time-value input 
(#18814)
---
 .../common/function/scalar/DateTimeConvert.java    | 26 +++++++++++++++-------
 .../core/data/function/DateTimeFunctionsTest.java  | 26 ++++++++++++++++++++++
 2 files changed, 44 insertions(+), 8 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeConvert.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeConvert.java
index be7e8de54db..11192bb95ee 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeConvert.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/DateTimeConvert.java
@@ -22,10 +22,12 @@ import java.time.DateTimeException;
 import java.time.ZoneId;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
+import org.apache.pinot.common.function.FunctionUtils;
 import org.apache.pinot.spi.annotations.ScalarFunction;
 import org.apache.pinot.spi.data.DateTimeFieldSpec;
 import org.apache.pinot.spi.data.DateTimeFormatSpec;
 import org.apache.pinot.spi.data.DateTimeGranularitySpec;
+import org.apache.pinot.spi.utils.PinotDataType;
 import org.joda.time.DateTimeZone;
 import org.joda.time.MutableDateTime;
 import org.joda.time.format.DateTimeFormatter;
@@ -38,18 +40,17 @@ public class DateTimeConvert {
   private DateTimeFormatSpec _inputFormatSpec;
   private DateTimeFormatSpec _outputFormatSpec;
   private DateTimeGranularitySpec _granularitySpec;
-  private DateTimeZone _bucketingTimeZone;
   private MutableDateTime _dateTime;
   private StringBuilder _buffer;
 
   @ScalarFunction
-  public Object dateTimeConvert(String timeValueStr, String inputFormatStr, 
String outputFormatStr,
+  public Object dateTimeConvert(Object timeValue, String inputFormatStr, 
String outputFormatStr,
       String outputGranularityStr) {
     if (_inputFormatSpec == null) {
       init(inputFormatStr, outputFormatStr, outputGranularityStr, null, false);
     }
 
-    long timeValueMs = _inputFormatSpec.fromFormatToMillis(timeValueStr);
+    long timeValueMs = fromInputFormatToMillis(timeValue);
     if (_outputFormatSpec.getTimeFormat() == 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT) {
       truncateDateTime(timeValueMs);
       return getFormattedDate();
@@ -66,13 +67,13 @@ public class DateTimeConvert {
   }
 
   @ScalarFunction
-  public Object dateTimeConvert(String timeValueStr, String inputFormatStr, 
String outputFormatStr,
+  public Object dateTimeConvert(Object timeValue, String inputFormatStr, 
String outputFormatStr,
       String outputGranularityStr, String bucketingTimeZone) {
     if (_inputFormatSpec == null) {
       init(inputFormatStr, outputFormatStr, outputGranularityStr, 
bucketingTimeZone, true);
     }
 
-    long timeValueMs = _inputFormatSpec.fromFormatToMillis(timeValueStr);
+    long timeValueMs = fromInputFormatToMillis(timeValue);
     truncateDateTime(timeValueMs);
 
     if (_outputFormatSpec.getTimeFormat() == 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT) {
@@ -84,6 +85,17 @@ public class DateTimeConvert {
     }
   }
 
+  /// Converts the input time value to millis since epoch:
+  /// - `EPOCH` / `TIMESTAMP` input is treated as a `LONG`.
+  /// - `SIMPLE_DATE_FORMAT` input is treated as a `STRING`.
+  private long fromInputFormatToMillis(Object timeValue) {
+    PinotDataType argumentType = FunctionUtils.getArgumentType(timeValue);
+    if (_inputFormatSpec.getTimeFormat() == 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT) {
+      return _inputFormatSpec.fromFormatToMillis((String) 
PinotDataType.STRING.convert(timeValue, argumentType));
+    }
+    return _inputFormatSpec.fromFormatToMillis((Long) 
PinotDataType.LONG.convert(timeValue, argumentType));
+  }
+
   private void init(String inputFormatStr, String outputFormatStr, String 
outputGranularityStr,
       String bucketingTimeZone, boolean bucketTzRequired) {
     _inputFormatSpec = new DateTimeFormatSpec(inputFormatStr);
@@ -91,13 +103,11 @@ public class DateTimeConvert {
     _granularitySpec = new DateTimeGranularitySpec(outputGranularityStr);
 
     DateTimeZone timeZone;
-
     if (bucketTzRequired) {
       try {
         // we're not using TimeZone.getTimeZone() because it's globally 
synchronized
         // and returns default TZ when str makes no sense
-        _bucketingTimeZone = 
DateTimeZone.forTimeZone(TimeZone.getTimeZone(ZoneId.of(bucketingTimeZone)));
-        timeZone = _bucketingTimeZone;
+        timeZone = 
DateTimeZone.forTimeZone(TimeZone.getTimeZone(ZoneId.of(bucketingTimeZone)));
       } catch (DateTimeException dte) {
         throw new IllegalArgumentException("Error parsing bucketing time zone: 
" + dte.getMessage(), dte);
       }
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java
index aa340186bd5..c9218ce57f3 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java
@@ -20,6 +20,8 @@ package org.apache.pinot.core.data.function;
 
 import com.google.common.collect.Lists;
 import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalTime;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
@@ -750,6 +752,30 @@ public class DateTimeFunctionsTest {
         "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS", "20170921");
   }
 
+  @Test
+  public void testDateTimeConvertRecordExtractorDateTypes() {
+    // The RecordExtractor contract decodes date-related logical types into 
TIMESTAMP -> java.sql.Timestamp,
+    // DATE -> java.time.LocalDate, TIME -> java.time.LocalTime. These reach 
dateTimeConvert as the raw object
+    // and are coerced via PinotDataType the same way 
FunctionInvoker.convertTypes does: EPOCH / TIMESTAMP input
+    // is read as LONG, SIMPLE_DATE_FORMAT input is read as STRING. (The 
raw-mode Integer / Long forms are already
+    // covered by the numeric cases above.)
+
+    // TIMESTAMP -> epoch millis (Timestamp#getTime)
+    testDateTimeConvert(new Timestamp(1505898960000L), "1:MILLISECONDS:EPOCH", 
"1:MILLISECONDS:EPOCH",
+        "1:MILLISECONDS", 1505898960000L);
+    testDateTimeConvert(new Timestamp(1505898960000L), "TIMESTAMP", 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS",
+        "20170920");
+
+    // DATE -> days since epoch (LocalDate#toEpochDay)
+    testDateTimeConvert(LocalDate.of(2017, 9, 20), "1:DAYS:EPOCH", 
"1:MILLISECONDS:EPOCH", "1:DAYS", 1505865600000L);
+    testDateTimeConvert(LocalDate.of(2017, 9, 20), "1:DAYS:EPOCH", 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS",
+        "20170920");
+
+    // TIME -> millis since midnight (LocalTime#toNanoOfDay / 1_000_000)
+    testDateTimeConvert(LocalTime.of(2, 16, 0), "1:MILLISECONDS:EPOCH", 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS",
+        8160000L);
+  }
+
   @Test
   private void testTimestampAdd() {
     long currentTimestamp = System.currentTimeMillis();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to