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

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


The following commit(s) were added to refs/heads/main by this push:
     new c0cb4b71f [CALCITE-6248] Illegal dates are accepted by casts
c0cb4b71f is described below

commit c0cb4b71f187e688e3dfd579111c3c646b9c47af
Author: Mihai Budiu <[email protected]>
AuthorDate: Sat Mar 23 18:08:29 2024 -0700

    [CALCITE-6248] Illegal dates are accepted by casts
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../apache/calcite/avatica/util/DateTimeUtils.java | 40 ++++++++++++++++++++--
 .../calcite/avatica/util/DateTimeUtilsTest.java    | 22 ++++++++++--
 2 files changed, 56 insertions(+), 6 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java 
b/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
index bb9f233a7..a71f3e52e 100644
--- a/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
+++ b/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
@@ -63,6 +63,10 @@ public class DateTimeUtils {
   private static final Pattern ISO_DATE_PATTERN =
       Pattern.compile("^(\\d{4})-([0]\\d|1[0-2])-([0-2]\\d|3[01])$");
 
+  /** Regex for lenient date patterns. */
+  private static final Pattern LENIENT_DATE_PATTERN =
+      Pattern.compile("^\\s*(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s*$");
+
   /** Regex for time, HH:MM:SS. */
   private static final Pattern ISO_TIME_PATTERN =
       Pattern.compile("^([0-2]\\d):[0-5]\\d:[0-5]\\d(\\.\\d*)*$");
@@ -653,6 +657,7 @@ public class DateTimeUtils {
   }
 
   public static int dateStringToUnixDate(String s) {
+    validateLenientDate(s);
     int hyphen1 = s.indexOf('-');
     int y;
     int m;
@@ -739,15 +744,44 @@ public class DateTimeUtils {
     return r;
   }
 
+  /** Check that the combination year, month, date forms a legal date. */
+  static void checkLegalDate(int year, int month, int day, String full) {
+    if (day > daysInMonth(year, month)) {
+      throw fieldOutOfRange("DAY", full);
+    }
+    if (month < 1 || month > 12) {
+      throw fieldOutOfRange("MONTH", full);
+    }
+    if (year <= 0) {
+      // Year 0 is not really a legal value.
+      throw fieldOutOfRange("YEAR", full);
+    }
+  }
+
+  /** Lenient date validation.  This accepts more date strings
+   * than validateDate: it does not insist on having two-digit
+   * values for days and months, and accepts spaces around the value.
+   * @param s     A string representing a date.
+   */
+  private static void validateLenientDate(String s) {
+    Matcher matcher = LENIENT_DATE_PATTERN.matcher(s);
+    if (matcher.find()) {
+      int year = Integer.parseInt(matcher.group(1));
+      int month = Integer.parseInt(matcher.group(2));
+      int day = Integer.parseInt(matcher.group(3));
+      checkLegalDate(year, month, day, s);
+    } else {
+      throw invalidType("DATE", s);
+    }
+  }
+
   private static void validateDate(String s, String full) {
     Matcher matcher = ISO_DATE_PATTERN.matcher(s);
     if (matcher.find()) {
       int year = Integer.parseInt(matcher.group(1));
       int month = Integer.parseInt(matcher.group(2));
       int day = Integer.parseInt(matcher.group(3));
-      if (day > daysInMonth(year, month)) {
-        throw fieldOutOfRange("DAY", full);
-      }
+      checkLegalDate(year, month, day, full);
     } else {
       throw invalidType("DATE", full);
     }
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java 
b/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
index 2cae4a743..b4e0203fb 100644
--- a/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
+++ b/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java
@@ -790,12 +790,10 @@ public class DateTimeUtilsTest {
     checkDateString("0200-01-01", d200);
     final int d100 = d200 - century + 1;
     checkDateString("0100-01-01", d100);
-    final int d000 = d100 - century;
-    checkDateString("0000-01-01", d000);
   }
 
   @Test public void testDateConversion() {
-    for (int i = 0; i < 4000; ++i) {
+    for (int i = 1; i < 4000; ++i) {
       for (int j = 1; j <= 12; ++j) {
         String date = String.format(Locale.ENGLISH, "%04d-%02d-28", i, j);
         assertThat(unixDateToString(ymdToUnixDate(i, j, 28)), is(date));
@@ -1681,6 +1679,24 @@ public class DateTimeUtilsTest {
    * Test exception is raised if date in inappropriate meaning.
    */
   @Test public void testBrokenDate() {
+    // Test case for https://issues.apache.org/jira/browse/CALCITE-6248
+    // [CALCITE-6248] Illegal dates are accepted by casts
+    assertThrows(() -> DateTimeUtils.dateStringToUnixDate("2023-02-29"),
+        IllegalArgumentException.class,
+        is("Value of DAY field is out of range in '2023-02-29'"));
+
+    // Test case for https://issues.apache.org/jira/browse/CALCITE-6248
+    // [CALCITE-6248] Illegal dates are accepted by casts
+    assertThrows(() -> DateTimeUtils.dateStringToUnixDate("2023-13-1"),
+        IllegalArgumentException.class,
+        is("Value of MONTH field is out of range in '2023-13-1'"));
+
+    // Test case for https://issues.apache.org/jira/browse/CALCITE-6248
+    // [CALCITE-6248] Illegal dates are accepted by casts
+    assertThrows(() -> DateTimeUtils.dateStringToUnixDate("0-1-1"),
+        IllegalArgumentException.class,
+        is("Value of YEAR field is out of range in '0-1-1'"));
+
     // 2023 is not a leap year
     assertThrows(() ->
             DateTimeUtils.timestampStringToUnixDate("2023-02-29 12:00:00.123"),

Reply via email to