From 78c81dce732e5b6a76db90fa52d0cdb19d57270a Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Wed, 20 Nov 2019 08:55:40 -0500 Subject: [PATCH 1/4] fix: pginterval to take iso8601 strings --- .../java/org/postgresql/util/PGInterval.java | 90 ++++++++++++++++--- .../postgresql/test/jdbc2/IntervalTest.java | 29 +++++- 2 files changed, 105 insertions(+), 14 deletions(-) diff --git a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java index c1861b9dd1..78b608bcc1 100644 --- a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java +++ b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.StringTokenizer; @@ -19,10 +20,10 @@ public class PGInterval extends PGobject implements Serializable, Cloneable { private int years; - private int months; - private int days; - private int hours; - private int minutes; + private byte months; + private byte days; + private byte hours; + private byte minutes; private double seconds; private static final DecimalFormat secondsFormat; @@ -53,6 +54,68 @@ public PGInterval(String value) throws SQLException { setValue(value); } + private int lookAhead(String value, int position, String find){ + char [] tokens = find.toCharArray(); + int found = -1; + + for ( int i=0; i < tokens.length; i++ ) { + found = value.indexOf(tokens[i], position); + if (found > 0) { + return found; + } + } + return found; + } + + private void parseISO8601Format(String value) { + int number = 0; + String dateValue; + String timeValue=null; + + int hasTime = value.indexOf('T'); + if ( hasTime > 0 ) { + /* skip over the P */ + dateValue = value.substring(1,hasTime); + timeValue = value.substring(hasTime + 1); + } + else { + /* skip over the P */ + dateValue = value.substring(1); + } + + for ( int i = 0; i < dateValue.length(); i++ ) { + + int lookAhead = lookAhead(dateValue, i, "YMD"); + if (lookAhead > 0) { + number = Integer.parseInt(dateValue.substring(i, lookAhead)); + if (dateValue.charAt(lookAhead) == 'Y') { + setYears(number); + } else if (dateValue.charAt(lookAhead) == 'M') { + setMonths(number); + } else if (dateValue.charAt(lookAhead) == 'D') { + setDays(number); + } + i = lookAhead; + } + } + if ( timeValue != null ) { + for (int i = 0; i < timeValue.length(); i++) { + int lookAhead = lookAhead(timeValue, i, "HMS"); + if (lookAhead > 0) { + number = Integer.parseInt(timeValue.substring(i, lookAhead)); + if (timeValue.charAt(lookAhead) == 'H') { + setHours(number); + } else if (timeValue.charAt(lookAhead) == 'M') { + setMinutes(number); + } else if (timeValue.charAt(lookAhead) == 'S') { + setSeconds(number); + } + i = lookAhead; + } + } + } +} + /** * Initializes all values of this interval to the specified values. * @@ -77,10 +140,13 @@ public PGInterval(int years, int months, int days, int hours, int minutes, doubl * @throws SQLException Is thrown if the string representation has an unknown format */ public void setValue(String value) throws SQLException { - final boolean ISOFormat = !value.startsWith("@"); - + final boolean PostgresFormat = !value.startsWith("@"); + if (value.startsWith("P")) { + parseISO8601Format(value); + return; + } // Just a simple '0' - if (!ISOFormat && value.length() == 3 && value.charAt(2) == '0') { + if (!PostgresFormat && value.length() == 3 && value.charAt(2) == '0') { setValue(0, 0, 0, 0, 0, 0.0); return; } @@ -153,7 +219,7 @@ public void setValue(String value) throws SQLException { PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE, e); } - if (!ISOFormat && value.endsWith("ago")) { + if (!PostgresFormat && value.endsWith("ago")) { // Inverse the leading sign setValue(-years, -months, -days, -hours, -minutes, -seconds); } else { @@ -227,7 +293,7 @@ public int getMonths() { * @param months months to set */ public void setMonths(int months) { - this.months = months; + this.months = (byte) months; } /** @@ -245,7 +311,7 @@ public int getDays() { * @param days days to set */ public void setDays(int days) { - this.days = days; + this.days = (byte)days; } /** @@ -263,7 +329,7 @@ public int getHours() { * @param hours hours to set */ public void setHours(int hours) { - this.hours = hours; + this.hours = (byte)hours; } /** @@ -281,7 +347,7 @@ public int getMinutes() { * @param minutes minutes to set */ public void setMinutes(int minutes) { - this.minutes = minutes; + this.minutes = (byte)minutes; } /** diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java index 80395c4454..67de463e63 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java @@ -256,7 +256,7 @@ public void testDate() throws Exception { } @Test - public void testISODate() throws Exception { + public void testPostgresDate() throws Exception { Date date = getStartCalendar().getTime(); Date date2 = getStartCalendar().getTime(); @@ -268,7 +268,32 @@ public void testISODate() throws Exception { assertEquals(date2, date); } - + @Test + public void testISO8601() throws Exception { + PGInterval pgi = new PGInterval("P1Y2M3DT4H5M6S"); + assertEquals(1, pgi.getYears() ); + assertEquals(2, pgi.getMonths() ); + assertEquals(3, pgi.getDays() ); + assertEquals(4, pgi.getHours() ); + assertEquals( 5, pgi.getMinutes() ); + assertEquals( 6, pgi.getSeconds(), .1 ); + + pgi = new PGInterval("P-1Y2M3DT4H5M6S"); + assertEquals(-1, pgi.getYears()); + + pgi = new PGInterval("P1Y2M"); + assertEquals(1,pgi.getYears()); + assertEquals(2, pgi.getMonths()); + assertEquals(0, pgi.getDays()); + + pgi = new PGInterval("P3DT4H5M6S"); + assertEquals(0,pgi.getYears()); + + pgi = new PGInterval("P-1Y-2M3DT-4H-5M-6S"); + assertEquals(-1, pgi.getYears()); + assertEquals(-2, pgi.getMonths()); + assertEquals(-4, pgi.getHours()); + } private java.sql.Date makeDate(int y, int m, int d) { return new java.sql.Date(y - 1900, m - 1, d); } From 62a81d2fa6793d6ca6a66ab1636b099064716406 Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Thu, 21 Nov 2019 14:47:34 -0500 Subject: [PATCH 2/4] use integers for seconds and microseconds all tests passed --- .../java/org/postgresql/util/PGInterval.java | 111 +++++++++++------- .../postgresql/test/jdbc2/IntervalTest.java | 4 +- 2 files changed, 71 insertions(+), 44 deletions(-) diff --git a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java index 78b608bcc1..84d0674df0 100644 --- a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java +++ b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java @@ -7,11 +7,9 @@ import java.io.Serializable; import java.sql.SQLException; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import java.util.Objects; import java.util.StringTokenizer; /** @@ -24,16 +22,9 @@ public class PGInterval extends PGobject implements Serializable, Cloneable { private byte days; private byte hours; private byte minutes; - private double seconds; + private int wholeSeconds; + private int microSeconds; - private static final DecimalFormat secondsFormat; - - static { - secondsFormat = new DecimalFormat("0.00####"); - DecimalFormatSymbols dfs = secondsFormat.getDecimalFormatSymbols(); - dfs.setDecimalSeparator('.'); - secondsFormat.setDecimalFormatSymbols(dfs); - } /** * required by the driver. @@ -54,14 +45,14 @@ public PGInterval(String value) throws SQLException { setValue(value); } - private int lookAhead(String value, int position, String find){ + private int lookAhead(String value, int position, String find) { char [] tokens = find.toCharArray(); int found = -1; - for ( int i=0; i < tokens.length; i++ ) { + for ( int i = 0; i < tokens.length; i++ ) { found = value.indexOf(tokens[i], position); - if (found > 0) { - return found; + if ( found > 0 ) { + return found; } } return found; @@ -70,32 +61,30 @@ private int lookAhead(String value, int position, String find){ private void parseISO8601Format(String value) { int number = 0; String dateValue; - String timeValue=null; + String timeValue = null; int hasTime = value.indexOf('T'); if ( hasTime > 0 ) { /* skip over the P */ dateValue = value.substring(1,hasTime); timeValue = value.substring(hasTime + 1); - } - else { + } else { /* skip over the P */ dateValue = value.substring(1); } for ( int i = 0; i < dateValue.length(); i++ ) { - - int lookAhead = lookAhead(dateValue, i, "YMD"); - if (lookAhead > 0) { - number = Integer.parseInt(dateValue.substring(i, lookAhead)); - if (dateValue.charAt(lookAhead) == 'Y') { - setYears(number); - } else if (dateValue.charAt(lookAhead) == 'M') { - setMonths(number); - } else if (dateValue.charAt(lookAhead) == 'D') { - setDays(number); - } - i = lookAhead; + int lookAhead = lookAhead(dateValue, i, "YMD"); + if (lookAhead > 0) { + number = Integer.parseInt(dateValue.substring(i, lookAhead)); + if (dateValue.charAt(lookAhead) == 'Y') { + setYears(number); + } else if (dateValue.charAt(lookAhead) == 'M') { + setMonths(number); + } else if (dateValue.charAt(lookAhead) == 'D') { + setDays(number); + } + i = lookAhead; } } if ( timeValue != null ) { @@ -114,7 +103,7 @@ private void parseISO8601Format(String value) { } } } -} + } /** * Initializes all values of this interval to the specified values. @@ -257,7 +246,7 @@ public String getValue() { + days + " days " + hours + " hours " + minutes + " mins " - + secondsFormat.format(seconds) + " secs"; + + wholeSeconds + '.' + microSeconds + " secs"; } /** @@ -293,7 +282,7 @@ public int getMonths() { * @param months months to set */ public void setMonths(int months) { - this.months = (byte) months; + this.months = (byte) months; } /** @@ -356,7 +345,22 @@ public void setMinutes(int minutes) { * @return seconds represented by this interval */ public double getSeconds() { - return seconds; + if ( microSeconds < 0) { + if ( wholeSeconds == 0 ) { + return Double.parseDouble("-0." + -microSeconds); + } else { + return Double.parseDouble("" + wholeSeconds + '.' + -microSeconds); + } + } + return Double.parseDouble("" + wholeSeconds + '.' + microSeconds ); + } + + public int getWholeSeconds() { + return wholeSeconds; + } + + public int getMicroSeconds() { + return microSeconds; } /** @@ -365,7 +369,23 @@ public double getSeconds() { * @param seconds seconds to set */ public void setSeconds(double seconds) { - this.seconds = seconds; + String str = Double.toString(seconds); + int decimal = str.indexOf('.'); + if (decimal > 0) { + + /* how many 10's do we need to multiply by to get microseconds */ + String micSeconds = str.substring(decimal + 1); + int power = 6 - micSeconds.length(); + + microSeconds = Integer.parseInt(micSeconds) * (int)Math.pow(10,power); + wholeSeconds = Integer.parseInt(str.substring(0,decimal)); + } else { + microSeconds = 0; + wholeSeconds = Integer.parseInt(str); + } + if ( seconds < 0 ) { + microSeconds = -microSeconds; + } } /** @@ -374,10 +394,8 @@ public void setSeconds(double seconds) { * @param cal Calendar instance to add to */ public void add(Calendar cal) { - // Avoid precision loss - // Be aware postgres doesn't return more than 60 seconds - no overflow can happen - final int microseconds = (int) (getSeconds() * 1000000.0); - final int milliseconds = (microseconds + ((microseconds < 0) ? -500 : 500)) / 1000; + + final int milliseconds = (microSeconds + ((microSeconds < 0) ? -500 : 500)) / 1000 + wholeSeconds * 1000; cal.add(Calendar.MILLISECOND, milliseconds); cal.add(Calendar.MINUTE, getMinutes()); @@ -479,7 +497,14 @@ public boolean equals(Object obj) { && pgi.days == days && pgi.hours == hours && pgi.minutes == minutes - && Double.doubleToLongBits(pgi.seconds) == Double.doubleToLongBits(seconds); + && pgi.wholeSeconds == wholeSeconds + && pgi.microSeconds == microSeconds; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), years, months, days, hours, minutes, wholeSeconds, + microSeconds); } /** @@ -487,8 +512,8 @@ public boolean equals(Object obj) { * * @return hashCode */ - public int hashCode() { - return ((((((7 * 31 + (int) Double.doubleToLongBits(seconds)) * 31 + minutes) * 31 + hours) * 31 + public int hashmCode() { + return ((((((7 * 31 + wholeSeconds) * 31 + minutes) * 31 + hours) * 31 + days) * 31 + months) * 31 + years) * 31; } diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java index 67de463e63..56b5878427 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java @@ -107,7 +107,7 @@ public void testIntervalToStringCoercion() throws SQLException { PGInterval interval = new PGInterval("1 year 3 months"); String coercedStringValue = interval.toString(); - assertEquals("1 years 3 mons 0 days 0 hours 0 mins 0.00 secs", coercedStringValue); + assertEquals("1 years 3 mons 0 days 0 hours 0 mins 0.0 secs", coercedStringValue); } @Test @@ -268,6 +268,7 @@ public void testPostgresDate() throws Exception { assertEquals(date2, date); } + @Test public void testISO8601() throws Exception { PGInterval pgi = new PGInterval("P1Y2M3DT4H5M6S"); @@ -294,6 +295,7 @@ public void testISO8601() throws Exception { assertEquals(-2, pgi.getMonths()); assertEquals(-4, pgi.getHours()); } + private java.sql.Date makeDate(int y, int m, int d) { return new java.sql.Date(y - 1900, m - 1, d); } From 7a2bf62f5579ec299e39f3d1b9a254b923e769c2 Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Mon, 25 Nov 2019 10:13:09 -0500 Subject: [PATCH 3/4] Appease checkstyle --- pgjdbc/src/main/java/org/postgresql/util/PGInterval.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java index 84d0674df0..0d0c39afde 100644 --- a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java +++ b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java @@ -25,7 +25,6 @@ public class PGInterval extends PGobject implements Serializable, Cloneable { private int wholeSeconds; private int microSeconds; - /** * required by the driver. */ From 4222c7d75d7eac8a30918d853968de1ab2e6fc7b Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Mon, 25 Nov 2019 11:58:40 -0500 Subject: [PATCH 4/4] remove Objects.hash as it is not available in Java 6 --- .../main/java/org/postgresql/util/PGInterval.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java index 0d0c39afde..0e9ccfaddb 100644 --- a/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java +++ b/pgjdbc/src/main/java/org/postgresql/util/PGInterval.java @@ -9,7 +9,6 @@ import java.sql.SQLException; import java.util.Calendar; import java.util.Date; -import java.util.Objects; import java.util.StringTokenizer; /** @@ -500,19 +499,14 @@ public boolean equals(Object obj) { && pgi.microSeconds == microSeconds; } - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), years, months, days, hours, minutes, wholeSeconds, - microSeconds); - } - /** * Returns a hashCode for this object. * * @return hashCode */ - public int hashmCode() { - return ((((((7 * 31 + wholeSeconds) * 31 + minutes) * 31 + hours) * 31 + @Override + public int hashCode() { + return (((((((8 * 31 + microSeconds) * 31 + wholeSeconds) * 31 + minutes) * 31 + hours) * 31 + days) * 31 + months) * 31 + years) * 31; }