Skip to content

Commit

Permalink
fix: pginterval to take iso8601 strings (#1612)
Browse files Browse the repository at this point in the history
* fix: pginterval to take iso8601 strings

* use integers for seconds and microseconds all tests passed
  • Loading branch information
davecramer committed Nov 25, 2019
1 parent 0507979 commit 7b45435
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 35 deletions.
150 changes: 117 additions & 33 deletions pgjdbc/src/main/java/org/postgresql/util/PGInterval.java
Expand Up @@ -7,8 +7,6 @@

import java.io.Serializable;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Calendar;
import java.util.Date;
import java.util.StringTokenizer;
Expand All @@ -19,20 +17,12 @@
public class PGInterval extends PGobject implements Serializable, Cloneable {

private int years;
private int months;
private int days;
private int hours;
private int minutes;
private double seconds;

private static final DecimalFormat secondsFormat;

static {
secondsFormat = new DecimalFormat("0.00####");
DecimalFormatSymbols dfs = secondsFormat.getDecimalFormatSymbols();
dfs.setDecimalSeparator('.');
secondsFormat.setDecimalFormatSymbols(dfs);
}
private byte months;
private byte days;
private byte hours;
private byte minutes;
private int wholeSeconds;
private int microSeconds;

/**
* required by the driver.
Expand All @@ -53,6 +43,66 @@ 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.
*
Expand All @@ -77,10 +127,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;
}
Expand Down Expand Up @@ -153,7 +206,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 {
Expand Down Expand Up @@ -191,7 +244,7 @@ public String getValue() {
+ days + " days "
+ hours + " hours "
+ minutes + " mins "
+ secondsFormat.format(seconds) + " secs";
+ wholeSeconds + '.' + microSeconds + " secs";
}

/**
Expand Down Expand Up @@ -227,7 +280,7 @@ public int getMonths() {
* @param months months to set
*/
public void setMonths(int months) {
this.months = months;
this.months = (byte) months;
}

/**
Expand All @@ -245,7 +298,7 @@ public int getDays() {
* @param days days to set
*/
public void setDays(int days) {
this.days = days;
this.days = (byte)days;
}

/**
Expand All @@ -263,7 +316,7 @@ public int getHours() {
* @param hours hours to set
*/
public void setHours(int hours) {
this.hours = hours;
this.hours = (byte)hours;
}

/**
Expand All @@ -281,7 +334,7 @@ public int getMinutes() {
* @param minutes minutes to set
*/
public void setMinutes(int minutes) {
this.minutes = minutes;
this.minutes = (byte)minutes;
}

/**
Expand All @@ -290,7 +343,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;
}

/**
Expand All @@ -299,7 +367,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;
}
}

/**
Expand All @@ -308,10 +392,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());
Expand Down Expand Up @@ -413,16 +495,18 @@ 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;
}

/**
* Returns a hashCode for this object.
*
* @return hashCode
*/
@Override
public int hashCode() {
return ((((((7 * 31 + (int) Double.doubleToLongBits(seconds)) * 31 + minutes) * 31 + hours) * 31
return (((((((8 * 31 + microSeconds) * 31 + wholeSeconds) * 31 + minutes) * 31 + hours) * 31
+ days) * 31 + months) * 31 + years) * 31;
}

Expand Down
31 changes: 29 additions & 2 deletions pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
Expand Up @@ -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
Expand Down Expand Up @@ -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();

Expand All @@ -269,6 +269,33 @@ 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);
}
Expand Down

0 comments on commit 7b45435

Please sign in to comment.