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]