Skip to content

Commit 8861e09

Browse files
committed
Parse timestamp string with multiple parsers in a loop
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent 01c4d7f commit 8861e09

3 files changed

Lines changed: 33 additions & 37 deletions

File tree

core/src/main/java/org/opensearch/sql/utils/DateTimeUtils.java

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.time.format.DateTimeParseException;
1616
import java.time.temporal.ChronoUnit;
1717
import java.util.Locale;
18+
import java.util.Optional;
19+
import java.util.Set;
1820
import lombok.experimental.UtilityClass;
1921
import org.opensearch.sql.data.model.ExprTimeValue;
2022
import org.opensearch.sql.data.model.ExprValue;
@@ -25,6 +27,11 @@ public class DateTimeUtils {
2527

2628
private static final DateTimeFormatter DIRECT_FORMATTER =
2729
DateTimeFormatter.ofPattern("MM/dd/yyyy:HH:mm:ss");
30+
public static final Set<DateTimeFormatter> SUPPORTED_FORMATTERS =
31+
Set.of(
32+
DIRECT_FORMATTER,
33+
DateTimeFormatters.DATE_TIMESTAMP_FORMATTER,
34+
DateTimeFormatter.ISO_DATE_TIME);
2835

2936
/**
3037
* Util method to round the date/time with given unit.
@@ -167,22 +174,9 @@ public static LocalDate extractDate(ExprValue value, FunctionProperties function
167174
}
168175

169176
public static ZonedDateTime getRelativeZonedDateTime(String input, ZonedDateTime baseTime) {
170-
try {
171-
Instant parsed;
172-
try {
173-
parsed = LocalDateTime.parse(input, DIRECT_FORMATTER).toInstant(ZoneOffset.UTC);
174-
} catch (DateTimeParseException ignored) {
175-
try {
176-
parsed =
177-
LocalDateTime.parse(input, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER)
178-
.toInstant(ZoneOffset.UTC);
179-
} catch (DateTimeParseException ignored2) {
180-
ZonedDateTime zdt = ZonedDateTime.parse(input, DateTimeFormatter.ISO_DATE_TIME);
181-
parsed = zdt.withZoneSameInstant(ZoneOffset.UTC).toInstant();
182-
}
183-
}
184-
return parsed.atZone(baseTime.getZone());
185-
} catch (DateTimeParseException ignored) {
177+
Optional<ZonedDateTime> parsed = tryParseAbsoluteTime(input);
178+
if (parsed.isPresent()) {
179+
return parsed.get().withZoneSameInstant(baseTime.getZone());
186180
}
187181

188182
if ("now".equalsIgnoreCase(input) || "now()".equalsIgnoreCase(input)) {
@@ -340,7 +334,7 @@ static String resolveTimeModifier(String input, ZonedDateTime nowReference) {
340334
return "now";
341335
}
342336

343-
String absoluteTime = tryParseAbsoluteTime(input);
337+
String absoluteTime = tryParseAbsoluteTimeAndFormat(input);
344338
if (absoluteTime != null) {
345339
return absoluteTime;
346340
}
@@ -354,29 +348,31 @@ static String resolveTimeModifier(String input, ZonedDateTime nowReference) {
354348
* @param input The time string
355349
* @return ISO formatted datetime string or null if parsing fails
356350
*/
357-
private static String tryParseAbsoluteTime(String input) {
358-
try {
359-
try {
360-
LocalDate parsedDate = LocalDate.parse(input);
361-
return parsedDate.toString();
362-
} catch (DateTimeParseException ignored) {
363-
}
351+
private static String tryParseAbsoluteTimeAndFormat(String input) {
352+
Optional<ZonedDateTime> parsed = tryParseAbsoluteTime(input);
353+
return parsed
354+
.map(zonedDateTime -> zonedDateTime.format(DateTimeFormatter.ISO_INSTANT))
355+
.orElse(null);
356+
}
364357

365-
LocalDateTime parsed;
358+
private static Optional<ZonedDateTime> tryParseAbsoluteTime(String input) {
359+
for (DateTimeFormatter formatter : SUPPORTED_FORMATTERS) {
366360
try {
367-
parsed = LocalDateTime.parse(input, DIRECT_FORMATTER);
368-
} catch (DateTimeParseException ignored) {
369-
try {
370-
parsed = LocalDateTime.parse(input, DateTimeFormatters.DATE_TIMESTAMP_FORMATTER);
371-
} catch (DateTimeParseException ignored2) {
372-
ZonedDateTime zdt = ZonedDateTime.parse(input, DateTimeFormatter.ISO_DATE_TIME);
373-
parsed = zdt.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
361+
ZonedDateTime parsed;
362+
if (formatter == DateTimeFormatter.ISO_DATE_TIME) {
363+
// ISO_DATE_TIME can handle zone information
364+
parsed = ZonedDateTime.parse(input, formatter);
365+
} else {
366+
// Treat LocalDateTime formatters as UTC
367+
LocalDateTime localDateTime = LocalDateTime.parse(input, formatter);
368+
parsed = localDateTime.atZone(ZoneOffset.UTC);
374369
}
370+
return Optional.of(parsed);
371+
} catch (DateTimeParseException ignored) {
372+
// Try next formatter
375373
}
376-
return parsed.atZone(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
377-
} catch (DateTimeParseException ignored) {
378-
return null;
379374
}
375+
return Optional.empty();
380376
}
381377

382378
/**

core/src/test/java/org/opensearch/sql/utils/DateTimeUtilsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ void testResolveTimeModifierNull() {
177177

178178
@Test
179179
void testResolveTimeModifierWithDatetimeString() {
180-
assertEquals("2025-10-22", DateTimeUtils.resolveTimeModifier("2025-10-22"));
180+
assertEquals("2025-10-22T00:00:00Z", DateTimeUtils.resolveTimeModifier("2025-10-22"));
181181
assertEquals("2025-10-22T10:32:12Z", DateTimeUtils.resolveTimeModifier("2025-10-22 10:32:12"));
182182
// Test "direct" format
183183
assertEquals("2025-10-22T10:32:12Z", DateTimeUtils.resolveTimeModifier("10/22/2025:10:32:12"));

ppl/src/main/antlr/OpenSearchPPLParser.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ timeModifierValue
772772
| INTEGER_LITERAL
773773
| stringLiteral
774774
| TIME_SNAP
775-
| (PLUS | MINUS) SPANLENGTH (TIME_SNAP)? ((PLUS | MINUS) SPANLENGTH)*
775+
| (PLUS | MINUS) SPANLENGTH (TIME_SNAP)?
776776
;
777777

778778
// tables

0 commit comments

Comments
 (0)