You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by ku...@apache.org on 2019/08/22 09:16:53 UTC
[hive] branch master updated: HIVE-21580: Introduce ISO 8601 week
numbering SQL:2016 formats (Karen Coppage via Marta Kuczora)
This is an automated email from the ASF dual-hosted git repository.
kuczoram pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push:
new db59ec6 HIVE-21580: Introduce ISO 8601 week numbering SQL:2016 formats (Karen Coppage via Marta Kuczora)
db59ec6 is described below
commit db59ec6ecf57dad72588b7593fb516c3e810b5a2
Author: Karen Coppage <kc...@gmail.com>
AuthorDate: Thu Aug 22 11:16:02 2019 +0200
HIVE-21580: Introduce ISO 8601 week numbering SQL:2016 formats (Karen Coppage via Marta Kuczora)
---
.../format/datetime/HiveSqlDateTimeFormatter.java | 147 +++++++++++++++++++--
.../datetime/TestHiveSqlDateTimeFormatter.java | 77 ++++++++++-
2 files changed, 213 insertions(+), 11 deletions(-)
diff --git a/common/src/java/org/apache/hadoop/hive/common/format/datetime/HiveSqlDateTimeFormatter.java b/common/src/java/org/apache/hadoop/hive/common/format/datetime/HiveSqlDateTimeFormatter.java
index 9443e8e..9167a8d 100644
--- a/common/src/java/org/apache/hadoop/hive/common/format/datetime/HiveSqlDateTimeFormatter.java
+++ b/common/src/java/org/apache/hadoop/hive/common/format/datetime/HiveSqlDateTimeFormatter.java
@@ -74,7 +74,7 @@ import java.util.regex.Pattern;
* - "Delimiter" for numeric tokens means any non-numeric character or end of input.
* - The words token and pattern are used interchangeably.
*
- * A. Temporal tokens
+ * A.1. Numeric temporal tokens
* YYYY
* 4-digit year
* - For string to datetime conversion, prefix digits for 1, 2, and 3-digit inputs are obtained
@@ -199,6 +199,48 @@ import java.util.regex.Pattern;
* - 1st week starts on the 1st of the month and ends on the 7th, and so on.
* - Not allowed in string to datetime conversion.
*
+ * IYYY
+ * 4-digit ISO 8601 week-numbering year
+ * - Returns the year relating to the ISO week number (IW), which is the full week (Monday to
+ * Sunday) which contains January 4 of the Gregorian year.
+ * - Behaves similarly to YYYY in that for datetime to string conversion, prefix digits for 1, 2,
+ * and 3-digit inputs are obtained from current week-numbering year.
+ * - For string to datetime conversion, requires IW and ID|DAY|DY. Conflicts with all other date
+ * patterns (see "List of Date-Based Patterns").
+ *
+ * IYY
+ * Last 3 digits of ISO 8601 week-numbering year
+ * - See IYYY.
+ * - Behaves similarly to YYY in that for datetime to string conversion, prefix digit is obtained
+ * from current week-numbering year and can accept 1 or 2-digit input.
+ *
+ * IY
+ * Last 2 digits of ISO 8601 week-numbering year
+ * - See IYYY.
+ * - Behaves similarly to YY in that for datetime to string conversion, prefix digits are obtained
+ * from current week-numbering year and can accept 1-digit input.
+ *
+ * I
+ * Last digit of ISO 8601 week-numbering year
+ * - See IYYY.
+ * - Behaves similarly to Y in that for datetime to string conversion, prefix digits are obtained
+ * from current week-numbering year.
+ *
+ * IW
+ * ISO 8601 week of year (1-53)
+ * - Begins on the Monday closest to January 1 of the year.
+ * - For string to datetime conversion, if the input week does not exist in the input year, an
+ * error will be thrown. e.g. the 2019 week-year has 52 weeks; with pattern="iyyy-iw-id"
+ * input="2019-53-2" is not accepted.
+ * - For string to datetime conversion, requires IYYY|IYY|IY|I and ID|DAY|DY. Conflicts with all other
+ * date patterns (see "List of Date-Based Patterns").
+ *
+ * ID
+ * ISO 8601 day of week (1-7)
+ * - 1 is Monday, and so on.
+ * - For string to datetime conversion, requires IYYY|IYY|IY|I and IW. Conflicts with all other
+ * date patterns (see "List of Date-Based Patterns").
+ *
* A.2. Character temporals
* Temporal elements, but spelled out.
* - For datetime to string conversion, the pattern's case must match one of the listed formats
@@ -241,7 +283,7 @@ import java.util.regex.Pattern;
* - day = sunday
* - For string to datetime conversion, neither the case of the pattern nor the case of the input
* are taken into account.
- * - Not allowed in string to datetime conversion.
+ * - Not allowed in string to datetime conversion except with IYYY|IYY|IY|I and IW.
*
* DY|Dy|dy
* Abbreviated name of day of week
@@ -252,7 +294,7 @@ import java.util.regex.Pattern;
* - dy = sun
* - For string to datetime conversion, neither the case of the pattern nor the case of the input
* are taken into account.
- * - Not allowed in string to datetime conversion.
+ * - Not allowed in string to datetime conversion except with IYYY|IYY|IY|I and IW.
*
* B. Time zone tokens
* TZH
@@ -346,6 +388,7 @@ public class HiveSqlDateTimeFormatter implements Serializable {
public static final int AM = 0;
public static final int PM = 1;
private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("MMM");
+ public static final DateTimeFormatter DAY_OF_WEEK_FORMATTER = DateTimeFormatter.ofPattern("EEE");
private String pattern;
private List<Token> tokens = new ArrayList<>();
private boolean formatExact = false;
@@ -374,6 +417,9 @@ public class HiveSqlDateTimeFormatter implements Serializable {
.put("p.m.", ChronoField.AMPM_OF_DAY).put("pm", ChronoField.AMPM_OF_DAY)
.put("ww", ChronoField.ALIGNED_WEEK_OF_YEAR).put("w", ChronoField.ALIGNED_WEEK_OF_MONTH)
.put("q", IsoFields.QUARTER_OF_YEAR)
+ .put("iyyy", IsoFields.WEEK_BASED_YEAR).put("iyy", IsoFields.WEEK_BASED_YEAR)
+ .put("iy", IsoFields.WEEK_BASED_YEAR).put("i", IsoFields.WEEK_BASED_YEAR)
+ .put("iw", IsoFields.WEEK_OF_WEEK_BASED_YEAR).put("id", ChronoField.DAY_OF_WEEK)
.build();
private static final Map<String, TemporalField> CHARACTER_TEMPORAL_TOKENS =
@@ -404,6 +450,11 @@ public class HiveSqlDateTimeFormatter implements Serializable {
.put("month", 9).put("day", 9).put("dy", 3)
.build();
+ private static final List<TemporalField> ISO_8601_TEMPORAL_FIELDS =
+ ImmutableList.of(ChronoField.DAY_OF_WEEK,
+ IsoFields.WEEK_OF_WEEK_BASED_YEAR,
+ IsoFields.WEEK_BASED_YEAR);
+
/**
* Represents broad categories of tokens.
*/
@@ -697,6 +748,7 @@ public class HiveSqlDateTimeFormatter implements Serializable {
ArrayList<TemporalField> temporalFields = new ArrayList<>();
ArrayList<TemporalUnit> timeZoneTemporalUnits = new ArrayList<>();
int roundYearCount=0, yearCount=0;
+ boolean containsIsoFields=false, containsGregorianFields=false;
for (Token token : tokens) {
if (token.temporalField != null) {
temporalFields.add(token.temporalField);
@@ -707,6 +759,13 @@ public class HiveSqlDateTimeFormatter implements Serializable {
yearCount += 1;
}
}
+ if (token.temporalField.isDateBased() && token.temporalField != ChronoField.DAY_OF_WEEK) {
+ if (ISO_8601_TEMPORAL_FIELDS.contains(token.temporalField)) {
+ containsIsoFields = true;
+ } else {
+ containsGregorianFields = true;
+ }
+ }
} else if (token.temporalUnit != null) {
timeZoneTemporalUnits.add(token.temporalUnit);
}
@@ -719,7 +778,7 @@ public class HiveSqlDateTimeFormatter implements Serializable {
if (temporalFields.contains(WeekFields.SUNDAY_START.dayOfWeek())) {
throw new IllegalArgumentException("Illegal field: d (" + WeekFields.SUNDAY_START.dayOfWeek() + ")");
}
- if (temporalFields.contains(ChronoField.DAY_OF_WEEK)) {
+ if (temporalFields.contains(ChronoField.DAY_OF_WEEK) && containsGregorianFields) {
throw new IllegalArgumentException("Illegal field: dy/day (" + ChronoField.DAY_OF_WEEK + ")");
}
if (temporalFields.contains(ChronoField.ALIGNED_WEEK_OF_MONTH)) {
@@ -729,15 +788,25 @@ public class HiveSqlDateTimeFormatter implements Serializable {
throw new IllegalArgumentException("Illegal field: ww (" + ChronoField.ALIGNED_WEEK_OF_YEAR + ")");
}
- if (!(temporalFields.contains(ChronoField.YEAR))) {
+ if (containsGregorianFields && containsIsoFields) {
+ throw new IllegalArgumentException("Pattern cannot contain both ISO and Gregorian tokens");
+ }
+ if (!(temporalFields.contains(ChronoField.YEAR)
+ || temporalFields.contains(IsoFields.WEEK_BASED_YEAR))) {
throw new IllegalArgumentException("Missing year token.");
}
- if (!(temporalFields.contains(ChronoField.MONTH_OF_YEAR) &&
+ if (containsGregorianFields &&
+ !(temporalFields.contains(ChronoField.MONTH_OF_YEAR) &&
temporalFields.contains(ChronoField.DAY_OF_MONTH) ||
temporalFields.contains(ChronoField.DAY_OF_YEAR))) {
throw new IllegalArgumentException("Missing day of year or (month of year + day of month)"
+ " tokens.");
}
+ if (containsIsoFields &&
+ !(temporalFields.contains(IsoFields.WEEK_OF_WEEK_BASED_YEAR) &&
+ temporalFields.contains(ChronoField.DAY_OF_WEEK))) {
+ throw new IllegalArgumentException("Missing week of year (iw) or day of week (id) tokens.");
+ }
if (roundYearCount > 0 && yearCount > 0) {
throw new IllegalArgumentException("Invalid duplication of format element: Both year and"
+ "round year are provided");
@@ -934,6 +1003,7 @@ public class HiveSqlDateTimeFormatter implements Serializable {
int index = 0;
int value;
int timeZoneSign = 0, timeZoneHours = 0, timeZoneMinutes = 0;
+ int iyyy = 0, iw = 0;
for (Token token : tokens) {
switch (token.type) {
@@ -952,6 +1022,15 @@ public class HiveSqlDateTimeFormatter implements Serializable {
throw new IllegalArgumentException(
"Value " + value + " not valid for token " + token.toString());
}
+
+ //update IYYY and IW if necessary
+ if (token.temporalField == IsoFields.WEEK_BASED_YEAR) {
+ iyyy = value;
+ }
+ if (token.temporalField == IsoFields.WEEK_OF_WEEK_BASED_YEAR) {
+ iw = value;
+ }
+
index += substring.length();
break;
case TIMEZONE:
@@ -1004,10 +1083,28 @@ public class HiveSqlDateTimeFormatter implements Serializable {
throw new IllegalArgumentException("Leftover input after parsing: " +
fullInput.substring(index) + " in string " + fullInput);
}
+ checkForInvalidIsoWeek(iyyy, iw);
return Timestamp.ofEpochSecond(ldt.toEpochSecond(ZoneOffset.UTC), ldt.getNano());
}
+ /**
+ * Check for WEEK_OF_WEEK_BASED_YEAR (iw) value 53 when WEEK_BASED_YEAR (iyyy) does not have 53
+ * weeks.
+ */
+ private void checkForInvalidIsoWeek(int iyyy, int iw) {
+ if (iyyy == 0) {
+ return;
+ }
+
+ LocalDateTime ldt = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
+ ldt = ldt.with(IsoFields.WEEK_BASED_YEAR, iyyy);
+ ldt = ldt.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, iw);
+ if (ldt.getYear() != iyyy) {
+ throw new IllegalArgumentException("ISO year " + iyyy + " does not have " + iw + " weeks.");
+ }
+ }
+
public Date parseDate(String input){
return Date.ofEpochMilli(parseTimestamp(input).toEpochMilli());
}
@@ -1060,8 +1157,16 @@ public class HiveSqlDateTimeFormatter implements Serializable {
} else if (token.temporalField == ChronoField.HOUR_OF_AMPM && "12".equals(substring)) {
substring = "0";
- } else if (token.temporalField == ChronoField.YEAR) {
- String currentYearString = String.valueOf(LocalDateTime.now().getYear());
+ } else if (token.temporalField == ChronoField.YEAR
+ || token.temporalField == IsoFields.WEEK_BASED_YEAR) {
+
+ String currentYearString;
+ if (token.temporalField == ChronoField.YEAR) {
+ currentYearString = String.valueOf(LocalDateTime.now().getYear());
+ } else {
+ currentYearString = String.valueOf(LocalDateTime.now().get(IsoFields.WEEK_BASED_YEAR));
+ }
+
//deal with round years
if (token.string.startsWith("r") && substring.length() == 2) {
int currFirst2Digits = Integer.parseInt(currentYearString.substring(0, 2));
@@ -1092,6 +1197,7 @@ public class HiveSqlDateTimeFormatter implements Serializable {
}
private static final String MONTH_REGEX;
+ private static final String DAY_OF_WEEK_REGEX;
static {
StringBuilder sb = new StringBuilder();
String or = "";
@@ -1100,6 +1206,13 @@ public class HiveSqlDateTimeFormatter implements Serializable {
or = "|";
}
MONTH_REGEX = sb.toString();
+ sb = new StringBuilder();
+ or = "";
+ for (DayOfWeek dayOfWeek : DayOfWeek.values()) {
+ sb.append(or).append(dayOfWeek);
+ or = "|";
+ }
+ DAY_OF_WEEK_REGEX = sb.toString();
}
private String getNextCharacterSubstring(String fullInput, int index, Token token) {
@@ -1112,7 +1225,17 @@ public class HiveSqlDateTimeFormatter implements Serializable {
return substring;
}
- Matcher matcher = Pattern.compile(MONTH_REGEX, Pattern.CASE_INSENSITIVE).matcher(substring);
+ // patterns day, month
+ String regex;
+ if (token.temporalField == ChronoField.MONTH_OF_YEAR) {
+ regex = MONTH_REGEX;
+ } else if (token.temporalField == ChronoField.DAY_OF_WEEK) {
+ regex = DAY_OF_WEEK_REGEX;
+ } else {
+ throw new IllegalArgumentException("Error at index " + index + ": " + token + " not a "
+ + "character temporal with length not 3");
+ }
+ Matcher matcher = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(substring);
if (matcher.find()) {
return substring.substring(0, matcher.end());
}
@@ -1128,6 +1251,12 @@ public class HiveSqlDateTimeFormatter implements Serializable {
} else {
return Month.valueOf(substring.toUpperCase()).getValue();
}
+ } else if (token.temporalField == ChronoField.DAY_OF_WEEK) {
+ if (token.length == 3) {
+ return DayOfWeek.from(DAY_OF_WEEK_FORMATTER.parse(capitalize(substring))).getValue();
+ } else {
+ return DayOfWeek.valueOf(substring.toUpperCase()).getValue();
+ }
}
} catch (Exception e) {
throw new IllegalArgumentException(
diff --git a/common/src/test/org/apache/hadoop/hive/common/format/datetime/TestHiveSqlDateTimeFormatter.java b/common/src/test/org/apache/hadoop/hive/common/format/datetime/TestHiveSqlDateTimeFormatter.java
index ff41534..ea60a31 100644
--- a/common/src/test/org/apache/hadoop/hive/common/format/datetime/TestHiveSqlDateTimeFormatter.java
+++ b/common/src/test/org/apache/hadoop/hive/common/format/datetime/TestHiveSqlDateTimeFormatter.java
@@ -95,6 +95,11 @@ public class TestHiveSqlDateTimeFormatter {
verifyBadPattern("yyyy mm-MONTH dd", true);
verifyBadPattern("yyyy MON, month dd", true);
+ verifyBadPattern("iyyy-mm-dd", true); // can't mix iso and Gregorian
+ verifyBadPattern("iyyy-id", true); // missing iyyy, iw, or id
+ verifyBadPattern("iyyy-iw", true);
+ verifyBadPattern("iw-id", true);
+
verifyBadPattern("tzm", false);
verifyBadPattern("tzh", false);
@@ -145,6 +150,23 @@ public class TestHiveSqlDateTimeFormatter {
checkFormatTs("YYYY-mm-dd: Q WW W", "2019-03-31 00:00:00", "2019-03-31: 1 13 5");
checkFormatTs("YYYY-mm-dd: Q WW W", "2019-04-01 00:00:00", "2019-04-01: 2 13 1");
checkFormatTs("YYYY-mm-dd: Q WW W", "2019-12-31 00:00:00", "2019-12-31: 4 53 5");
+
+ //ISO 8601
+ checkFormatTs("YYYY-MM-DD : IYYY-IW-ID", "2018-12-31 00:00:00", "2018-12-31 : 2019-01-01");
+ checkFormatTs("YYYY-MM-DD : IYYY-IW-ID", "2019-01-06 00:00:00", "2019-01-06 : 2019-01-07");
+ checkFormatTs("YYYY-MM-DD : IYYY-IW-ID", "2019-01-07 00:00:00", "2019-01-07 : 2019-02-01");
+ checkFormatTs("YYYY-MM-DD : IYYY-IW-ID", "2019-12-29 00:00:00", "2019-12-29 : 2019-52-07");
+ checkFormatTs("YYYY-MM-DD : IYYY-IW-ID", "2019-12-30 00:00:00", "2019-12-30 : 2020-01-01");
+ checkFormatTs("YYYY-MM-DD : IYY-IW-ID", "2019-12-30 00:00:00", "2019-12-30 : 020-01-01");
+ checkFormatTs("YYYY-MM-DD : IY-IW-ID", "2019-12-30 00:00:00", "2019-12-30 : 20-01-01");
+ checkFormatTs("YYYY-MM-DD : I-IW-ID", "2019-12-30 00:00:00", "2019-12-30 : 0-01-01");
+ checkFormatTs("id: Day", "2018-12-31 00:00:00", "01: Monday ");
+ checkFormatTs("id: Day", "2019-01-01 00:00:00", "02: Tuesday ");
+ checkFormatTs("id: Day", "2019-01-02 00:00:00", "03: Wednesday");
+ checkFormatTs("id: Day", "2019-01-03 00:00:00", "04: Thursday ");
+ checkFormatTs("id: Day", "2019-01-04 00:00:00", "05: Friday ");
+ checkFormatTs("id: Day", "2019-01-05 00:00:00", "06: Saturday ");
+ checkFormatTs("id: Day", "2019-01-06 00:00:00", "07: Sunday ");
}
private void checkFormatTs(String pattern, String input, String expectedOutput) {
@@ -236,9 +258,33 @@ public class TestHiveSqlDateTimeFormatter {
checkParseTimestamp("yyyy-MON-dd", "2018-FEB-28", "2018-02-28 00:00:00");
checkParseTimestamp("yyyy-moN-dd", "2018-FeB-28", "2018-02-28 00:00:00");
checkParseTimestamp("yyyy-mon-dd", "2018-FEB-28", "2018-02-28 00:00:00");
+ verifyBadParseString("yyyy-MON-dd", "2018-FEBRUARY-28");
+ verifyBadParseString("yyyy-MON-dd", "2018-FEBR-28");
+ verifyBadParseString("yyyy-MONTH-dd", "2018-FEB-28");
//letters and numbers are delimiters to each other, respectively
checkParseDate("yyyy-ddMONTH", "2018-4March", "2018-03-04");
checkParseDate("yyyy-MONTHdd", "2018-March4", "2018-03-04");
+ //ISO 8601
+ checkParseTimestamp("IYYY-IW-ID", "2019-01-01", "2018-12-31 00:00:00");
+ checkParseTimestamp("IYYY-IW-ID", "2019-01-07", "2019-01-06 00:00:00");
+ checkParseTimestamp("IYYY-IW-ID", "2019-02-01", "2019-01-07 00:00:00");
+ checkParseTimestamp("IYYY-IW-ID", "2019-52-07", "2019-12-29 00:00:00");
+ checkParseTimestamp("IYYY-IW-ID", "2020-01-01", "2019-12-30 00:00:00");
+ checkParseTimestamp("IYYY-IW-ID", "020-01-04", thisYearString.substring(0, 1) + "020-01-02 00:00:00");
+ checkParseTimestamp("IYY-IW-ID", "020-01-04", thisYearString.substring(0, 1) + "020-01-02 00:00:00");
+ checkParseTimestamp("IYY-IW-ID", "20-01-04", thisYearString.substring(0, 2) + "20-01-02 00:00:00");
+ checkParseTimestamp("IY-IW-ID", "20-01-04", thisYearString.substring(0, 2) + "20-01-02 00:00:00");
+ checkParseTimestamp("IYYY-IW-DAY", "2019-01-monday", "2018-12-31 00:00:00");
+ checkParseTimestamp("IYYY-IW-Day", "2019-01-Sunday", "2019-01-06 00:00:00");
+ checkParseTimestamp("IYYY-IW-Dy", "2019-02-MON", "2019-01-07 00:00:00");
+ checkParseTimestamp("IYYY-IW-DY", "2019-52-sun", "2019-12-29 00:00:00");
+ checkParseTimestamp("IYYY-IW-dy", "2020-01-Mon", "2019-12-30 00:00:00");
+ //Tests for these patterns would need changing every decade if done in the above way.
+ //Thursday of the first week in an ISO year always matches the Gregorian year.
+ checkParseTimestampIso("IY-IW-ID", "0-01-04", "iw, yyyy", "01, " + thisYearString.substring(0, 3) + "0");
+ checkParseTimestampIso("I-IW-ID", "0-01-04", "iw, yyyy", "01, " + thisYearString.substring(0, 3) + "0");
+ //time patterns are allowed; date patterns are not
+ checkParseTimestamp("IYYY-IW-ID hh24:mi:ss", "2019-01-01 01:02:03", "2018-12-31 01:02:03");
}
private int getFirstTwoDigits() {
@@ -256,6 +302,14 @@ public class TestHiveSqlDateTimeFormatter {
Timestamp.valueOf(expectedOutput), formatter.parseTimestamp(input));
}
+ private void checkParseTimestampIso(String parsePattern, String input, String formatPattern,
+ String expectedOutput) {
+ formatter = new HiveSqlDateTimeFormatter(parsePattern, true);
+ Timestamp ts = formatter.parseTimestamp(input);
+ formatter = new HiveSqlDateTimeFormatter(formatPattern, false);
+ assertEquals(expectedOutput, formatter.format(ts));
+ }
+
@Test
public void testParseDate() {
@@ -283,6 +337,15 @@ public class TestHiveSqlDateTimeFormatter {
checkParseDate("dd/MonthT/yyyy", "31/AugustT/2020", "2020-08-31");
checkParseDate("dd/MonthT/yyyy", "31/MarchT/2020", "2020-03-31");
+
+ //ISO 8601
+ checkParseDate("IYYY-IW-ID", "2019-01-01", "2018-12-31");
+ checkParseDate("IW-ID-IYYY", "01-02-2019", "2019-01-01");
+ checkParseDate("ID-IW-IYYY", "02-01-2019", "2019-01-01");
+ checkParseDate("IYYY-IW-ID", "2019-01-07", "2019-01-06");
+ checkParseDate("IYYY-IW-ID", "2019-02-01", "2019-01-07");
+ checkParseDate("IYYY-IW-ID", "2019-52-07", "2019-12-29");
+ checkParseDate("IYYY-IW-ID", "2020-01-01", "2019-12-30");
}
private void checkParseDate(String pattern, String input, String expectedOutput) {
@@ -306,6 +369,12 @@ public class TestHiveSqlDateTimeFormatter {
verifyBadParseString("yyyy-MON-dd", "2018-FEBRUARY-28"); // can't mix and match mon and month
verifyBadParseString("yyyy-MON-dd", "2018-FEBR-28");
verifyBadParseString("yyyy-MONTH-dd", "2018-FEB-28");
+ verifyBadParseString("iyyy-iw-id", "2019-00-01"); //ISO 8601 week number out of range for year
+ verifyBadParseString("iyyy-iw-id", "2019-53-01"); //ISO 8601 week number out of range for year
+ verifyBadParseString("iw-iyyy-id", "53-2019-01"); //ISO 8601 week number out of range for year
+ verifyBadParseString("iw-iyyy-id", "54-2019-01"); //ISO 8601 week number out of range
+ verifyBadParseString("iyyy-iw-id", "2019-52-00"); //ISO 8601 day of week out of range
+ verifyBadParseString("iyyy-iw-id", "2019-52-08"); //ISO 8601 day of week out of range
}
private void verifyBadPattern(String string, boolean forParsing) {
@@ -322,6 +391,7 @@ public class TestHiveSqlDateTimeFormatter {
public void testFm() {
//year (019) becomes 19 even if pattern is yyy
checkFormatTs("FMyyy-FMmm-dd FMHH12:MI:FMSS", "2019-01-01 01:01:01", "19-1-01 1:01:1");
+ checkFormatTs("FMiyy-FMiw-id FMHH12:MI:FMSS", "2018-12-31 01:01:01", "19-1-01 1:01:1");
//ff[1-9] shouldn't be affected, because leading zeroes hold information
checkFormatTs("FF5/FMFF5", "2019-01-01 01:01:01.0333", "03330/03330");
checkFormatTs("FF/FMFF", "2019-01-01 01:01:01.0333", "0333/0333");
@@ -357,6 +427,7 @@ public class TestHiveSqlDateTimeFormatter {
//enforce correct amount of leading zeroes
verifyBadParseString("FXyyyy-mm-dd hh24:miss", "2018-01-01 17:005");
verifyBadParseString("FXyyyy-mm-dd sssss", "2019-01-01 003");
+ verifyBadParseString("FXiyyy-iw-id hh24:mi:ss", "019-01-02 17:00:05");
//text case does not matter
checkParseTimestamp("\"the DATE is\" yyyy-mm-dd", "the date is 2018-01-01", "2018-01-01 00:00:00");
//AM/PM length has to match, but case doesn't
@@ -372,6 +443,8 @@ public class TestHiveSqlDateTimeFormatter {
public void testFmFx() {
checkParseTimestamp("FXDD-FMMM-YYYY hh12 am", "01-1-1998 12 PM", "1998-01-01 12:00:00");
checkParseTimestamp("FXFMDD-MM-YYYY hh12 am", "1-01-1998 12 PM", "1998-01-01 12:00:00");
+ checkParseTimestamp("FXFMiyyy-iw-id hh24:mi:ss", "019-01-02 17:00:05", "2019-01-01 17:00:05");
+ verifyBadParseString("FXFMiyyy-iw-id hh24:mi:ss", "019-01-02 17:0:05");
//ff[1-9] unaffected
checkParseTimestamp("FXFMDD-MM-YYYY FMff2", "1-01-1998 4", "1998-01-01 00:00:00.4");
checkParseTimestamp("FXFMDD-MM-YYYY ff2", "1-01-1998 4", "1998-01-01 00:00:00.4");
@@ -450,9 +523,9 @@ public class TestHiveSqlDateTimeFormatter {
private void verifyBadParseString(String pattern, String string) {
formatter = new HiveSqlDateTimeFormatter(pattern, true);
try {
- formatter.parseTimestamp(string);
+ Timestamp output = formatter.parseTimestamp(string);
fail("Parse string to timestamp should have failed.\nString: " + string + "\nPattern: "
- + pattern);
+ + pattern + ", output = " + output);
} catch (Exception e) {
assertEquals("Expected IllegalArgumentException, got another exception.",
e.getClass().getName(), IllegalArgumentException.class.getName());