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

mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new f707ef4b7e [CALCITE-7527] SqlParserUtil.parseTimestampTzLiteral does 
not validate timezone
f707ef4b7e is described below

commit f707ef4b7eef4bbb75163d336a6cc1e72e7ce673
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu May 14 13:51:02 2026 -0700

    [CALCITE-7527] SqlParserUtil.parseTimestampTzLiteral does not validate 
timezone
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../apache/calcite/sql/parser/SqlParserUtil.java   | 33 ++++++++++----
 .../calcite/sql/parser/SqlParserUtilTest.java      | 51 ++++++++++++++++++++++
 .../org/apache/calcite/test/SqlValidatorTest.java  |  2 +-
 3 files changed, 76 insertions(+), 10 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java 
b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
index fac78417da..1ba1933fc7 100644
--- a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
@@ -65,6 +65,8 @@
 import java.nio.charset.Charset;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.time.DateTimeException;
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.IllformedLocaleException;
@@ -403,21 +405,34 @@ public static SqlUuidLiteral parseUuidLiteral(String s, 
SqlParserPos pos) {
 
   public static SqlTimestampTzLiteral parseTimestampTzLiteral(
       String s, SqlParserPos pos) {
-    // We expect the string to end in a timezone.
-    int lastSpace = s.lastIndexOf(" ");
-    if (lastSpace >= 0) {
+    // We expect the string to to contain exactly two spaces:
+    // - one between date and time
+    // - one between time and timezone
+    long spaces = s.chars().filter(c -> c == ' ').count();
+    if (spaces == 2) {
+      int lastSpace = s.lastIndexOf(" ");
       final String timeZone = s.substring(lastSpace + 1);
       final String timestamp = s.substring(0, lastSpace);
-      TimeZone tz = TimeZone.getTimeZone(timeZone);
-      if (tz != null) {
-        SqlTimestampLiteral ts = parseTimestampLiteral(SqlTypeName.TIMESTAMP, 
timestamp, pos);
-        TimestampWithTimeZoneString tsz = new 
TimestampWithTimeZoneString(ts.getTimestamp(), tz);
-        return SqlLiteral.createTimestamp(tsz, ts.getPrec(), pos);
+      try {
+        ZoneId zoneId = ZoneId.of(timeZone);
+        TimeZone tz = TimeZone.getTimeZone(zoneId);
+        if (tz != null) {
+          SqlTimestampLiteral ts = 
parseTimestampLiteral(SqlTypeName.TIMESTAMP, timestamp, pos);
+          TimestampWithTimeZoneString tsz = new 
TimestampWithTimeZoneString(ts.getTimestamp(), tz);
+          return SqlLiteral.createTimestamp(tsz, ts.getPrec(), pos);
+        }
+      } catch (DateTimeException e) {
+        String message = e.getMessage();
+        if (message == null) {
+          message = "Error parsing TIME ZONE";
+        }
+        throw SqlUtil.newContextException(pos,
+            RESOURCE.illegalLiteral("TIMESTAMP WITH TIME ZONE", s, message));
       }
     }
     throw SqlUtil.newContextException(pos,
         RESOURCE.illegalLiteral("TIMESTAMP WITH TIME ZONE", s,
-            RESOURCE.badFormat(DateTimeUtils.TIMESTAMP_FORMAT_STRING).str()));
+            RESOURCE.badFormat(DateTimeUtils.TIMESTAMP_FORMAT_STRING + " 
zone").str()));
   }
 
   private static SqlTimestampLiteral parseTimestampLiteral(SqlTypeName 
typeName,
diff --git 
a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserUtilTest.java 
b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserUtilTest.java
index ca1293c3dd..03de04f860 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserUtilTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserUtilTest.java
@@ -17,12 +17,17 @@
 package org.apache.calcite.sql.parser;
 
 import org.apache.calcite.avatica.util.TimeUnit;
+import org.apache.calcite.runtime.CalciteContextException;
 import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.SqlTimestampTzLiteral;
 
 import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasToString;
+import static org.junit.jupiter.api.Assertions.fail;
 
 /**
  * Tests {@link SqlParserUtil}. Currently, this test focuses on tests for the 
methods that work
@@ -43,6 +48,52 @@ public class SqlParserUtilTest {
     assertThat(SqlParserUtil.intervalToMillis("2", qualifier), 
equalTo(120_000L));
   }
 
+  @Test void testTimestampWithTimeZone() {
+    SqlParserPos pos = new SqlParserPos(2, 3);
+    SqlTimestampTzLiteral lit =
+        SqlParserUtil.parseTimestampTzLiteral("2020-01-01 10:10:10 GMT", pos);
+    assertThat(lit, hasToString("TIMESTAMP_TZ '2020-01-01 10:10:10 GMT'"));
+
+    lit = SqlParserUtil.parseTimestampTzLiteral("2020-01-01 10:10:10 UTC", 
pos);
+    assertThat(lit, hasToString("TIMESTAMP_TZ '2020-01-01 10:10:10 UTC'"));
+
+    lit = SqlParserUtil.parseTimestampTzLiteral("2020-01-01 10:10:10 
America/Los_Angeles", pos);
+    assertThat(lit, hasToString("TIMESTAMP_TZ '2020-01-01 10:10:10 
America/Los_Angeles'"));
+
+    lit = SqlParserUtil.parseTimestampTzLiteral("2020-01-01 10:10:10 +00:00", 
pos);
+    assertThat(lit, hasToString("TIMESTAMP_TZ '2020-01-01 10:10:10 UTC'"));
+
+    lit = SqlParserUtil.parseTimestampTzLiteral("2020-01-01 10:10:10 Z", pos);
+    assertThat(lit, hasToString("TIMESTAMP_TZ '2020-01-01 10:10:10 UTC'"));
+
+    lit = SqlParserUtil.parseTimestampTzLiteral("2020-01-01 10:10:10 -00:30", 
pos);
+    assertThat(lit, hasToString("TIMESTAMP_TZ '2020-01-01 10:10:10 
GMT-00:30'"));
+
+    lit = SqlParserUtil.parseTimestampTzLiteral("2020-01-01 10:10:10 +05:45", 
pos);
+    assertThat(lit, hasToString("TIMESTAMP_TZ '2020-01-01 10:10:10 
GMT+05:45'"));
+
+    // Test case for [CALCITE-7527] SqlParserUtil.parseTimestampTzLiteral does 
not validate timezone
+    // https://issues.apache.org/jira/browse/CALCITE-7527
+    try {
+      SqlParserUtil.parseTimestampTzLiteral("2020-06-21 
14:23:44.123654+00:00", pos);
+      fail("Should be unreachable");
+    } catch (CalciteContextException ex) {
+      assertThat(
+          ex.getMessage(), is("At line 2, column 3: Illegal TIMESTAMP WITH 
TIME ZONE literal "
+              + "'2020-06-21 14:23:44.123654+00:00': not in format 'yyyy-MM-dd 
HH:mm:ss zone'"));
+    }
+
+    try {
+      SqlParserUtil.parseTimestampTzLiteral("2020-06-21 14:23:44.123654 
incorrect_zone", pos);
+      fail("Should be unreachable");
+    } catch (CalciteContextException ex) {
+      assertThat(
+          ex.getMessage(), is("At line 2, column 3: Illegal TIMESTAMP WITH 
TIME ZONE literal "
+              + "'2020-06-21 14:23:44.123654 incorrect_zone': Unknown "
+              + "time-zone ID: incorrect_zone"));
+    }
+  }
+
   @Test void testMinuteToSecondIntervalToMillis() {
     final SqlIntervalQualifier qualifier =
         new SqlIntervalQualifier(TimeUnit.MINUTE, TimeUnit.SECOND, POSITION);
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 36ef279b53..8a4a970e12 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -299,7 +299,7 @@ static SqlOperatorTable operatorTableFor(SqlLibrary 
library) {
     expr("^TIMESTAMP WITH LOCAL TIME ZONE '12-21-99, 12:30:00'^")
         .fails("(?s).*Illegal TIMESTAMP WITH LOCAL TIME ZONE literal.*");
     expr("^TIMESTAMP WITH TIME ZONE '12-21-99, 12:30:00'^")
-        .fails("(?s).*Illegal TIMESTAMP literal.*");
+        .fails("(?s).*Illegal TIMESTAMP WITH TIME ZONE literal.*");
   }
 
   /**

Reply via email to