add feature of support LocalDateTime and test code

This commit is contained in:
justbk
2021-02-02 16:55:25 +08:00
parent 4f82f621cd
commit 9f2da4faaa
21 changed files with 3030 additions and 3 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -40,6 +40,7 @@ import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.math.RoundingMode; import java.math.RoundingMode;
@ -182,6 +183,8 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
sqlTypeToOid.put(Types.DATE, Oid.DATE); sqlTypeToOid.put(Types.DATE, Oid.DATE);
sqlTypeToOid.put(Types.TIME, Oid.UNSPECIFIED); sqlTypeToOid.put(Types.TIME, Oid.UNSPECIFIED);
sqlTypeToOid.put(Types.TIMESTAMP, Oid.UNSPECIFIED); sqlTypeToOid.put(Types.TIMESTAMP, Oid.UNSPECIFIED);
sqlTypeToOid.put(Types.TIME_WITH_TIMEZONE, Oid.UNSPECIFIED);
sqlTypeToOid.put(Types.TIMESTAMP_WITH_TIMEZONE, Oid.UNSPECIFIED);
sqlTypeToOid.put(Types.BOOLEAN, Oid.BOOL); sqlTypeToOid.put(Types.BOOLEAN, Oid.BOOL);
sqlTypeToOid.put(Types.BIT, Oid.BOOL); sqlTypeToOid.put(Types.BIT, Oid.BOOL);
sqlTypeToOid.put(Types.BINARY, Oid.BYTEA); sqlTypeToOid.put(Types.BINARY, Oid.BYTEA);
@ -555,6 +558,9 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
java.sql.Date tmpd; java.sql.Date tmpd;
if (in instanceof java.util.Date) { if (in instanceof java.util.Date) {
tmpd = new java.sql.Date(((java.util.Date) in).getTime()); tmpd = new java.sql.Date(((java.util.Date) in).getTime());
} else if (in instanceof java.time.LocalDate) {
setDate(parameterIndex, (java.time.LocalDate) in);
break;
} else { } else {
tmpd = connection.getTimestampUtils().toDate(getDefaultCalendar(), in.toString()); tmpd = connection.getTimestampUtils().toDate(getDefaultCalendar(), in.toString());
} }
@ -568,6 +574,9 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
java.sql.Time tmpt; java.sql.Time tmpt;
if (in instanceof java.util.Date) { if (in instanceof java.util.Date) {
tmpt = new java.sql.Time(((java.util.Date) in).getTime()); tmpt = new java.sql.Time(((java.util.Date) in).getTime());
} else if (in instanceof java.time.LocalTime) {
setTime(parameterIndex, (java.time.LocalTime) in);
break;
} else { } else {
tmpt = connection.getTimestampUtils().toTime(getDefaultCalendar(), in.toString()); tmpt = connection.getTimestampUtils().toTime(getDefaultCalendar(), in.toString());
} }
@ -583,12 +592,27 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
java.sql.Timestamp tmpts; java.sql.Timestamp tmpts;
if (in instanceof java.util.Date) { if (in instanceof java.util.Date) {
tmpts = new java.sql.Timestamp(((java.util.Date) in).getTime()); tmpts = new java.sql.Timestamp(((java.util.Date) in).getTime());
} else if (in instanceof java.time.LocalDateTime) {
setTimestamp(parameterIndex, (java.time.LocalDateTime) in);
break;
} else { } else {
tmpts = connection.getTimestampUtils().toTimestamp(getDefaultCalendar(), in.toString()); tmpts = connection.getTimestampUtils().toTimestamp(getDefaultCalendar(), in.toString());
} }
setTimestamp(parameterIndex, tmpts); setTimestamp(parameterIndex, tmpts);
} }
break; break;
case Types.TIMESTAMP_WITH_TIMEZONE:
if (in instanceof java.time.OffsetDateTime) {
setTimestamp(parameterIndex, (java.time.OffsetDateTime) in);
} else if (in instanceof PGTimestamp) {
setObject(parameterIndex, in);
} else {
throw new PSQLException(
GT.tr("Cannot cast an instance of {0} to type {1}",
in.getClass().getName(), "Types.TIMESTAMP_WITH_TIMEZONE"),
PSQLState.INVALID_PARAMETER_TYPE);
}
break;
case Types.BOOLEAN: case Types.BOOLEAN:
case Types.BIT: case Types.BIT:
setBoolean(parameterIndex, BooleanTypeUtil.castToBoolean(in)); setBoolean(parameterIndex, BooleanTypeUtil.castToBoolean(in));
@ -923,6 +947,14 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
setObjectOfByte(parameterIndex, x); setObjectOfByte(parameterIndex, x);
} else if (x instanceof java.sql.Date || x instanceof Time || x instanceof Timestamp) { } else if (x instanceof java.sql.Date || x instanceof Time || x instanceof Timestamp) {
setObjectOfDate(parameterIndex, x); setObjectOfDate(parameterIndex, x);
} else if (x instanceof java.time.LocalDate) {
setDate(parameterIndex, (java.time.LocalDate) x);
} else if (x instanceof java.time.LocalTime) {
setTime(parameterIndex, (java.time.LocalTime) x);
} else if (x instanceof java.time.LocalDateTime) {
setTimestamp(parameterIndex, (java.time.LocalDateTime) x);
} else if (x instanceof java.time.OffsetDateTime) {
setTimestamp(parameterIndex, (java.time.OffsetDateTime) x);
} else if (x instanceof Boolean) { } else if (x instanceof Boolean) {
setBoolean(parameterIndex, (Boolean) x); setBoolean(parameterIndex, (Boolean) x);
} else if (x instanceof Blob) { } else if (x instanceof Blob) {
@ -1318,6 +1350,28 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
bindString(i, connection.getTimestampUtils().toString(cal, t), oid); bindString(i, connection.getTimestampUtils().toString(cal, t), oid);
} }
private void setDate(int i, java.time.LocalDate localDate) throws SQLException {
int oid = Oid.DATE;
bindString(i, connection.getTimestampUtils().toString(localDate), oid);
}
private void setTime(int i, java.time.LocalTime localTime) throws SQLException {
int oid = Oid.TIME;
bindString(i, connection.getTimestampUtils().toString(localTime), oid);
}
private void setTimestamp(int i, java.time.LocalDateTime localDateTime)
throws SQLException {
int oid = Oid.TIMESTAMP;
bindString(i, connection.getTimestampUtils().toString(localDateTime), oid);
}
private void setTimestamp(int i, java.time.OffsetDateTime offsetDateTime)
throws SQLException {
int oid = Oid.TIMESTAMPTZ;
bindString(i, connection.getTimestampUtils().toString(offsetDateTime), oid);
}
public ParameterMetaData createParameterMetaData(BaseConnection conn, int[] oids) public ParameterMetaData createParameterMetaData(BaseConnection conn, int[] oids)
throws SQLException { throws SQLException {
return new PgParameterMetaData(conn, oids); return new PgParameterMetaData(conn, oids);

View File

@ -562,6 +562,29 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
return connection.getTimestampUtils().toTime(cal, string); return connection.getTimestampUtils().toTime(cal, string);
} }
private java.time.LocalTime getLocalTime(int i) throws SQLException {
byte[] value = getRawValue(i);
if (value == null) {
return null;
}
if (isBinary(i)) {
int col = i - 1;
int oid = fields[col].getOID();
if (oid == Oid.TIME) {
return connection.getTimestampUtils().toLocalTimeBin(value);
} else {
throw new PSQLException(
GT.tr("Cannot convert the column of type {0} to requested type {1}.",
Oid.toString(oid), "time"),
PSQLState.DATA_TYPE_MISMATCH);
}
}
String string = getString(i);
return connection.getTimestampUtils().toLocalTime(string);
}
@Override @Override
public Timestamp getTimestamp(int i, java.util.Calendar cal) throws SQLException { public Timestamp getTimestamp(int i, java.util.Calendar cal) throws SQLException {
checkResultSet(i); checkResultSet(i);
@ -607,6 +630,69 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
return connection.getTimestampUtils().toTimestamp(cal, string); return connection.getTimestampUtils().toTimestamp(cal, string);
} }
private java.time.OffsetDateTime getOffsetDateTime(int i) throws SQLException {
byte[] value = getRawValue(i);
if (value == null) {
return null;
}
int col = i - 1;
int oid = fields[col].getOID();
if (isBinary(i)) {
if (oid == Oid.TIMESTAMPTZ || oid == Oid.TIMESTAMP) {
return connection.getTimestampUtils().toOffsetDateTimeBin(value);
} else if (oid == Oid.TIMETZ) {
// JDBC spec says timetz must be supported
Time time = getTime(i);
if (time == null) {
return null;
}
return connection.getTimestampUtils().toOffsetDateTime(time);
} else {
throw new PSQLException(
GT.tr("Cannot convert the column of type {0} to requested type {1}.",
Oid.toString(oid), "timestamptz"),
PSQLState.DATA_TYPE_MISMATCH);
}
}
// If this is actually a timestamptz, the server-provided timezone will override
// the one we pass in, which is the desired behaviour. Otherwise, we'll
// interpret the timezone-less value in the provided timezone.
String string = getString(i);
if (oid == Oid.TIMETZ) {
// JDBC spec says timetz must be supported
// If server sends us a TIMETZ, we ensure java counterpart has date of 1970-01-01
Calendar cal = getDefaultCalendar();
Time time = connection.getTimestampUtils().toTime(cal, string);
return connection.getTimestampUtils().toOffsetDateTime(time);
}
return connection.getTimestampUtils().toOffsetDateTime(string);
}
private java.time.LocalDateTime getLocalDateTime(int i) throws SQLException {
byte[] value = getRawValue(i);
if (value == null) {
return null;
}
int col = i - 1;
int oid = fields[col].getOID();
if (oid != Oid.TIMESTAMP) {
throw new PSQLException(
GT.tr("Cannot convert the column of type {0} to requested type {1}.",
Oid.toString(oid), "timestamp"),
PSQLState.DATA_TYPE_MISMATCH);
}
if (isBinary(i)) {
return connection.getTimestampUtils().toLocalDateTimeBin(value);
}
String string = getString(i);
return connection.getTimestampUtils().toLocalDateTime(string);
}
public java.sql.Date getDate(String c, java.util.Calendar cal) throws SQLException { public java.sql.Date getDate(String c, java.util.Calendar cal) throws SQLException {
return getDate(findColumn(c), cal); return getDate(findColumn(c), cal);
} }
@ -2722,6 +2808,27 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
} }
} }
/**
* Checks that the result set is not closed, it's positioned on a valid row and that the given
* column number is valid. Also updates the {@link #wasNullFlag} to correct value.
*
* @param column The column number to check. Range starts from 1.
* @return byte[] value or null
* @throws SQLException If state or column is invalid.
*/
protected byte[] getRawValue(int column) throws SQLException {
checkClosed();
if (this_row == null) {
throw new PSQLException(
GT.tr("ResultSet not positioned properly, perhaps you need to call next."),
PSQLState.INVALID_CURSOR_STATE);
}
checkColumnIndex(column);
byte[] bytes = this_row[column - 1];
wasNullFlag = bytes == null;
return bytes;
}
/** /**
* Checks that the result set is not closed, it's positioned on a valid row and that the given * Checks that the result set is not closed, it's positioned on a valid row and that the given
* column number is valid. Also updates the {@link #wasNullFlag} to correct value. * column number is valid. Also updates the {@link #wasNullFlag} to correct value.
@ -3246,14 +3353,16 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
PSQLState.INVALID_PARAMETER_VALUE); PSQLState.INVALID_PARAMETER_VALUE);
} }
} else if (type == Timestamp.class) { } else if (type == Timestamp.class) {
if (sqlType == Types.TIMESTAMP) { if (sqlType == Types.TIMESTAMP
|| sqlType == Types.TIME_WITH_TIMEZONE) {
return type.cast(getTimestamp(columnIndex)); return type.cast(getTimestamp(columnIndex));
} else { } else {
throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, sqlType), throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, sqlType),
PSQLState.INVALID_PARAMETER_VALUE); PSQLState.INVALID_PARAMETER_VALUE);
} }
} else if (type == Calendar.class) { } else if (type == Calendar.class) {
if (sqlType == Types.TIMESTAMP) { if (sqlType == Types.TIMESTAMP
|| sqlType == Types.TIME_WITH_TIMEZONE) {
Timestamp timestampValue = getTimestamp(columnIndex); Timestamp timestampValue = getTimestamp(columnIndex);
if (wasNull()) { if (wasNull()) {
return null; return null;
@ -3316,6 +3425,53 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
throw new SQLException("could not create inet address from string '" + addressString + "'"); throw new SQLException("could not create inet address from string '" + addressString + "'");
} }
// JSR-310 support
} else if (type == java.time.LocalDate.class) {
if (sqlType == Types.DATE) {
Date dateValue = getDate(columnIndex);
if (dateValue == null) {
return null;
}
long time = dateValue.getTime();
if (time == PGStatement.DATE_POSITIVE_INFINITY) {
return type.cast(java.time.LocalDate.MAX);
}
if (time == PGStatement.DATE_NEGATIVE_INFINITY) {
return type.cast(java.time.LocalDate.MIN);
}
return type.cast(dateValue.toLocalDate());
} else if (sqlType == Types.TIMESTAMP) {
java.time.LocalDateTime localDateTimeValue = getLocalDateTime(columnIndex);
if (localDateTimeValue == null) {
return null;
}
return type.cast(localDateTimeValue.toLocalDate());
} else {
throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
PSQLState.INVALID_PARAMETER_VALUE);
}
} else if (type == java.time.LocalTime.class) {
if (sqlType == Types.TIME) {
return type.cast(getLocalTime(columnIndex));
} else {
throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
PSQLState.INVALID_PARAMETER_VALUE);
}
} else if (type == java.time.LocalDateTime.class) {
if (sqlType == Types.TIMESTAMP) {
return type.cast(getLocalDateTime(columnIndex));
} else {
throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
PSQLState.INVALID_PARAMETER_VALUE);
}
} else if (type == java.time.OffsetDateTime.class) {
if (sqlType == Types.TIMESTAMP_WITH_TIMEZONE || sqlType == Types.TIMESTAMP) {
java.time.OffsetDateTime offsetDateTime = getOffsetDateTime(columnIndex);
return type.cast(offsetDateTime);
} else {
throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
PSQLState.INVALID_PARAMETER_VALUE);
}
} else if (PGobject.class.isAssignableFrom(type)) { } else if (PGobject.class.isAssignableFrom(type)) {
Object object; Object object;
if (isBinary(columnIndex)) { if (isBinary(columnIndex)) {

View File

@ -40,6 +40,16 @@ public class TimestampUtils {
private static final char[][] NUMBERS; private static final char[][] NUMBERS;
private static final HashMap<String, TimeZone> GMT_ZONES = new HashMap<String, TimeZone>(); private static final HashMap<String, TimeZone> GMT_ZONES = new HashMap<String, TimeZone>();
private static final int MAX_NANOS_BEFORE_WRAP_ON_ROUND = 999999500; private static final int MAX_NANOS_BEFORE_WRAP_ON_ROUND = 999999500;
private static final java.time.Duration ONE_MICROSECOND = java.time.Duration.ofNanos(1000);
// LocalTime.MAX is 23:59:59.999_999_999, and it wraps to 24:00:00 when nanos exceed 999_999_499
// since PostgreSQL has microsecond resolution only
private static final java.time.LocalTime MAX_TIME = java.time.LocalTime.MAX.minus(java.time.Duration.ofNanos(500));
private static final java.time.OffsetDateTime MAX_OFFSET_DATETIME = java.time.OffsetDateTime.MAX.minus(java.time.Duration.ofMillis(500));
private static final java.time.LocalDateTime MAX_LOCAL_DATETIME = java.time.LocalDateTime.MAX.minus(java.time.Duration.ofMillis(500));
// low value for dates is 4713 BC
private static final java.time.LocalDate MIN_LOCAL_DATE = java.time.LocalDate.of(4713, 1, 1).with(java.time.temporal.ChronoField.ERA, java.time.chrono.IsoEra.BCE.getValue());
private static final java.time.LocalDateTime MIN_LOCAL_DATETIME = MIN_LOCAL_DATE.atStartOfDay();
private static final java.time.OffsetDateTime MIN_OFFSET_DATETIME = MIN_LOCAL_DATETIME.atOffset(java.time.ZoneOffset.UTC);
private static final Field DEFAULT_TIME_ZONE_FIELD; private static final Field DEFAULT_TIME_ZONE_FIELD;
private static Log LOGGER = Logger.getLogger(TimestampUtils.class.getName()); private static Log LOGGER = Logger.getLogger(TimestampUtils.class.getName());
@ -112,7 +122,7 @@ public class TimestampUtils {
private final boolean usesDouble; private final boolean usesDouble;
private final Provider<TimeZone> timeZoneProvider; private final Provider<TimeZone> timeZoneProvider;
TimestampUtils(boolean usesDouble, Provider<TimeZone> timeZoneProvider) { public TimestampUtils(boolean usesDouble, Provider<TimeZone> timeZoneProvider) {
this.usesDouble = usesDouble; this.usesDouble = usesDouble;
this.timeZoneProvider = timeZoneProvider; this.timeZoneProvider = timeZoneProvider;
} }
@ -391,6 +401,143 @@ public class TimestampUtils {
return result; return result;
} }
/**
* Parse a string and return a LocalTime representing its value.
*
* @param s The ISO formated time string to parse.
* @return null if s is null or a LocalTime of the parsed string s.
* @throws SQLException if there is a problem parsing s.
*/
public java.time.LocalTime toLocalTime(String s) throws SQLException {
if (s == null) {
return null;
}
if (s.equals("24:00:00")) {
return java.time.LocalTime.MAX;
}
try {
return java.time.LocalTime.parse(s);
} catch (java.time.format.DateTimeParseException nfe) {
throw new PSQLException(
GT.tr("Bad value for type timestamp/date/time: {1}", s),
PSQLState.BAD_DATETIME_FORMAT, nfe);
}
}
/**
* Parse a string and return a LocalDateTime representing its value.
*
* @param s The ISO formated date string to parse.
* @return null if s is null or a LocalDateTime of the parsed string s.
* @throws SQLException if there is a problem parsing s.
*/
public java.time.LocalDateTime toLocalDateTime(String s) throws SQLException {
if (s == null) {
return null;
}
int slen = s.length();
// convert postgres's infinity values to internal infinity magic value
if (slen == 8 && s.equals("infinity")) {
return java.time.LocalDateTime.MAX;
}
if (slen == 9 && s.equals("-infinity")) {
return java.time.LocalDateTime.MIN;
}
ParsedTimestamp ts = parseBackendTimestamp(s);
// intentionally ignore time zone
// 2004-10-19 10:23:54+03:00 is 2004-10-19 10:23:54 locally
java.time.LocalDateTime result = java.time.LocalDateTime.of(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.nanos);
if (ts.era == GregorianCalendar.BC) {
return result.with(java.time.temporal.ChronoField.ERA, java.time.chrono.IsoEra.BCE.getValue());
} else {
return result;
}
}
/**
* Parse a string and return a LocalDateTime representing its value.
*
* @param s The ISO formated date string to parse.
* @return null if s is null or a LocalDateTime of the parsed string s.
* @throws SQLException if there is a problem parsing s.
*/
public java.time.OffsetDateTime toOffsetDateTime(
String s) throws SQLException {
if (s == null) {
return null;
}
int slen = s.length();
// convert postgres's infinity values to internal infinity magic value
if (slen == 8 && s.equals("infinity")) {
return java.time.OffsetDateTime.MAX;
}
if (slen == 9 && s.equals("-infinity")) {
return java.time.OffsetDateTime.MIN;
}
ParsedTimestamp ts = parseBackendTimestamp(s);
Calendar tz = ts.tz;
int offsetSeconds;
if (tz == null) {
offsetSeconds = 0;
} else {
offsetSeconds = tz.get(Calendar.ZONE_OFFSET) / 1000;
}
java.time.ZoneOffset zoneOffset = java.time.ZoneOffset.ofTotalSeconds(offsetSeconds);
// Postgres is always UTC
java.time.OffsetDateTime result = java.time.OffsetDateTime.of(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.nanos, zoneOffset)
.withOffsetSameInstant(java.time.ZoneOffset.UTC);
if (ts.era == GregorianCalendar.BC) {
return result.with(java.time.temporal.ChronoField.ERA, java.time.chrono.IsoEra.BCE.getValue());
} else {
return result;
}
}
/**
* Returns the offset date time object matching the given bytes with Oid#TIMETZ.
*
* @param t the time value
* @return the matching offset date time
*/
public java.time.OffsetDateTime toOffsetDateTime(Time t) {
// hardcode utc because the backend does not provide us the timezone
// hardcode UNIX epoch, JDBC requires OffsetDateTime but doesn't describe what date should be used
return t.toLocalTime().atDate(java.time.LocalDate.of(1970, 1, 1)).atOffset(java.time.ZoneOffset.UTC);
}
/**
* Returns the offset date time object matching the given bytes with Oid#TIMESTAMPTZ.
*
* @param bytes The binary encoded local date time value.
* @return The parsed local date time object.
* @throws PSQLException If binary format could not be parsed.
*/
public java.time.OffsetDateTime toOffsetDateTimeBin(byte[] bytes) throws PSQLException {
ParsedBinaryTimestamp parsedTimestamp = this.toProlepticParsedTimestampBin(bytes);
if (parsedTimestamp.infinity == Infinity.POSITIVE) {
return java.time.OffsetDateTime.MAX;
} else if (parsedTimestamp.infinity == Infinity.NEGATIVE) {
return java.time.OffsetDateTime.MIN;
}
// hardcode utc because the backend does not provide us the timezone
// Postgres is always UTC
java.time.Instant instant = java.time.Instant.ofEpochSecond(parsedTimestamp.millis / 1000L, parsedTimestamp.nanos);
return java.time.OffsetDateTime.ofInstant(instant, java.time.ZoneOffset.UTC);
}
public synchronized Time toTime(Calendar cal, String s) throws SQLException { public synchronized Time toTime(Calendar cal, String s) throws SQLException {
// 1) Parse backend string // 1) Parse backend string
@ -675,6 +822,111 @@ public class TimestampUtils {
} }
} }
public synchronized String toString(java.time.LocalDate localDate) {
if (java.time.LocalDate.MAX.equals(localDate)) {
return "infinity";
} else if (localDate.isBefore(MIN_LOCAL_DATE)) {
return "-infinity";
}
sbuf.setLength(0);
appendDate(sbuf, localDate);
appendEra(sbuf, localDate);
return sbuf.toString();
}
public synchronized String toString(java.time.LocalTime localTime) {
sbuf.setLength(0);
if (localTime.isAfter(MAX_TIME)) {
return "24:00:00";
}
int nano = localTime.getNano();
if (nanosExceed499(nano)) {
// Technically speaking this is not a proper rounding, however
// it relies on the fact that appendTime just truncates 000..999 nanosecond part
localTime = localTime.plus(ONE_MICROSECOND);
}
appendTime(sbuf, localTime);
return sbuf.toString();
}
public synchronized String toString(java.time.OffsetDateTime offsetDateTime) {
if (offsetDateTime.isAfter(MAX_OFFSET_DATETIME)) {
return "infinity";
} else if (offsetDateTime.isBefore(MIN_OFFSET_DATETIME)) {
return "-infinity";
}
sbuf.setLength(0);
int nano = offsetDateTime.getNano();
if (nanosExceed499(nano)) {
// Technically speaking this is not a proper rounding, however
// it relies on the fact that appendTime just truncates 000..999 nanosecond part
offsetDateTime = offsetDateTime.plus(ONE_MICROSECOND);
}
java.time.LocalDateTime localDateTime = offsetDateTime.toLocalDateTime();
java.time.LocalDate localDate = localDateTime.toLocalDate();
appendDate(sbuf, localDate);
sbuf.append(' ');
appendTime(sbuf, localDateTime.toLocalTime());
appendTimeZone(sbuf, offsetDateTime.getOffset());
appendEra(sbuf, localDate);
return sbuf.toString();
}
/**
* Formats {@link java.time.LocalDateTime} to be sent to the backend, thus it adds time zone.
* Do not use this method in {@link java.sql.ResultSet#getString(int)}
* @param localDateTime The local date to format as a String
* @return The formatted local date
*/
public synchronized String toString(java.time.LocalDateTime localDateTime) {
if (localDateTime.isAfter(MAX_LOCAL_DATETIME)) {
return "infinity";
} else if (localDateTime.isBefore(MIN_LOCAL_DATETIME)) {
return "-infinity";
}
// LocalDateTime is always passed with time zone so backend can decide between timestamp and timestamptz
java.time.ZonedDateTime zonedDateTime = localDateTime.atZone(getDefaultTz().toZoneId());
return toString(zonedDateTime.toOffsetDateTime());
}
private static void appendDate(StringBuilder sb, java.time.LocalDate localDate) {
int year = localDate.get(java.time.temporal.ChronoField.YEAR_OF_ERA);
int month = localDate.getMonthValue();
int day = localDate.getDayOfMonth();
appendDate(sb, year, month, day);
}
private static void appendTime(StringBuilder sb, java.time.LocalTime localTime) {
int hours = localTime.getHour();
int minutes = localTime.getMinute();
int seconds = localTime.getSecond();
int nanos = localTime.getNano();
appendTime(sb, hours, minutes, seconds, nanos);
}
private void appendTimeZone(StringBuilder sb, java.time.ZoneOffset offset) {
int offsetSeconds = offset.getTotalSeconds();
appendTimeZone(sb, offsetSeconds);
}
private static void appendEra(StringBuilder sb, java.time.LocalDate localDate) {
if (localDate.get(java.time.temporal.ChronoField.ERA) == java.time.chrono.IsoEra.BCE.getValue()) {
sb.append(" BC");
}
}
private static int skipWhitespace(char[] s, int start) { private static int skipWhitespace(char[] s, int start) {
int slen = s.length; int slen = s.length;
for (int i = start; i < slen; i++) { for (int i = start; i < slen; i++) {
@ -816,6 +1068,32 @@ public class TimestampUtils {
return convertToTime(millis, tz); // Ensure date part is 1970-01-01 return convertToTime(millis, tz); // Ensure date part is 1970-01-01
} }
/**
* Returns the SQL Time object matching the given bytes with {@link Oid#TIME}.
*
* @param bytes The binary encoded time value.
* @return The parsed time object.
* @throws PSQLException If binary format could not be parsed.
*/
public java.time.LocalTime toLocalTimeBin(byte[] bytes) throws PSQLException {
if (bytes.length != 8) {
throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "time"),
PSQLState.BAD_DATETIME_FORMAT);
}
long micros;
if (usesDouble) {
double seconds = ByteConverter.float8(bytes, 0);
micros = (long) (seconds * 1000000d);
} else {
micros = ByteConverter.int8(bytes, 0);
}
return java.time.LocalTime.ofNanoOfDay(micros * 1000);
}
/** /**
* Returns the SQL Timestamp object matching the given bytes with {@link Oid#TIMESTAMP} or * Returns the SQL Timestamp object matching the given bytes with {@link Oid#TIMESTAMP} or
* {@link Oid#TIMESTAMPTZ}. * {@link Oid#TIMESTAMPTZ}.
@ -1174,4 +1452,102 @@ public class TimestampUtils {
} }
return TimeZone.getTimeZone(timeZone); return TimeZone.getTimeZone(timeZone);
} }
private ParsedBinaryTimestamp toParsedTimestampBinPlain(byte[] bytes)
throws PSQLException {
if (bytes.length != 8) {
throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "timestamp"),
PSQLState.BAD_DATETIME_FORMAT);
}
long secs;
int nanos;
if (usesDouble) {
double time = ByteConverter.float8(bytes, 0);
if (time == Double.POSITIVE_INFINITY) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.POSITIVE;
return ts;
} else if (time == Double.NEGATIVE_INFINITY) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.NEGATIVE;
return ts;
}
secs = (long) time;
nanos = (int) ((time - secs) * 1000000);
} else {
long time = ByteConverter.int8(bytes, 0);
// compatibility with text based receiving, not strictly necessary
// and can actually be confusing because there are timestamps
// that are larger than infinite
if (time == Long.MAX_VALUE) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.POSITIVE;
return ts;
} else if (time == Long.MIN_VALUE) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.NEGATIVE;
return ts;
}
secs = time / 1000000;
nanos = (int) (time - secs * 1000000);
}
if (nanos < 0) {
secs--;
nanos += 1000000;
}
nanos *= 1000;
long millis = secs * 1000L;
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.millis = millis;
ts.nanos = nanos;
return ts;
}
private ParsedBinaryTimestamp toProlepticParsedTimestampBin(byte[] bytes)
throws PSQLException {
ParsedBinaryTimestamp ts = toParsedTimestampBinPlain(bytes);
if (ts.infinity != null) {
return ts;
}
long secs = ts.millis / 1000L;
// postgres epoc to java epoc
secs += 946684800L;
long millis = secs * 1000L;
ts.millis = millis;
return ts;
}
/**
* Returns the local date time object matching the given bytes with {@link Oid#TIMESTAMP} or
* {@link Oid#TIMESTAMPTZ}.
* @param bytes The binary encoded local date time value.
*
* @return The parsed local date time object.
* @throws PSQLException If binary format could not be parsed.
*/
public java.time.LocalDateTime toLocalDateTimeBin(byte[] bytes) throws PSQLException {
ParsedBinaryTimestamp parsedTimestamp = this.toProlepticParsedTimestampBin(bytes);
if (parsedTimestamp.infinity == Infinity.POSITIVE) {
return java.time.LocalDateTime.MAX;
} else if (parsedTimestamp.infinity == Infinity.NEGATIVE) {
return java.time.LocalDateTime.MIN;
}
// hardcode utc because the backend does not provide us the timezone
// Postgres is always UTC
return java.time.LocalDateTime.ofEpochSecond(parsedTimestamp.millis / 1000L, parsedTimestamp.nanos, java.time.ZoneOffset.UTC);
}
} }

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2004, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import org.postgresql.PGConnection;
import org.postgresql.PGProperty;
import org.postgresql.core.Version;
import org.postgresql.jdbc.PreferQueryMode;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class BaseTest4 {
public enum BinaryMode {
REGULAR, FORCE
}
public enum ReWriteBatchedInserts {
YES, NO
}
public enum AutoCommit {
YES, NO
}
public enum StringType {
UNSPECIFIED, VARCHAR;
}
protected Connection con;
private BinaryMode binaryMode;
private ReWriteBatchedInserts reWriteBatchedInserts;
protected PreferQueryMode preferQueryMode;
private StringType stringType;
protected void updateProperties(Properties props) {
if (binaryMode == BinaryMode.FORCE) {
forceBinary(props);
}
if (reWriteBatchedInserts == ReWriteBatchedInserts.YES) {
PGProperty.REWRITE_BATCHED_INSERTS.set(props, true);
}
if (stringType != null) {
PGProperty.STRING_TYPE.set(props, stringType.name().toLowerCase());
}
}
protected void forceBinary(Properties props) {
PGProperty.PREPARE_THRESHOLD.set(props, -1);
}
public final void setBinaryMode(BinaryMode binaryMode) {
this.binaryMode = binaryMode;
}
public StringType getStringType() {
return stringType;
}
public void setStringType(StringType stringType) {
this.stringType = stringType;
}
public void setReWriteBatchedInserts(
ReWriteBatchedInserts reWriteBatchedInserts) {
this.reWriteBatchedInserts = reWriteBatchedInserts;
}
@After
public void tearDown() throws SQLException {
TestUtil.closeDB(con);
}
@Before
public void setUp() throws Exception {
Properties props = new Properties();
updateProperties(props);
con = TestUtil.openDB(props);
PGConnection pg = con.unwrap(PGConnection.class);
preferQueryMode = pg == null ? PreferQueryMode.EXTENDED : pg.getPreferQueryMode();
}
public void assumeByteaSupported() {
Assume.assumeTrue("bytea is not supported in simple protocol execution mode",
preferQueryMode.compareTo(PreferQueryMode.EXTENDED) >= 0);
}
public void assumeCallableStatementsSupported() {
Assume.assumeTrue("callable statements are not fully supported in simple protocol execution mode",
preferQueryMode.compareTo(PreferQueryMode.EXTENDED) >= 0);
}
public void assumeBinaryModeRegular() {
Assume.assumeTrue(binaryMode == BinaryMode.REGULAR);
}
public void assumeBinaryModeForce() {
Assume.assumeTrue(binaryMode == BinaryMode.FORCE);
Assume.assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE);
}
/**
* Shorthand for {@code Assume.assumeTrue(TestUtil.haveMinimumServerVersion(conn, version)}.
*/
public void assumeMinimumServerVersion(String message, Version version) throws SQLException {
Assume.assumeTrue(message, TestUtil.haveMinimumServerVersion(con, version));
}
/**
* Shorthand for {@code Assume.assumeTrue(TestUtil.haveMinimumServerVersion(conn, version)}.
*/
public void assumeMinimumServerVersion(Version version) throws SQLException {
Assume.assumeTrue(TestUtil.haveMinimumServerVersion(con, version));
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2017, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import org.postgresql.core.ServerVersion;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@RunWith(Parameterized.class)
public class GetObject310InfinityTests extends BaseTest4 {
private final String expression;
private final String pgType;
private final Class<?> klass;
private final Object expectedValue;
public GetObject310InfinityTests(BinaryMode binaryMode, String expression,
String pgType, Class<?> klass, Object expectedValue) {
setBinaryMode(binaryMode);
this.expression = expression;
this.pgType = pgType;
this.klass = klass;
this.expectedValue = expectedValue;
}
@Override
public void setUp() throws Exception {
super.setUp();
Assume.assumeTrue("PostgreSQL 8.3 does not support 'infinity' for 'date'",
!"date".equals(pgType) || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4));
}
@Parameterized.Parameters(name = "binary = {0}, expr = {1}, pgType = {2}, klass = {3}")
public static Iterable<Object[]> data() throws IllegalAccessException {
Collection<Object[]> ids = new ArrayList<Object[]>();
for (BinaryMode binaryMode : BinaryMode.values()) {
for (String expression : Arrays.asList("-infinity", "infinity")) {
for (String pgType : Arrays.asList("date", "timestamp",
"timestamp with time zone")) {
for (Class<?> klass : Arrays.asList(LocalDate.class, LocalDateTime.class,
OffsetDateTime.class)) {
if (klass.equals(LocalDate.class) && !pgType.equals("date")) {
continue;
}
if (klass.equals(LocalDateTime.class) && !pgType.startsWith("timestamp")) {
continue;
}
if (klass.equals(OffsetDateTime.class) && !pgType.startsWith("timestamp")) {
continue;
}
if (klass.equals(LocalDateTime.class) && pgType.equals("timestamp with time zone")) {
// org.postgresql.util.PSQLException: Cannot convert the column of type TIMESTAMPTZ to requested type timestamp.
continue;
}
Field field = null;
try {
field = klass.getField(expression.startsWith("-") ? "MIN" : "MAX");
} catch (NoSuchFieldException e) {
throw new IllegalStateException("No min/max field in " + klass, e);
}
Object expected = field.get(null);
ids.add(new Object[]{binaryMode, expression, pgType, klass, expected});
}
}
}
}
return ids;
}
@Test
public void test() throws SQLException {
PreparedStatement stmt = con.prepareStatement("select '" + expression + "'::" + pgType);
ResultSet rs = stmt.executeQuery();
rs.next();
Object res = rs.getObject(1, klass);
Assert.assertEquals(expectedValue, res);
}
}

View File

@ -0,0 +1,372 @@
/*
* Copyright (c) 2004, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import org.postgresql.core.ServerVersion;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.chrono.IsoEra;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.TimeZone;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@RunWith(Parameterized.class)
public class GetObject310Test extends BaseTest4 {
private static final TimeZone saveTZ = TimeZone.getDefault();
private static final ZoneOffset UTC = ZoneOffset.UTC; // +0000 always
private static final ZoneOffset GMT03 = ZoneOffset.of("+03:00"); // +0300 always
private static final ZoneOffset GMT05 = ZoneOffset.of("-05:00"); // -0500 always
private static final ZoneOffset GMT13 = ZoneOffset.of("+13:00"); // +1300 always
public GetObject310Test(BinaryMode binaryMode) {
setBinaryMode(binaryMode);
}
@Parameterized.Parameters(name = "binary = {0}")
public static Iterable<Object[]> data() {
Collection<Object[]> ids = new ArrayList<Object[]>();
for (BinaryMode binaryMode : BinaryMode.values()) {
ids.add(new Object[]{binaryMode});
}
return ids;
}
@Override
public void setUp() throws Exception {
super.setUp();
TestUtil.createTable(con, "table1", "timestamp_without_time_zone_column timestamp without time zone,"
+ "timestamp_with_time_zone_column timestamp with time zone,"
+ "date_column date,"
+ "time_without_time_zone_column time without time zone,"
+ "time_with_time_zone_column time with time zone"
);
}
@Override
public void tearDown() throws SQLException {
TimeZone.setDefault(saveTZ);
TestUtil.dropTable(con, "table1");
super.tearDown();
}
/**
* Test the behavior getObject for date columns.
*/
@Test
public void testGetLocalDate() throws SQLException {
Statement stmt = con.createStatement();
stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","DATE '1999-01-08'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "date_column"));
try {
assertTrue(rs.next());
LocalDate localDate = LocalDate.of(1999, 1, 8);
assertEquals(localDate, rs.getObject("date_column", LocalDate.class));
assertEquals(localDate, rs.getObject(1, LocalDate.class));
} finally {
rs.close();
}
}
/**
* Test the behavior getObject for time columns.
*/
@Test
public void testGetLocalTime() throws SQLException {
Statement stmt = con.createStatement();
stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","TIME '04:05:06.123456'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"));
try {
assertTrue(rs.next());
LocalTime localTime = LocalTime.of(4, 5, 6, 123456000);
assertEquals(localTime, rs.getObject("time_without_time_zone_column", LocalTime.class));
assertEquals(localTime, rs.getObject(1, LocalTime.class));
} finally {
rs.close();
}
}
/**
* Test the behavior getObject for time columns with null.
*/
@Test
public void testGetLocalTimeNull() throws SQLException {
Statement stmt = con.createStatement();
stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","NULL"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"));
try {
assertTrue(rs.next());
assertNull(rs.getObject("time_without_time_zone_column", LocalTime.class));
assertNull(rs.getObject(1, LocalTime.class));
} finally {
rs.close();
}
}
/**
* Test the behavior getObject for time columns with null.
*/
@Test
public void testGetLocalTimeInvalidType() throws SQLException {
Statement stmt = con.createStatement();
stmt.executeUpdate(TestUtil.insertSQL("table1","time_with_time_zone_column", "TIME '04:05:06.123456-08:00'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column"));
try {
assertTrue(rs.next());
try {
assertNull(rs.getObject("time_with_time_zone_column", LocalTime.class));
} catch (PSQLException e) {
assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState())
|| e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState()));
}
try {
assertNull(rs.getObject(1, LocalTime.class));
} catch (PSQLException e) {
assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState())
|| e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState()));
}
} finally {
rs.close();
}
}
/**
* Test the behavior getObject for timestamp columns.
*/
@Test
public void testGetLocalDateTime() throws SQLException {
assumeTrue(TestUtil.haveIntegerDateTimes(con));
List<String> zoneIdsToTest = new ArrayList<String>();
zoneIdsToTest.add("Africa/Casablanca"); // It is something like GMT+0..GMT+1
zoneIdsToTest.add("America/Adak"); // It is something like GMT-10..GMT-9
zoneIdsToTest.add("Atlantic/Azores"); // It is something like GMT-1..GMT+0
zoneIdsToTest.add("Europe/Moscow"); // It is something like GMT+3..GMT+4 for 2000s
zoneIdsToTest.add("Pacific/Apia"); // It is something like GMT+13..GMT+14
zoneIdsToTest.add("Pacific/Niue"); // It is something like GMT-11..GMT-11
for (int i = -12; i <= 13; i++) {
zoneIdsToTest.add(String.format("GMT%+02d", i));
}
List<String> datesToTest = Arrays.asList("2015-09-03T12:00:00", "2015-06-30T23:59:58",
"1997-06-30T23:59:59", "1997-07-01T00:00:00", "2012-06-30T23:59:59", "2012-07-01T00:00:00",
"2015-06-30T23:59:59", "2015-07-01T00:00:00", "2005-12-31T23:59:59", "2006-01-01T00:00:00",
"2008-12-31T23:59:59", "2009-01-01T00:00:00", /* "2015-06-30T23:59:60", */ "2015-07-31T00:00:00",
"2015-07-31T00:00:01", "2015-07-31T00:00:00.000001",
// On 2000-03-26 02:00:00 Moscow went to DST, thus local time became 03:00:00
"2000-03-26T01:59:59", "2000-03-26T02:00:00", "2000-03-26T02:00:01", "2000-03-26T02:59:59",
"2000-03-26T03:00:00", "2000-03-26T03:00:01", "2000-03-26T03:59:59", "2000-03-26T04:00:00",
"2000-03-26T04:00:01", "2000-03-26T04:00:00.000001",
// This is a pre-1970 date, so check if it is rounded properly
"1950-07-20T02:00:00",
// Ensure the calendar is proleptic
"1582-09-30T00:00:00", "1582-10-16T00:00:00",
// On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00
"2000-10-29T01:59:59", "2000-10-29T02:00:00", "2000-10-29T02:00:01", "2000-10-29T02:59:59",
"2000-10-29T03:00:00", "2000-10-29T03:00:01", "2000-10-29T03:59:59", "2000-10-29T04:00:00",
"2000-10-29T04:00:01", "2000-10-29T04:00:00.000001");
for (String zoneId : zoneIdsToTest) {
ZoneId zone = ZoneId.of(zoneId);
for (String date : datesToTest) {
localTimestamps(zone, date);
}
}
}
public void localTimestamps(ZoneId zoneId, String timestamp) throws SQLException {
TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
Statement stmt = con.createStatement();
try {
stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_without_time_zone_column","TIMESTAMP '" + timestamp + "'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_without_time_zone_column"));
try {
assertTrue(rs.next());
LocalDateTime localDateTime = LocalDateTime.parse(timestamp);
assertEquals(localDateTime, rs.getObject("timestamp_without_time_zone_column", LocalDateTime.class));
assertEquals(localDateTime, rs.getObject(1, LocalDateTime.class));
//Also test that we get the correct values when retrieving the data as LocalDate objects
assertEquals(localDateTime.toLocalDate(), rs.getObject("timestamp_without_time_zone_column", LocalDate.class));
assertEquals(localDateTime.toLocalDate(), rs.getObject(1, LocalDate.class));
} finally {
rs.close();
}
stmt.executeUpdate("DELETE FROM table1");
} finally {
stmt.close();
}
}
/**
* Test the behavior getObject for timestamp with time zone columns.
*/
@Test
public void testGetTimestampWithTimeZone() throws SQLException {
runGetOffsetDateTime(UTC);
runGetOffsetDateTime(GMT03);
runGetOffsetDateTime(GMT05);
runGetOffsetDateTime(GMT13);
}
private void runGetOffsetDateTime(ZoneOffset offset) throws SQLException {
Statement stmt = con.createStatement();
try {
stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_with_time_zone_column","TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54.123456" + offset.toString() + "'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_with_time_zone_column"));
try {
assertTrue(rs.next());
LocalDateTime localDateTime = LocalDateTime.of(2004, 10, 19, 10, 23, 54, 123456000);
OffsetDateTime offsetDateTime = localDateTime.atOffset(offset).withOffsetSameInstant(ZoneOffset.UTC);
assertEquals(offsetDateTime, rs.getObject("timestamp_with_time_zone_column", OffsetDateTime.class));
assertEquals(offsetDateTime, rs.getObject(1, OffsetDateTime.class));
} finally {
rs.close();
}
stmt.executeUpdate("DELETE FROM table1");
} finally {
stmt.close();
}
}
@Test
public void testBcTimestamp() throws SQLException {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56 BC'::timestamp");
try {
assertTrue(rs.next());
LocalDateTime expected = LocalDateTime.of(1582, 9, 30, 12, 34, 56)
.with(ChronoField.ERA, IsoEra.BCE.getValue());
LocalDateTime actual = rs.getObject(1, LocalDateTime.class);
assertEquals(expected, actual);
assertFalse(rs.next());
} finally {
rs.close();
stmt.close();
}
}
@Test
public void testBcTimestamptz() throws SQLException {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56Z BC'::timestamp");
try {
assertTrue(rs.next());
OffsetDateTime expected = OffsetDateTime.of(1582, 9, 30, 12, 34, 56, 0, UTC)
.with(ChronoField.ERA, IsoEra.BCE.getValue());
OffsetDateTime actual = rs.getObject(1, OffsetDateTime.class);
assertEquals(expected, actual);
assertFalse(rs.next());
} finally {
rs.close();
stmt.close();
}
}
@Test
public void testProlepticCalendarTimestamp() throws SQLException {
// date time ranges and CTEs are both new with 8.4
assumeMinimumServerVersion(ServerVersion.v8_4);
LocalDateTime start = LocalDate.of(1582, 9, 30).atStartOfDay();
LocalDateTime end = LocalDate.of(1582, 10, 16).atStartOfDay();
long numberOfDays = Duration.between(start, end).toDays() + 1L;
List<LocalDateTime> range = Stream.iterate(start, new LocalDateTimePlusOneDay())
.limit(numberOfDays)
.collect(Collectors.toList());
runProlepticTests(LocalDateTime.class, "'1582-09-30 00:00'::timestamp, '1582-10-16 00:00'::timestamp", range);
}
@Test
public void testProlepticCalendarTimestamptz() throws SQLException {
// date time ranges and CTEs are both new with 8.4
assumeMinimumServerVersion(ServerVersion.v8_4);
OffsetDateTime start = LocalDate.of(1582, 9, 30).atStartOfDay().atOffset(UTC);
OffsetDateTime end = LocalDate.of(1582, 10, 16).atStartOfDay().atOffset(UTC);
long numberOfDays = Duration.between(start, end).toDays() + 1L;
List<OffsetDateTime> range = Stream.iterate(start, new OffsetDateTimePlusOneDay())
.limit(numberOfDays)
.collect(Collectors.toList());
runProlepticTests(OffsetDateTime.class, "'1582-09-30 00:00:00 Z'::timestamptz, '1582-10-16 00:00:00 Z'::timestamptz", range);
}
private <T extends Temporal> void runProlepticTests(Class<T> clazz, String selectRange, List<T> range) throws SQLException {
List<T> temporals = new ArrayList<>(range.size());
PreparedStatement stmt = con.prepareStatement("SELECT * FROM generate_series(" + selectRange + ", '1 day');");
ResultSet rs = stmt.executeQuery();
try {
while (rs.next()) {
T temporal = rs.getObject(1, clazz);
temporals.add(temporal);
}
assertEquals(range, temporals);
} finally {
rs.close();
stmt.close();
}
}
private static class LocalDateTimePlusOneDay implements UnaryOperator<LocalDateTime> {
@Override
public LocalDateTime apply(LocalDateTime x) {
return x.plusDays(1);
}
}
private static class OffsetDateTimePlusOneDay implements UnaryOperator<OffsetDateTime> {
@Override
public OffsetDateTime apply(OffsetDateTime x) {
return x.plusDays(1);
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2004, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
GetObject310InfinityTests.class,
GetObject310Test.class,
PreparedStatementTest.class,
SetObject310Test.class,
SetObject310InfinityTests.class,
TimestampUtilsTest.class
})
public class Jdbc42TestSuite {
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2004, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import org.postgresql.PGProperty;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.LocalTime;
import java.util.Properties;
public class PreparedStatementTest extends BaseTest4 {
protected void updateProperties(Properties props) {
PGProperty.PREFER_QUERY_MODE.set(props, "simple");
}
@Override
public void setUp() throws Exception {
super.setUp();
TestUtil.createTable(con, "timestamptztable", "tstz timestamptz");
TestUtil.createTable(con, "timetztable", "ttz timetz");
TestUtil.createTable(con, "timetable", "id serial, tt time");
}
@Override
public void tearDown() throws SQLException {
TestUtil.dropTable(con, "timestamptztable");
TestUtil.dropTable(con, "timetztable");
TestUtil.dropTable(con, "timetable");
super.tearDown();
}
// @Test
public void testSetNumber() throws SQLException {
PreparedStatement pstmt = con.prepareStatement("SELECT ? * 2");
pstmt.setBigDecimal(1, new BigDecimal("1.6"));
ResultSet rs = pstmt.executeQuery();
rs.next();
BigDecimal d = rs.getBigDecimal(1);
pstmt.close();
Assert.assertEquals(new BigDecimal("3.2"), d);
}
@Test
public void testTimestampTzSetNull() throws SQLException {
PreparedStatement pstmt = con.prepareStatement("INSERT INTO timestamptztable (tstz) VALUES (?)");
// valid: fully qualified type to setNull()
pstmt.setNull(1, Types.TIMESTAMP_WITH_TIMEZONE);
pstmt.executeUpdate();
// valid: fully qualified type to setObject()
pstmt.setObject(1, null, Types.TIMESTAMP_WITH_TIMEZONE);
pstmt.executeUpdate();
pstmt.close();
}
@Test
public void testTimeTzSetNull() throws SQLException {
PreparedStatement pstmt = con.prepareStatement("INSERT INTO timetztable (ttz) VALUES (?)");
// valid: fully qualified type to setNull()
pstmt.setNull(1, Types.TIME_WITH_TIMEZONE);
pstmt.executeUpdate();
// valid: fully qualified type to setObject()
pstmt.setObject(1, null, Types.TIME_WITH_TIMEZONE);
pstmt.executeUpdate();
pstmt.close();
}
@Test
public void testLocalTimeMax() throws SQLException {
PreparedStatement pstmt = con.prepareStatement("INSERT INTO timetable (tt) VALUES (?)");
pstmt.setObject(1, LocalTime.MAX);
pstmt.executeUpdate();
pstmt.setObject(1, LocalTime.MIN);
pstmt.executeUpdate();
ResultSet rs = con.createStatement().executeQuery("select tt from timetable order by id asc");
Assert.assertTrue(rs.next());
LocalTime localTime = (LocalTime)rs.getObject(1,LocalTime.class);
Assert.assertEquals( LocalTime.MAX, localTime);
Assert.assertTrue(rs.next());
localTime = (LocalTime)rs.getObject(1, LocalTime.class);
Assert.assertEquals( LocalTime.MIN, localTime);
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) 2017, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.postgresql.core.ServerVersion;
import org.junit.After;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
@RunWith(Parameterized.class)
public class SetObject310InfinityTests extends BaseTest4 {
public SetObject310InfinityTests(BinaryMode binaryMode) {
setBinaryMode(binaryMode);
}
@Parameterized.Parameters(name = "binary = {0}")
public static Iterable<Object[]> data() {
Collection<Object[]> ids = new ArrayList<>(2);
for (BinaryMode binaryMode : BinaryMode.values()) {
ids.add(new Object[]{binaryMode});
}
return ids;
}
@Override
public void setUp() throws Exception {
super.setUp();
Assume.assumeTrue("PostgreSQL 8.3 does not support 'infinity' for 'date'",
TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4));
super.setUp();
TestUtil.createTable(con, "table1", "timestamp_without_time_zone_column timestamp without time zone,"
+ "timestamp_with_time_zone_column timestamp with time zone,"
+ "date_column date"
);
}
@After
public void tearDown() throws SQLException {
TestUtil.dropTable(con, "table1");
super.tearDown();
}
@Test
public void testTimestamptz() throws SQLException {
runTestforType(OffsetDateTime.MAX, OffsetDateTime.MIN, "timestamp_without_time_zone_column", null);
}
@Test
public void testTimestamp() throws SQLException {
runTestforType(LocalDateTime.MAX, LocalDateTime.MIN, "timestamp_without_time_zone_column", null);
}
@Test
public void testDate() throws SQLException {
runTestforType(LocalDate.MAX, LocalDate.MIN, "date_column", null);
}
private void runTestforType(Object max, Object min, String columnName, Integer type) throws SQLException {
insert(max, columnName, type);
String readback = readString(columnName);
assertEquals("infinity", readback);
delete();
insert(min, columnName, type);
readback = readString(columnName);
assertEquals("-infinity", readback);
delete();
}
private void insert(Object data, String columnName, Integer type) throws SQLException {
PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
try {
if (type != null) {
ps.setObject(1, data, type);
} else {
ps.setObject(1, data);
}
assertEquals(1, ps.executeUpdate());
} finally {
ps.close();
}
}
private String readString(String columnName) throws SQLException {
Statement st = con.createStatement();
try {
ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName));
try {
assertNotNull(rs);
assertTrue(rs.next());
return rs.getString(1);
} finally {
rs.close();
}
} finally {
st.close();
}
}
private void delete() throws SQLException {
Statement st = con.createStatement();
try {
st.execute("DELETE FROM table1");
} finally {
st.close();
}
}
}

View File

@ -0,0 +1,437 @@
/*
* Copyright (c) 2004, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Types;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.chrono.IsoChronology;
import java.time.chrono.IsoEra;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.ResolverStyle;
import java.time.format.SignStyle;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
@RunWith(Parameterized.class)
public class SetObject310Test extends BaseTest4 {
private static final TimeZone saveTZ = TimeZone.getDefault();
public static final DateTimeFormatter LOCAL_TIME_FORMATTER =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue(ChronoField.YEAR_OF_ERA, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalStart()
.appendOffset("+HH:mm", "+00")
.optionalEnd()
.optionalStart()
.appendLiteral(' ')
.appendPattern("GG")
.toFormatter(Locale.ROOT)
.withResolverStyle(ResolverStyle.LENIENT)
.withChronology(IsoChronology.INSTANCE);
public SetObject310Test(BinaryMode binaryMode) {
setBinaryMode(binaryMode);
}
@Parameterized.Parameters(name = "binary = {0}")
public static Iterable<Object[]> data() {
Collection<Object[]> ids = new ArrayList<Object[]>();
for (BinaryMode binaryMode : BinaryMode.values()) {
ids.add(new Object[]{binaryMode});
}
return ids;
}
@Before
public void setUp() throws Exception {
super.setUp();
TestUtil.createTable(con, "table1", "timestamp_without_time_zone_column timestamp without time zone,"
+ "timestamp_with_time_zone_column timestamp with time zone,"
+ "date_column date,"
+ "time_without_time_zone_column time without time zone,"
+ "time_with_time_zone_column time with time zone"
);
}
@After
public void tearDown() throws SQLException {
TimeZone.setDefault(saveTZ);
TestUtil.dropTable(con, "table1");
super.tearDown();
}
private void insert(Object data, String columnName, Integer type) throws SQLException {
PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
try {
if (type != null) {
ps.setObject(1, data, type);
} else {
ps.setObject(1, data);
}
assertEquals(1, ps.executeUpdate());
} finally {
ps.close();
}
}
private String readString(String columnName) throws SQLException {
Statement st = con.createStatement();
try {
ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName));
try {
assertNotNull(rs);
assertTrue(rs.next());
return rs.getString(1);
} finally {
rs.close();
}
} finally {
st.close();
}
}
private String insertThenReadStringWithoutType(LocalDateTime data, String columnName) throws SQLException {
insert(data, columnName, null);
return readString(columnName);
}
private String insertThenReadStringWithType(LocalDateTime data, String columnName) throws SQLException {
insert(data, columnName, Types.TIMESTAMP);
return readString(columnName);
}
private void insertWithoutType(Object data, String columnName) throws SQLException {
insert(data, columnName, null);
}
private <T> T insertThenReadWithoutType(Object data, String columnName, Class<T> expectedType) throws SQLException {
PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
try {
ps.setObject(1, data);
assertEquals(1, ps.executeUpdate());
} finally {
ps.close();
}
Statement st = con.createStatement();
try {
ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName));
try {
assertNotNull(rs);
assertTrue(rs.next());
return expectedType.cast(rs.getObject(1));
} finally {
rs.close();
}
} finally {
st.close();
}
}
private <T> T insertThenReadWithType(Object data, int sqlType, String columnName, Class<T> expectedType) throws SQLException {
PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
try {
ps.setObject(1, data, sqlType);
assertEquals(1, ps.executeUpdate());
} finally {
ps.close();
}
Statement st = con.createStatement();
try {
ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName));
try {
assertNotNull(rs);
assertTrue(rs.next());
return expectedType.cast(rs.getObject(1));
} finally {
rs.close();
}
} finally {
st.close();
}
}
private void deleteRows() throws SQLException {
Statement st = con.createStatement();
try {
st.executeUpdate("DELETE FROM table1");
} finally {
st.close();
}
}
/**
* Test the behavior of setObject for timestamp columns.
*/
@Test
public void testSetLocalDateTime() throws SQLException {
List<String> zoneIdsToTest = getZoneIdsToTest();
List<String> datesToTest = getDatesToTest();
for (String zoneId : zoneIdsToTest) {
ZoneId zone = ZoneId.of(zoneId);
for (String date : datesToTest) {
LocalDateTime localDateTime = LocalDateTime.parse(date);
String expected = localDateTime.atZone(zone)
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.replace('T', ' ');
localTimestamps(zone, localDateTime, expected);
}
}
}
/**
* Test the behavior of setObject for timestamp columns.
*/
@Test
public void testSetOffsetDateTime() throws SQLException {
List<String> zoneIdsToTest = getZoneIdsToTest();
List<TimeZone> storeZones = new ArrayList<TimeZone>();
for (String zoneId : zoneIdsToTest) {
storeZones.add(TimeZone.getTimeZone(zoneId));
}
List<String> datesToTest = getDatesToTest();
for (TimeZone timeZone : storeZones) {
ZoneId zoneId = timeZone.toZoneId();
for (String date : datesToTest) {
LocalDateTime localDateTime = LocalDateTime.parse(date);
String expected = date.replace('T', ' ');
offsetTimestamps(zoneId, localDateTime, expected, storeZones);
}
}
}
private List<String> getDatesToTest() {
return Arrays.asList("2015-09-03T12:00:00", "2015-06-30T23:59:58",
"1997-06-30T23:59:59", "1997-07-01T00:00:00", "2012-06-30T23:59:59", "2012-07-01T00:00:00",
"2015-06-30T23:59:59", "2015-07-01T00:00:00", "2005-12-31T23:59:59", "2006-01-01T00:00:00",
"2008-12-31T23:59:59", "2009-01-01T00:00:00", /* "2015-06-30T23:59:60", */ "2015-07-31T00:00:00",
"2015-07-31T00:00:01", "2015-07-31T00:00:00.000001",
// On 2000-03-26 02:00:00 Moscow went to DST, thus local time became 03:00:00
"2000-03-26T01:59:59", "2000-03-26T02:00:00", "2000-03-26T02:00:01", "2000-03-26T02:59:59",
"2000-03-26T03:00:00", "2000-03-26T03:00:01", "2000-03-26T03:59:59", "2000-03-26T04:00:00",
"2000-03-26T04:00:01", "2000-03-26T04:00:00.000001",
// This is a pre-1970 date, so check if it is rounded properly
"1950-07-20T02:00:00",
// Ensure the calendar is proleptic
"1582-09-30T00:00:00", "1582-10-16T00:00:00",
// On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00
"2000-10-29T01:59:59", "2000-10-29T02:00:00", "2000-10-29T02:00:01", "2000-10-29T02:59:59",
"2000-10-29T03:00:00", "2000-10-29T03:00:01", "2000-10-29T03:59:59", "2000-10-29T04:00:00",
"2000-10-29T04:00:01", "2000-10-29T04:00:00.000001");
}
private List<String> getZoneIdsToTest() {
List<String> zoneIdsToTest = new ArrayList<String>();
zoneIdsToTest.add("Africa/Casablanca"); // It is something like GMT+0..GMT+1
zoneIdsToTest.add("America/Adak"); // It is something like GMT-10..GMT-9
zoneIdsToTest.add("Atlantic/Azores"); // It is something like GMT-1..GMT+0
zoneIdsToTest.add("Europe/Moscow"); // It is something like GMT+3..GMT+4 for 2000s
zoneIdsToTest.add("Pacific/Apia"); // It is something like GMT+13..GMT+14
zoneIdsToTest.add("Pacific/Niue"); // It is something like GMT-11..GMT-11
for (int i = -12; i <= 13; i++) {
zoneIdsToTest.add(String.format("GMT%+02d", i));
}
return zoneIdsToTest;
}
private void localTimestamps(ZoneId zoneId, LocalDateTime localDateTime, String expected) throws SQLException {
TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
String readBack = insertThenReadStringWithoutType(localDateTime, "timestamp_without_time_zone_column");
assertEquals(
"LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object)",
expected, readBack);
deleteRows();
readBack = insertThenReadStringWithType(localDateTime, "timestamp_without_time_zone_column");
assertEquals(
"LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object, TIMESTAMP)",
expected, readBack);
deleteRows();
}
private void offsetTimestamps(ZoneId dataZone, LocalDateTime localDateTime, String expected, List<TimeZone> storeZones) throws SQLException {
OffsetDateTime data = localDateTime.atZone(dataZone).toOffsetDateTime();
try (PreparedStatement ps = con.prepareStatement(
"select ?::timestamp with time zone, ?::timestamp with time zone")) {
for (TimeZone storeZone : storeZones) {
TimeZone.setDefault(storeZone);
ps.setObject(1, data);
ps.setObject(2, data, Types.TIMESTAMP_WITH_TIMEZONE);
try (ResultSet rs = ps.executeQuery()) {
rs.next();
String noType = rs.getString(1);
String noTypeAppend = ifAddAppendString(noType);
OffsetDateTime noTypeRes = OffsetDateTime.parse(noType.replace(' ', 'T') + noTypeAppend);
assertEquals(
"OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone.default="
+ storeZone + ", setObject(int, Object)", data.toInstant(),
noTypeRes.toInstant());
String withType = rs.getString(2);
String withTypeAppend = ifAddAppendString(withType);
OffsetDateTime withTypeRes = OffsetDateTime.parse(withType.replace(' ', 'T') + withTypeAppend);
assertEquals(
"OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone.default="
+ storeZone + ", setObject(int, Object, TIMESTAMP_WITH_TIMEZONE)",
data.toInstant(), withTypeRes.toInstant());
}
}
}
}
private String ifAddAppendString(String offsetDateTimeString) {
String append = "";
if (offsetDateTimeString.length() - offsetDateTimeString.indexOf("+") <= 5) {
append = ":00";
}
return append;
}
@Test
public void testLocalDateTimeRounding() throws SQLException {
LocalDateTime dateTime = LocalDateTime.parse("2018-12-31T23:59:59.999999500");
localTimestamps(ZoneOffset.UTC, dateTime, "2019-01-01 00:00:00");
}
@Test
public void testTimeStampRounding() throws SQLException {
// TODO: fix for binary
assumeBinaryModeRegular();
LocalTime time = LocalTime.parse("23:59:59.999999500");
Time actual = insertThenReadWithoutType(time, "time_without_time_zone_column", Time.class);
assertEquals(Time.valueOf("24:00:00"), actual);
}
@Test
public void testTimeStampRoundingWithType() throws SQLException {
// TODO: fix for binary
assumeBinaryModeRegular();
LocalTime time = LocalTime.parse("23:59:59.999999500");
Time actual =
insertThenReadWithType(time, Types.TIME, "time_without_time_zone_column", Time.class);
assertEquals(Time.valueOf("24:00:00"), actual);
}
/**
* Test the behavior of setObject for timestamp columns.
*/
@Test
public void testSetLocalDateTimeBc() throws SQLException {
assumeTrue(TestUtil.haveIntegerDateTimes(con));
// use BC for funsies
List<LocalDateTime> bcDates = new ArrayList<LocalDateTime>();
bcDates.add(LocalDateTime.parse("1997-06-30T23:59:59.999999").with(ChronoField.ERA, IsoEra.BCE.getValue()));
bcDates.add(LocalDateTime.parse("0997-06-30T23:59:59.999999").with(ChronoField.ERA, IsoEra.BCE.getValue()));
for (LocalDateTime bcDate : bcDates) {
String expected = LOCAL_TIME_FORMATTER.format(bcDate);
localTimestamps(ZoneOffset.UTC, bcDate, expected);
}
}
/**
* Test the behavior setObject for date columns.
*/
@Test
public void testSetLocalDateWithType() throws SQLException {
LocalDate data = LocalDate.parse("1971-12-15");
java.sql.Date actual = insertThenReadWithType(data, Types.DATE, "date_column", java.sql.Date.class);
java.sql.Date expected = java.sql.Date.valueOf("1971-12-15");
assertEquals(expected, actual);
}
/**
* Test the behavior setObject for date columns.
*/
@Test
public void testSetLocalDateWithoutType() throws SQLException {
LocalDate data = LocalDate.parse("1971-12-15");
java.sql.Date actual = insertThenReadWithoutType(data, "date_column", java.sql.Date.class);
java.sql.Date expected = java.sql.Date.valueOf("1971-12-15");
assertEquals(expected, actual);
}
/**
* Test the behavior setObject for time columns.
*/
@Test
public void testSetLocalTimeAndReadBack() throws SQLException {
// TODO: fix for binary mode.
// Avoid micros truncation in org.postgresql.jdbc.PgResultSet#internalGetObject
assumeBinaryModeRegular();
LocalTime data = LocalTime.parse("16:21:51.123456");
insertWithoutType(data, "time_without_time_zone_column");
String readBack = readString("time_without_time_zone_column");
assertEquals("16:21:51.123456", readBack);
}
/**
* Test the behavior setObject for time columns.
*/
@Test
public void testSetLocalTimeWithType() throws SQLException {
LocalTime data = LocalTime.parse("16:21:51");
Time actual = insertThenReadWithType(data, Types.TIME, "time_without_time_zone_column", Time.class);
Time expected = Time.valueOf("16:21:51");
assertEquals(expected, actual);
}
/**
* Test the behavior setObject for time columns.
*/
@Test
public void testSetLocalTimeWithoutType() throws SQLException {
LocalTime data = LocalTime.parse("16:21:51");
Time actual = insertThenReadWithoutType(data, "time_without_time_zone_column", Time.class);
Time expected = Time.valueOf("16:21:51");
assertEquals(expected, actual);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2019, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.localtimedate;
import static org.junit.Assert.assertEquals;
import org.postgresql.core.Provider;
import org.postgresql.jdbc.TimestampUtils;
import org.junit.Test;
import java.sql.SQLException;
import java.time.LocalTime;
import java.util.TimeZone;
public class TimestampUtilsTest {
@Test
public void testToStringOfLocalTime() {
TimestampUtils timestampUtils = createTimestampUtils();
assertEquals("00:00:00", timestampUtils.toString(LocalTime.parse("00:00:00")));
assertEquals("00:00:00.1", timestampUtils.toString(LocalTime.parse("00:00:00.1")));
assertEquals("00:00:00.12", timestampUtils.toString(LocalTime.parse("00:00:00.12")));
assertEquals("00:00:00.123", timestampUtils.toString(LocalTime.parse("00:00:00.123")));
assertEquals("00:00:00.1234", timestampUtils.toString(LocalTime.parse("00:00:00.1234")));
assertEquals("00:00:00.12345", timestampUtils.toString(LocalTime.parse("00:00:00.12345")));
assertEquals("00:00:00.123456", timestampUtils.toString(LocalTime.parse("00:00:00.123456")));
assertEquals("00:00:00.999999", timestampUtils.toString(LocalTime.parse("00:00:00.999999")));
assertEquals("00:00:00.999999", timestampUtils.toString(LocalTime.parse("00:00:00.999999499"))); // 499 NanoSeconds
assertEquals("00:00:01", timestampUtils.toString(LocalTime.parse("00:00:00.999999500"))); // 500 NanoSeconds
assertEquals("23:59:59", timestampUtils.toString(LocalTime.parse("23:59:59")));
assertEquals("23:59:59.999999", timestampUtils.toString(LocalTime.parse("23:59:59.999999"))); // 0 NanoSeconds
assertEquals("23:59:59.999999", timestampUtils.toString(LocalTime.parse("23:59:59.999999499"))); // 499 NanoSeconds
assertEquals("24:00:00", timestampUtils.toString(LocalTime.parse("23:59:59.999999500")));// 500 NanoSeconds
assertEquals("24:00:00", timestampUtils.toString(LocalTime.parse("23:59:59.999999999")));// 999 NanoSeconds
}
@Test
public void testToLocalTime() throws SQLException {
TimestampUtils timestampUtils = createTimestampUtils();
assertEquals(LocalTime.parse("00:00:00"), timestampUtils.toLocalTime("00:00:00"));
assertEquals(LocalTime.parse("00:00:00.1"), timestampUtils.toLocalTime("00:00:00.1"));
assertEquals(LocalTime.parse("00:00:00.12"), timestampUtils.toLocalTime("00:00:00.12"));
assertEquals(LocalTime.parse("00:00:00.123"), timestampUtils.toLocalTime("00:00:00.123"));
assertEquals(LocalTime.parse("00:00:00.1234"), timestampUtils.toLocalTime("00:00:00.1234"));
assertEquals(LocalTime.parse("00:00:00.12345"), timestampUtils.toLocalTime("00:00:00.12345"));
assertEquals(LocalTime.parse("00:00:00.123456"), timestampUtils.toLocalTime("00:00:00.123456"));
assertEquals(LocalTime.parse("00:00:00.999999"), timestampUtils.toLocalTime("00:00:00.999999"));
assertEquals(LocalTime.parse("23:59:59"), timestampUtils.toLocalTime("23:59:59"));
assertEquals(LocalTime.parse("23:59:59.999999"), timestampUtils.toLocalTime("23:59:59.999999")); // 0 NanoSeconds
assertEquals(LocalTime.parse("23:59:59.9999999"), timestampUtils.toLocalTime("23:59:59.9999999")); // 900 NanoSeconds
assertEquals(LocalTime.parse("23:59:59.99999999"), timestampUtils.toLocalTime("23:59:59.99999999")); // 990 NanoSeconds
assertEquals(LocalTime.parse("23:59:59.999999998"), timestampUtils.toLocalTime("23:59:59.999999998")); // 998 NanoSeconds
assertEquals(LocalTime.parse("23:59:59.999999999"), timestampUtils.toLocalTime("24:00:00"));
}
private TimestampUtils createTimestampUtils() {
return new TimestampUtils(true, (Provider<TimeZone>) TimeZone::getDefault);
}
}

9
prepare_demo.sh Normal file
View File

@ -0,0 +1,9 @@
cd shade
mvn clean install -Dmaven.test.skip=true
rm -rf temp
mkdir temp
cp target/demo-0.0.1-SNAPSHOT.jar ./temp/
cd temp
jar -xf demo-0.0.1-SNAPSHOT.jar
find ./com -name "*" | sort | xargs zip demo-0.0.1-SNAPSHOT_new.jar
mvn install:install-file -Dfile=./demo-0.0.1-SNAPSHOT_new.jar -DgroupId=com.huawei -DartifactId=demo-0.0.1-SNAPSHOT -Dversion=0.0.1 -Dpackaging=jar

18
prepare_maven.sh Normal file
View File

@ -0,0 +1,18 @@
echo begin run
mkdir libs
for src in `find open_source -name '*.jar'`
do
cp $src ./libs/
done
mvn install:install-file -Dfile=./libs/commons-logging-1.2.jar -DgroupId=commons-logging -DartifactId=commons-logging -Dversion=1.2 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/commons-codec-1.11.jar -DgroupId=commons-codec -DartifactId=commons-codec -Dversion=1.11 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/httpclient-4.5.13.jar -DgroupId=org.apache.httpcomponents -DartifactId=httpclient -Dversion=4.5.13 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/httpcore-4.4.13.jar -DgroupId=org.apache.httpcomponents -DartifactId=httpcore -Dversion=4.4.13 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/fastjson-1.2.70.jar -DgroupId=com.alibaba -DartifactId=fastjson -Dversion=1.2.70 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/joda-time-2.10.6.jar -DgroupId=joda-time -DartifactId=joda-time -Dversion=2.10.6 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/jackson-databind-2.11.2.jar -DgroupId=com.fasterxml.jackson.core -DartifactId=jackson-databind -Dversion=2.11.2 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/jackson-core-2.11.2.jar -DgroupId=com.fasterxml.jackson.core -DartifactId=jackson-core -Dversion=2.11.2 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/jackson-annotations-2.11.2.jar -DgroupId=com.fasterxml.jackson.core -DartifactId=jackson-annotations -Dversion=2.11.2 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/slf4j-api-1.7.30.jar -DgroupId=org.slf4j -DartifactId=slf4j-api -Dversion=1.7.30 -Dpackaging=jar
mvn install:install-file -Dfile=./libs/java-sdk-core-3.0.12.jar -DgroupId=com.huawei.apigateway -DartifactId=hw-java-sdk-core -Dversion=3.0.12 -Dpackaging=jar