This is an automated email from the ASF dual-hosted git repository.
rubenql 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 8ffcc2acd6 [CALCITE-6226] Wrong ISOWEEK and no ISOYEAR on BigQuery
FORMAT_DATE
8ffcc2acd6 is described below
commit 8ffcc2acd6bc865fb8a98d5ab1c96e65e5b9f1d3
Author: rrueda <[email protected]>
AuthorDate: Fri Jan 26 09:24:15 2024 -0300
[CALCITE-6226] Wrong ISOWEEK and no ISOYEAR on BigQuery FORMAT_DATE
The ISOWEEK format function was not setting the minimalDaysInFirstWeek
to 4.
To avoid having to set the calendar week definition fields in each
format function, a new calendar instance configured with the iso8601
settings was added.
The format elements for the ISOYEAR with the century (%G) and without
it (%g) were added.
Also, the weekday with Monday as first day of week (%u) was fixed.
---
.../calcite/util/format/FormatElementEnum.java | 62 ++++++++++++++++++----
.../apache/calcite/util/format/FormatModels.java | 7 ++-
.../calcite/util/format/FormatElementEnumTest.java | 36 +++++++++++++
.../org/apache/calcite/test/SqlOperatorTest.java | 30 +++++++++--
4 files changed, 120 insertions(+), 15 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
b/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
index b78d88c91e..0e289cf678 100644
--- a/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
+++ b/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java
@@ -49,7 +49,10 @@ public enum FormatElementEnum implements FormatElement {
sb.append(String.format(Locale.ROOT, "%2d", calendar.get(Calendar.YEAR)
/ 100 + 1));
}
},
- D("F", "The weekday (Monday as the first day of the week) as a decimal
number (1-7)") {
+ D("", "The weekday (Sunday as the first day of the week) as a decimal number
(1-7)") {
+ @Override public void toPattern(StringBuilder sb) throws
UnsupportedOperationException {
+ throwToPatternNotImplemented();
+ }
@Override public void format(StringBuilder sb, Date date) {
final Calendar calendar = Work.get().calendar;
calendar.setTime(date);
@@ -207,17 +210,44 @@ public enum FormatElementEnum implements FormatElement {
sb.append(String.format(Locale.ROOT, "%02d",
calendar.get(Calendar.HOUR_OF_DAY)));
}
},
- // TODO: Ensure ISO 8601 for parsing
- IW("w", "The ISO 8601 week number of the year (Monday as the first day of
the week) "
- + "as a decimal number (01-53)") {
+ ID("u", "The weekday (Monday as the first day of the week) as a decimal
number (1-7)") {
@Override public void format(StringBuilder sb, Date date) {
- // TODO: ensure this is isoweek
final Calendar calendar = Work.get().calendar;
calendar.setTime(date);
- calendar.setFirstDayOfWeek(Calendar.MONDAY);
+ int weekDay = calendar.get(Calendar.DAY_OF_WEEK);
+ // Converting Sun(1)...Sat(7) to Mon(1)...Sun(7)
+ sb.append(weekDay == 1 ? 7 : weekDay - 1);
+ }
+ },
+ IW("", "The ISO 8601 week number of the year (Monday as the first day of the
week) "
+ + "as a decimal number (01-53). If the week containing January 1 has
four or more days "
+ + "in the new year, then it is week 1; otherwise it is week 53 of the
previous year, "
+ + "and the next week is week 1.") {
+ @Override public void toPattern(StringBuilder sb) throws
UnsupportedOperationException {
+ throwToPatternNotImplemented();
+ }
+ @Override public void format(StringBuilder sb, Date date) {
+ final Calendar calendar = Work.get().iso8601Calendar;
+ calendar.setTime(date);
sb.append(String.format(Locale.ROOT, "%02d",
calendar.get(Calendar.WEEK_OF_YEAR)));
}
},
+ IYY("YY", "The ISO 8601 year without century as a decimal number. Each ISO
year begins on "
+ + "the Monday before the first Thursday of the Gregorian calendar
year.") {
+ @Override public void format(StringBuilder sb, Date date) {
+ final Calendar calendar = Work.get().iso8601Calendar;
+ calendar.setTime(date);
+ sb.append(String.format(Locale.ROOT, "%02d", calendar.getWeekYear() %
100));
+ }
+ },
+ IYYYY("YYYY", "The ISO 8601 year with century as a decimal number. Each ISO
year begins on "
+ + "the Monday before the first Thursday of the Gregorian calendar
year.") {
+ @Override public void format(StringBuilder sb, Date date) {
+ final Calendar calendar = Work.get().iso8601Calendar;
+ calendar.setTime(date);
+ sb.append(calendar.getWeekYear());
+ }
+ },
MI("m", "The minute as a decimal number (00-59)") {
@Override public void format(StringBuilder sb, Date date) {
final Calendar calendar = Work.get().calendar;
@@ -278,9 +308,8 @@ public enum FormatElementEnum implements FormatElement {
}
},
Q("", "The quarter as a decimal number (1-4)") {
- // TODO: Allow parsing of quarters.
@Override public void toPattern(StringBuilder sb) throws
UnsupportedOperationException {
- throw new UnsupportedOperationException("Cannot convert 'Q'
FormatElement to Java pattern");
+ throwToPatternNotImplemented();
}
@Override public void format(StringBuilder sb, Date date) {
final Calendar calendar = Work.get().calendar;
@@ -366,7 +395,6 @@ public enum FormatElementEnum implements FormatElement {
@Override public void format(StringBuilder sb, Date date) {
final Calendar calendar = Work.get().calendar;
calendar.setTime(date);
- calendar.setFirstDayOfWeek(Calendar.SUNDAY);
sb.append(String.format(Locale.ROOT, "%02d",
calendar.get(Calendar.WEEK_OF_YEAR)));
}
},
@@ -422,6 +450,11 @@ public enum FormatElementEnum implements FormatElement {
sb.append(this.javaFmt);
}
+ final void throwToPatternNotImplemented() {
+ throw new UnsupportedOperationException("Cannot convert '"
+ + this.name().toUpperCase(Locale.ROOT) + "' FormatElement to Java
pattern");
+ }
+
/** Work space. Provides a value for each mutable data structure that might
* be needed by a format element. Ensures thread-safety. */
static class Work {
@@ -433,8 +466,15 @@ public enum FormatElementEnum implements FormatElement {
return THREAD_WORK.get();
}
- final Calendar calendar =
- Calendar.getInstance(DateTimeUtils.DEFAULT_ZONE, Locale.ROOT);
+ final Calendar calendar = new Calendar.Builder()
+ .setWeekDefinition(Calendar.SUNDAY, 1)
+ .setTimeZone(DateTimeUtils.DEFAULT_ZONE)
+ .setLocale(Locale.ROOT).build();
+
+ final Calendar iso8601Calendar = new Calendar.Builder()
+ .setCalendarType("iso8601")
+ .setTimeZone(DateTimeUtils.DEFAULT_ZONE)
+ .setLocale(Locale.ROOT).build();
final DateFormat mmmFormat = new SimpleDateFormat(MON.javaFmt, Locale.US);
/* Need to sse Locale.US instead of Locale.ROOT, because Locale.ROOT
diff --git
a/core/src/main/java/org/apache/calcite/util/format/FormatModels.java
b/core/src/main/java/org/apache/calcite/util/format/FormatModels.java
index a46b091ab8..d9c68950f7 100644
--- a/core/src/main/java/org/apache/calcite/util/format/FormatModels.java
+++ b/core/src/main/java/org/apache/calcite/util/format/FormatModels.java
@@ -51,7 +51,10 @@ import static
org.apache.calcite.util.format.FormatElementEnum.FF8;
import static org.apache.calcite.util.format.FormatElementEnum.FF9;
import static org.apache.calcite.util.format.FormatElementEnum.HH12;
import static org.apache.calcite.util.format.FormatElementEnum.HH24;
+import static org.apache.calcite.util.format.FormatElementEnum.ID;
import static org.apache.calcite.util.format.FormatElementEnum.IW;
+import static org.apache.calcite.util.format.FormatElementEnum.IYY;
+import static org.apache.calcite.util.format.FormatElementEnum.IYYYY;
import static org.apache.calcite.util.format.FormatElementEnum.MI;
import static org.apache.calcite.util.format.FormatElementEnum.MM;
import static org.apache.calcite.util.format.FormatElementEnum.MON;
@@ -139,6 +142,8 @@ public class FormatModels {
map.put("%F",
compositeElement("The date in the format %Y-%m-%d.", YYYY,
literalElement("-"), MM,
literalElement("-"), DD));
+ map.put("%G", IYYYY);
+ map.put("%g", IYY);
map.put("%H", HH24);
map.put("%I", HH12);
map.put("%j", DDD);
@@ -153,7 +158,7 @@ public class FormatModels {
map.put("%T",
compositeElement("The time in the format %H:%M:%S.",
HH24, literalElement(":"), MI, literalElement(":"), SS));
- map.put("%u", D);
+ map.put("%u", ID);
map.put("%V", IW);
map.put("%W", WW);
map.put("%x",
diff --git
a/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java
b/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java
index c77baaeaaf..066bcafaad 100644
---
a/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java
+++
b/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java
@@ -98,8 +98,44 @@ class FormatElementEnumTest {
assertFormatElement(FormatElementEnum.FF9, "2014-09-30T10:00:00.123456Z",
"123000000");
}
+ @Test void testID() {
+ assertFormatElement(FormatElementEnum.ID, "2014-09-30T10:00:00Z", "2");
+ }
+
@Test void testIW() {
assertFormatElement(FormatElementEnum.IW, "2014-09-30T10:00:00Z", "40");
+ // Test case for [CALCITE-6226]
https://issues.apache.org/jira/browse/CALCITE-6226
+ // Edge case where ISO WEEK != WEEK
+ assertFormatElement(FormatElementEnum.IW, "2023-01-01T10:00:00Z", "52");
+ assertFormatElement(FormatElementEnum.IW, "2023-01-02T10:00:00Z", "01");
+ // Edge case where ISO WEEK != WEEK for Julian dates - motivated by
[CALCITE-6252]
+ assertFormatElement(FormatElementEnum.IW, "0001-01-01T10:00:00Z", "01");
+ assertFormatElement(FormatElementEnum.IW, "0005-01-01T10:00:00Z", "53");
+ assertFormatElement(FormatElementEnum.IW, "0005-01-03T10:00:00Z", "01");
+ }
+
+ @Test void testIYY() {
+ assertFormatElement(FormatElementEnum.IYY, "2014-09-30T10:00:00Z", "14");
+ // Test case for [CALCITE-6226]
https://issues.apache.org/jira/browse/CALCITE-6226
+ // Edge case where ISO WEEK YEAR != YEAR
+ assertFormatElement(FormatElementEnum.IYY, "2023-01-01T10:00:00Z", "22");
+ assertFormatElement(FormatElementEnum.IYY, "2023-01-02T10:00:00Z", "23");
+ // Edge case where ISO WEEK YEAR != YEAR for Julian dates - motivated by
[CALCITE-6252]
+ assertFormatElement(FormatElementEnum.IYY, "0001-01-01T10:00:00Z", "01");
+ assertFormatElement(FormatElementEnum.IYY, "0005-01-01T10:00:00Z", "04");
+ assertFormatElement(FormatElementEnum.IYY, "0005-01-03T10:00:00Z", "05");
+ }
+
+ @Test void testIYYYY() {
+ assertFormatElement(FormatElementEnum.IYYYY, "2014-09-30T10:00:00Z",
"2014");
+ // Test case for [CALCITE-6226]
https://issues.apache.org/jira/browse/CALCITE-6226
+ // Edge case where ISO WEEK YEAR != YEAR
+ assertFormatElement(FormatElementEnum.IYYYY, "2023-01-01T10:00:00Z",
"2022");
+ assertFormatElement(FormatElementEnum.IYYYY, "2023-01-02T10:00:00Z",
"2023");
+ // Edge case where ISO WEEK YEAR != YEAR for Julian dates - motivated by
[CALCITE-6252]
+ assertFormatElement(FormatElementEnum.IYYYY, "0001-01-01T10:00:00Z", "1");
+ assertFormatElement(FormatElementEnum.IYYYY, "0005-01-01T10:00:00Z", "4");
+ assertFormatElement(FormatElementEnum.IYYYY, "0005-01-03T10:00:00Z", "5");
}
@Test void testMM() {
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index c5ad9a4b64..1b3f720d9a 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -4859,7 +4859,7 @@ public class SqlOperatorTest {
"2",
"VARCHAR NOT NULL");
f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'IW')",
- "23",
+ "22",
"VARCHAR NOT NULL");
f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'YYYY')",
"2022",
@@ -5195,7 +5195,7 @@ public class SqlOperatorTest {
f.checkFails("to_date('ABCD', 'YYYY-MM-DD')",
"java.sql.SQLException: Invalid format: 'YYYY-MM-DD' for datetime
string: 'ABCD'.",
true);
- f.checkFails("to_date('2022-06-03', 'Invalid')",
+ f.checkFails("to_date('2022-06-03', 'I')",
"Illegal pattern character 'I'",
true);
f.checkNull("to_date(NULL, 'YYYY-MM-DD')");
@@ -5265,7 +5265,7 @@ public class SqlOperatorTest {
f.checkFails("to_timestamp('ABCD', 'YYYY-MM-DD HH24:MI:SS')",
"java.sql.SQLException: Invalid format: 'YYYY-MM-DD HH24:MI:SS' for
datetime string: 'ABCD'.",
true);
- f.checkFails("to_timestamp('2022-06-03 18:34:56', 'Invalid')",
+ f.checkFails("to_timestamp('2022-06-03 18:34:56', 'I')",
"Illegal pattern character 'I'",
true);
f.checkNull("to_timestamp(NULL, 'YYYY-MM-DD HH24:MI:SS')");
@@ -14441,6 +14441,30 @@ public class SqlOperatorTest {
f.checkScalar("FORMAT_DATE('%x', DATE '2008-12-25')",
"12/25/08",
"VARCHAR NOT NULL");
+ f.checkScalar("FORMAT_DATE('%g-%V', DATE '2001-01-01')",
+ "01-01",
+ "VARCHAR NOT NULL");
+ // Test case for [CALCITE-6226]
https://issues.apache.org/jira/browse/CALCITE-6226
+ f.checkScalar("FORMAT_DATE('%G-%V', DATE '2023-01-01')",
+ "2022-52",
+ "VARCHAR NOT NULL");
+ f.checkScalar("FORMAT_DATE('%g-%V', DATE '2023-01-01')",
+ "22-52",
+ "VARCHAR NOT NULL");
+ // For Julian dates - motivated by [CALCITE-6252]
+ f.checkScalar("FORMAT_DATE('%G-%V', DATE '0005-01-01')",
+ "4-53",
+ "VARCHAR NOT NULL");
+ f.checkScalar("FORMAT_DATE('%g-%V', DATE '0005-01-01')",
+ "04-53",
+ "VARCHAR NOT NULL");
+ // End Test case for
+ f.checkScalar("FORMAT_DATE('%u', DATE '2024-01-01')",
+ "1",
+ "VARCHAR NOT NULL");
+ f.checkScalar("FORMAT_DATE('%u', DATE '2024-01-07')",
+ "7",
+ "VARCHAR NOT NULL");
f.checkNull("FORMAT_DATE('%x', CAST(NULL AS DATE))");
f.checkNull("FORMAT_DATE('%b-%d-%Y', CAST(NULL AS DATE))");
f.checkNull("FORMAT_DATE('%b %Y', CAST(NULL AS DATE))");