Repository: calcite-avatica Updated Branches: refs/heads/master 57b574eb3 -> 3c40e0db3
[CALCITE-1690] Timestamp literals cannot express precision above millisecond Switch preferred time zone from GMT to UTC. Close apache/calcite-avatica#9 Project: http://git-wip-us.apache.org/repos/asf/calcite-avatica/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite-avatica/commit/3c40e0db Tree: http://git-wip-us.apache.org/repos/asf/calcite-avatica/tree/3c40e0db Diff: http://git-wip-us.apache.org/repos/asf/calcite-avatica/diff/3c40e0db Branch: refs/heads/master Commit: 3c40e0db34eca431b59e1a3b962b6520128c70ff Parents: 57b574e Author: Julian Hyde <[email protected]> Authored: Tue May 2 19:34:40 2017 -0700 Committer: Julian Hyde <[email protected]> Committed: Mon May 22 22:06:35 2017 -0700 ---------------------------------------------------------------------- .../calcite/avatica/util/DateTimeUtils.java | 99 ++++++++++++-------- .../calcite/avatica/util/DateTimeUtilsTest.java | 42 +++++++++ 2 files changed, 102 insertions(+), 39 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/3c40e0db/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java ---------------------------------------------------------------------- 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 406dcfd..6cf52b6 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 @@ -16,10 +16,12 @@ */ package org.apache.calcite.avatica.util; +import java.text.DateFormat; import java.text.NumberFormat; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -92,7 +94,7 @@ public class DateTimeUtils { public static final Calendar ZERO_CALENDAR; static { - ZERO_CALENDAR = Calendar.getInstance(DateTimeUtils.GMT_ZONE, Locale.ROOT); + ZERO_CALENDAR = Calendar.getInstance(DateTimeUtils.UTC_ZONE, Locale.ROOT); ZERO_CALENDAR.setTimeInMillis(0); } @@ -106,54 +108,52 @@ public class DateTimeUtils { * less than 31, etc. * * @param s string to be parsed - * @param pattern {@link SimpleDateFormat} pattern (not null) + * @param dateFormat Date format * @param tz time zone in which to interpret string. Defaults to the Java * default time zone * @param pp position to start parsing from * @return a Calendar initialized with the parsed value, or null if parsing * failed. If returned, the Calendar is configured to the GMT time zone. */ - private static Calendar parseDateFormat( - String s, - String pattern, - TimeZone tz, - ParsePosition pp) { - assert pattern != null; - SimpleDateFormat df = new SimpleDateFormat(pattern, Locale.ROOT); + private static Calendar parseDateFormat(String s, DateFormat dateFormat, + TimeZone tz, ParsePosition pp) { if (tz == null) { tz = DEFAULT_ZONE; } Calendar ret = Calendar.getInstance(tz, Locale.ROOT); - df.setCalendar(ret); - df.setLenient(false); + dateFormat.setCalendar(ret); + dateFormat.setLenient(false); - java.util.Date d = df.parse(s, pp); + final Date d = dateFormat.parse(s, pp); if (null == d) { return null; } ret.setTime(d); - ret.setTimeZone(GMT_ZONE); + ret.setTimeZone(UTC_ZONE); return ret; } + @Deprecated // to be removed before 2.0 + public static Calendar parseDateFormat(String s, String pattern, + TimeZone tz) { + return parseDateFormat(s, new SimpleDateFormat(pattern, Locale.ROOT), tz); + } + /** * Parses a string using {@link SimpleDateFormat} and a given pattern. The * entire string must match the pattern specified. * * @param s string to be parsed - * @param pattern {@link SimpleDateFormat} pattern + * @param dateFormat Date format * @param tz time zone in which to interpret string. Defaults to the Java * default time zone * @return a Calendar initialized with the parsed value, or null if parsing - * failed. If returned, the Calendar is configured to the GMT time zone. + * failed. If returned, the Calendar is configured to the UTC time zone. */ - public static Calendar parseDateFormat( - String s, - String pattern, + public static Calendar parseDateFormat(String s, DateFormat dateFormat, TimeZone tz) { - assert pattern != null; ParsePosition pp = new ParsePosition(0); - Calendar ret = parseDateFormat(s, pattern, tz, pp); + Calendar ret = parseDateFormat(s, dateFormat, tz, pp); if (pp.getIndex() != s.length()) { // Didn't consume entire string - not good return null; @@ -161,6 +161,16 @@ public class DateTimeUtils { return ret; } + @Deprecated // to be removed before 2.0 + public static PrecisionTime parsePrecisionDateTimeLiteral( + String s, + String pattern, + TimeZone tz) { + assert pattern != null; + return parsePrecisionDateTimeLiteral(s, + new SimpleDateFormat(pattern, Locale.ROOT), tz, 3); + } + /** * Parses a string using {@link SimpleDateFormat} and a given pattern, and * if present, parses a fractional seconds component. The fractional seconds @@ -169,20 +179,17 @@ public class DateTimeUtils { * seconds precision (to obtain milliseconds). * * @param s string to be parsed - * @param pattern {@link SimpleDateFormat} pattern + * @param dateFormat Date format * @param tz time zone in which to interpret string. Defaults to the * local time zone * @return a {@link DateTimeUtils.PrecisionTime PrecisionTime} initialized * with the parsed value, or null if parsing failed. The PrecisionTime * contains a GMT Calendar and a precision. */ - public static PrecisionTime parsePrecisionDateTimeLiteral( - String s, - String pattern, - TimeZone tz) { - assert pattern != null; - ParsePosition pp = new ParsePosition(0); - Calendar cal = parseDateFormat(s, pattern, tz, pp); + public static PrecisionTime parsePrecisionDateTimeLiteral(String s, + DateFormat dateFormat, TimeZone tz, int maxPrecision) { + final ParsePosition pp = new ParsePosition(0); + final Calendar cal = parseDateFormat(s, dateFormat, tz, pp); if (cal == null) { return null; // Invalid date/time format } @@ -191,6 +198,7 @@ public class DateTimeUtils { // the decimal as milliseconds. That means 12:00:00.9 has 9 // milliseconds and 12:00:00.9999 has 9999 milliseconds. int p = 0; + String secFraction = ""; if (pp.getIndex() < s.length()) { // Check to see if rest is decimal portion if (s.charAt(pp.getIndex()) != '.') { @@ -202,7 +210,7 @@ public class DateTimeUtils { // Parse decimal portion if (pp.getIndex() < s.length()) { - String secFraction = s.substring(pp.getIndex()); + secFraction = s.substring(pp.getIndex()); if (!secFraction.matches("\\d+")) { return null; } @@ -215,22 +223,29 @@ public class DateTimeUtils { // Determine precision - only support prec 3 or lower // (milliseconds) Higher precisions are quietly rounded away - p = Math.min( - 3, - secFraction.length()); + p = secFraction.length(); + if (maxPrecision >= 0) { + // If there is a maximum precision, ignore subsequent digits + p = Math.min(maxPrecision, p); + secFraction = secFraction.substring(0, p); + } // Calculate milliseconds - int ms = - (int) Math.round( - num.longValue() - * Math.pow(10, 3 - secFraction.length())); + String millis = secFraction; + if (millis.length() > 3) { + millis = secFraction.substring(0, 3); + } + while (millis.length() < 3) { + millis = millis + "0"; + } + + int ms = Integer.valueOf(millis); cal.add(Calendar.MILLISECOND, ms); } } assert pp.getIndex() == s.length(); - PrecisionTime ret = new PrecisionTime(cal, p); - return ret; + return new PrecisionTime(cal, secFraction, p); } /** @@ -1014,10 +1029,12 @@ public class DateTimeUtils { */ public static class PrecisionTime { private final Calendar cal; + private final String fraction; private final int precision; - public PrecisionTime(Calendar cal, int precision) { + public PrecisionTime(Calendar cal, String fraction, int precision) { this.cal = cal; + this.fraction = fraction; this.precision = precision; } @@ -1028,6 +1045,10 @@ public class DateTimeUtils { public int getPrecision() { return precision; } + + public String getFraction() { + return fraction; + } } } http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/3c40e0db/core/src/test/java/org/apache/calcite/avatica/util/DateTimeUtilsTest.java ---------------------------------------------------------------------- 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 1ac1a90..f433884 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 @@ -18,6 +18,10 @@ package org.apache.calcite.avatica.util; import org.junit.Test; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + import static org.apache.calcite.avatica.util.DateTimeUtils.EPOCH_JULIAN; import static org.apache.calcite.avatica.util.DateTimeUtils.addMonths; import static org.apache.calcite.avatica.util.DateTimeUtils.dateStringToUnixDate; @@ -44,6 +48,7 @@ import static org.apache.calcite.avatica.util.DateTimeUtils.ymdToUnixDate; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -521,6 +526,43 @@ public class DateTimeUtilsTest { assertThat(unixTimestamp(2016, 2, 29, 0, 0, 0), is(y2016 + day)); assertThat(unixTimestamp(2016, 3, 1, 0, 0, 0), is(y2016 + day + day)); } + + @Test public void testParse() { + final SimpleDateFormat formatD = + new SimpleDateFormat(DateTimeUtils.DATE_FORMAT_STRING, Locale.ROOT); + final Calendar c = + DateTimeUtils.parseDateFormat("1234-04-12", formatD, + DateTimeUtils.UTC_ZONE); + assertThat(c, notNullValue()); + assertThat(c.get(Calendar.YEAR), is(1234)); + assertThat(c.get(Calendar.MONTH), is(Calendar.APRIL)); + assertThat(c.get(Calendar.DAY_OF_MONTH), is(12)); + + final SimpleDateFormat formatTs = + new SimpleDateFormat(DateTimeUtils.TIMESTAMP_FORMAT_STRING, + Locale.ROOT); + final DateTimeUtils.PrecisionTime pt = + DateTimeUtils.parsePrecisionDateTimeLiteral( + "1234-04-12 01:23:45.06789", formatTs, DateTimeUtils.UTC_ZONE, -1); + assertThat(pt, notNullValue()); + assertThat(pt.getCalendar().get(Calendar.YEAR), is(1234)); + assertThat(pt.getCalendar().get(Calendar.MONTH), is(Calendar.APRIL)); + assertThat(pt.getCalendar().get(Calendar.DAY_OF_MONTH), is(12)); + assertThat(pt.getCalendar().get(Calendar.HOUR_OF_DAY), is(1)); + assertThat(pt.getCalendar().get(Calendar.MINUTE), is(23)); + assertThat(pt.getCalendar().get(Calendar.SECOND), is(45)); + assertThat(pt.getCalendar().get(Calendar.MILLISECOND), is(67)); + assertThat(pt.getFraction(), is("06789")); + assertThat(pt.getPrecision(), is(5)); + + // as above, but limit to 2 fractional digits + final DateTimeUtils.PrecisionTime pt2 = + DateTimeUtils.parsePrecisionDateTimeLiteral( + "1234-04-12 01:23:45.06789", formatTs, DateTimeUtils.UTC_ZONE, 2); + assertThat(pt2, notNullValue()); + assertThat(pt2.getCalendar().get(Calendar.MILLISECOND), is(60)); + assertThat(pt2.getFraction(), is("06")); + } } // End DateTimeUtilsTest.java
