Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: pginterval to take iso8601 strings #1612

Merged
merged 4 commits into from Nov 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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