[NO ISSUE][FUN] Improve quarter printing/parsing in date functions
- user model changes: no
- storage format changes: no
- interface changes: no
Details:
- Add 'QQ' format to print_date/print_datetime()
functions to print quarter of year with leading 0
- Add 'Q' and 'QQ' formats to parse_date/parse_datetime()
functions to parse quarter of year
- Add testcases and update documentation
Change-Id: Ie71a1f59ab96ed0382255109d1f59bfa6e50ad82
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/13544
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ian Maxon <imaxon@uci.edu>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/temporal/parse_03/parse_03.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/temporal/parse_03/parse_03.6.query.sqlpp
new file mode 100644
index 0000000..522a8dc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/temporal/parse_03/parse_03.6.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Parse year and quarter
+ */
+
+select r, d, dt
+from range(1, 4) r
+let d = parse_date("2020-" || string(r) , "YYYY-Q"),
+ dt = parse_datetime("2021-0" || string(r), "YYYY-QQ")
+order by r;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/temporal/print_01/print_01.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/temporal/print_01/print_01.1.query.sqlpp
index ae1db50..1579c95 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/temporal/print_01/print_01.1.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/temporal/print_01/print_01.1.query.sqlpp
@@ -19,8 +19,9 @@
/* Print quarter */
-select p, count(*) cnt
+select p1, p2, count(*) cnt
from range(0, 365) r
let d = date_from_unix_time_in_days(unix_time_from_date_in_days(date("2020-01-01")) + r),
- p = print_date(d, "YYYY-Q")
-group by p;
+ p1 = print_date(d, "YYYY-Q"),
+ p2 = print_date(d, "YYYY-QQ")
+group by p1, p2;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/temporal/parse_03/parse_03.6.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/temporal/parse_03/parse_03.6.adm
new file mode 100644
index 0000000..5a7c485
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/temporal/parse_03/parse_03.6.adm
@@ -0,0 +1,4 @@
+{ "r": 1, "d": date("2020-01-01"), "dt": datetime("2021-01-01T00:00:00.000") }
+{ "r": 2, "d": date("2020-04-01"), "dt": datetime("2021-04-01T00:00:00.000") }
+{ "r": 3, "d": date("2020-07-01"), "dt": datetime("2021-07-01T00:00:00.000") }
+{ "r": 4, "d": date("2020-10-01"), "dt": datetime("2021-10-01T00:00:00.000") }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/temporal/print_01/print_01.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/temporal/print_01/print_01.1.adm
index facff48..83e0610 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/temporal/print_01/print_01.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/temporal/print_01/print_01.1.adm
@@ -1,4 +1,4 @@
-{ "p": "2020-1", "cnt": 91 }
-{ "p": "2020-2", "cnt": 91 }
-{ "p": "2020-3", "cnt": 92 }
-{ "p": "2020-4", "cnt": 92 }
\ No newline at end of file
+{ "p1": "2020-1", "p2": "2020-01", "cnt": 91 }
+{ "p1": "2020-2", "p2": "2020-02", "cnt": 91 }
+{ "p1": "2020-3", "p2": "2020-03", "cnt": 92 }
+{ "p1": "2020-4", "p2": "2020-04", "cnt": 92 }
\ No newline at end of file
diff --git a/asterixdb/asterix-doc/src/main/markdown/builtins/7_temporal.md b/asterixdb/asterix-doc/src/main/markdown/builtins/7_temporal.md
index 5c11b46..a1cdda2 100644
--- a/asterixdb/asterix-doc/src/main/markdown/builtins/7_temporal.md
+++ b/asterixdb/asterix-doc/src/main/markdown/builtins/7_temporal.md
@@ -648,6 +648,8 @@
* `a` am/pm
* `z` timezone (parsed and ignored)
* `Y` year
+ * `Q` quarter of year (1-4)
+ * `QQ` quarter of year (01-04)
* `M` month
* `D` day
* `EEE` weekday (abbreviated name, parsed and ignored)
@@ -685,6 +687,8 @@
* `n` (or `S`) milliseconds
* `a` am/pm
* `Y` year
+ * `Q` quarter of year (1-4)
+ * `QQ` quarter of year (01-04)
* `M` month
* `MMM` month (abbreviated name)
* `MMMM` month (full name)
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/base/temporal/DateTimeFormatUtils.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/base/temporal/DateTimeFormatUtils.java
index 73ba3d8..db771f9 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/base/temporal/DateTimeFormatUtils.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/base/temporal/DateTimeFormatUtils.java
@@ -37,7 +37,7 @@
* <p/>
* - <b>Y</b>: a digit for the year field. At most 4 year format characters are allowed for a valid format string.<br/>
* - <b>M</b>: a digit or character for the month field. At most 3 month format characters are allowed for a valid format string. When three month format characters are used, the shorten month names (like JAN, FEB etc.) are expected in the string to be parsed. Otherwise digits are expected.<br/>
- * - <b>Q</b>: (print-only) a digit for the quarter field (1-4). At most 1 format character is allowed.<br/>
+ * - <b>Q</b>: a digit for the quarter field (1-4). At most 2 format characters are allowed.<br/>
* - <b>D</b>: a digit for the day field. At most 2 day format characters are allowed.<br/>
* - <b>h</b>: a digit for the hour field. At most 2 hour format characters are allowed.<br/>
* - <b>m</b>: a digit for the minute field. At most 2 minute format characters are allowed.<br/>
@@ -100,7 +100,7 @@
private static final char WEEKDAY_CHAR = 'E';
private static final int MAX_YEAR_CHARS = 4;
- private static final int MAX_QUARTER_CHARS = 1;
+ private static final int MAX_QUARTER_CHARS = 2;
private static final int MAX_MONTH_CHARS = 4;
private static final int MAX_DAY_CHARS_PARSE = 2;
private static final int MAX_DAY_CHARS_PRINT = 3; // + DDD = Day of Year
@@ -341,6 +341,13 @@
formatPointer += pointerMove;
formatCharCopies += pointerMove;
break;
+ case QUARTER_CHAR:
+ processState = DateTimeProcessState.QUARTER;
+ pointerMove = parseFormatField(format, formatStart, formatLength, formatPointer, QUARTER_CHAR,
+ MAX_QUARTER_CHARS);
+ formatPointer += pointerMove;
+ formatCharCopies += pointerMove;
+ break;
case MONTH_CHAR:
processState = DateTimeProcessState.MONTH;
pointerMove = parseFormatField(format, formatStart, formatLength, formatPointer, MONTH_CHAR,
@@ -452,6 +459,7 @@
switch (processState) {
case YEAR:
+ case QUARTER:
case MONTH:
case DAY:
if (parseMode == DateTimeParseMode.TIME_ONLY) {
@@ -529,6 +537,52 @@
day = parsedValue;
}
break;
+ case QUARTER:
+ // the month is in the number format
+ parsedValue = 0;
+ int processedQuarterFieldsCount = 0;
+ for (int i = 0; i < formatCharCopies; i++) {
+ if (data[dataStart + dataStringPointer] < '0' || data[dataStart + dataStringPointer] > '9') {
+ if (raiseParseDataError) {
+ throw new AsterixTemporalTypeParseException("Unexpected char for quarter field at "
+ + (dataStart + dataStringPointer) + ": " + data[dataStart + dataStringPointer]);
+ } else {
+ return false;
+ }
+ }
+ parsedValue = parsedValue * 10 + (data[dataStart + dataStringPointer] - '0');
+ dataStringPointer++;
+ if (processedQuarterFieldsCount++ > 2) {
+ if (raiseParseDataError) {
+ throw new AsterixTemporalTypeParseException("Unexpected char for quarter field at "
+ + (dataStart + dataStringPointer) + ": " + data[dataStart + dataStringPointer]);
+ } else {
+ return false;
+ }
+ }
+ }
+ // if there are more than 2 digits for the quarter string
+ while (processedQuarterFieldsCount < 2 && dataStringPointer < dataLength
+ && data[dataStart + dataStringPointer] >= '0'
+ && data[dataStart + dataStringPointer] <= '9') {
+ parsedValue = parsedValue * 10 + (data[dataStart + dataStringPointer] - '0');
+ dataStringPointer++;
+ processedQuarterFieldsCount++;
+ }
+ if (parsedValue == 0) {
+ if (raiseParseDataError) {
+ throw new AsterixTemporalTypeParseException(
+ "Incorrect quarter value at " + (dataStart + dataStringPointer));
+ } else {
+ return false;
+ }
+ }
+ month = (parsedValue - 1) * 3 + 1;
+ // Allow day to be missing if we parsed quarter
+ if (day == 0) {
+ day = GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.DAY.ordinal()];
+ }
+ break;
case MONTH:
if (formatCharCopies >= 3) {
// the month is in the text format