@@ -20,6 +20,7 @@
import java.sql.Time ;
import java.sql.Timestamp ;
// #if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
import java.time.Duration ;
import java.time.LocalDate ;
import java.time.LocalDateTime ;
import java.time.LocalTime ;
@@ -48,6 +49,15 @@
private static final char [] ZEROS = {' 0' , ' 0' , ' 0' , ' 0' , ' 0' , ' 0' , ' 0' , ' 0' , ' 0' };
private static final char [][] NUMBERS ;
private static final HashMap<String , TimeZone > GMT_ZONES = new HashMap<String , TimeZone > ();
private static final int MAX_NANOS_BEFORE_WRAP_ON_ROUND = 999999500 ;
// #if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
private static final Duration ONE_MICROSECOND = 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 LocalTime MAX_TIME = LocalTime . MAX. minus(Duration . ofMillis(500 ));
private static final OffsetDateTime MAX_OFFSET_DATETIME = OffsetDateTime . MAX. minus(Duration . ofMillis(500 ));
private static final LocalDateTime MAX_LOCAL_DATETIME = LocalDateTime . MAX. minus(Duration . ofMillis(500 ));
// #endif
private static final Field DEFAULT_TIME_ZONE_FIELD ;
@@ -538,6 +548,16 @@ public Calendar getSharedCalendar(TimeZone timeZone) {
return tmp;
}
/**
* Returns true when microsecond part of the time should be increased
* when rounding to microseconds
* @param nanos nanosecond part of the time
* @return true when microsecond part of the time should be increased when rounding to microseconds
*/
private static boolean nanosExceed499 (int nanos ) {
return nanos % 1000 > 499 ;
}
public synchronized String toString (Calendar cal , Timestamp x ) {
return toString(cal, x, true );
}
@@ -551,13 +571,26 @@ public synchronized String toString(Calendar cal, Timestamp x,
}
cal = setupCalendar(cal);
cal. setTime(x);
long timeMillis = x. getTime();
// Round to microseconds
int nanos = x. getNanos();
if (nanos >= MAX_NANOS_BEFORE_WRAP_ON_ROUND ) {
nanos = 0 ;
timeMillis++ ;
} else if (nanosExceed499(nanos)) {
// PostgreSQL does not support nanosecond resolution yet, and appendTime will just ignore
// 0..999 part of the nanoseconds, however we subtract nanos % 1000 to make the value
// a little bit saner for debugging reasons
nanos += 1000 - nanos % 1000 ;
}
cal. setTimeInMillis(timeMillis);
sbuf. setLength(0 );
appendDate(sbuf, cal);
sbuf. append(' ' );
appendTime(sbuf, cal, x . getNanos() );
appendTime(sbuf, cal, nanos );
if (withTimeZone) {
appendTimeZone(sbuf, cal);
}
@@ -645,6 +678,16 @@ private static void appendTime(StringBuilder sb, Calendar cal, int nanos) {
appendTime(sb, hours, minutes, seconds, nanos);
}
/**
* Appends time part to the {@code StringBuilder } in PostgreSQL-compatible format.
* The function truncates {@param nanos} to microseconds. The value is expected to be rounded
* beforehand.
* @param sb destination
* @param hours hours
* @param minutes minutes
* @param seconds seconds
* @param nanos nanoseconds
*/
private static void appendTime (StringBuilder sb , int hours , int minutes , int seconds , int nanos ) {
sb. append(NUMBERS [hours]);
@@ -654,18 +697,17 @@ private static void appendTime(StringBuilder sb, int hours, int minutes, int sec
sb. append(' :' );
sb. append(NUMBERS [seconds]);
// Add microseconds, rounded .
// Add nanoseconds .
// This won't work for server versions < 7.2 which only want
// a two digit fractional second, but we don't need to support 7.1
// anymore and getting the version number here is difficult.
//
int microseconds = (nanos / 1000 ) + (((nanos % 1000 ) + 500 ) / 1000 );
if (microseconds == 0 ) {
if (nanos < 1000 ) {
return ;
}
sb. append(' .' );
int len = sb. length();
sb. append(microseconds);
sb. append(nanos / 1000 ); // append microseconds
int needZeros = 6 - (sb. length() - len);
if (needZeros > 0 ) {
sb. insert(len, ZEROS , 0 , needZeros);
@@ -733,25 +775,37 @@ public synchronized String toString(LocalTime localTime) {
sbuf. setLength(0 );
if (localTime. equals( LocalTime . MAX )) {
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 (OffsetDateTime offsetDateTime ) {
if (OffsetDateTime . MAX . equals(offsetDateTime )) {
if (offsetDateTime . isAfter( MAX_OFFSET_DATETIME )) {
return " infinity" ;
} else if (OffsetDateTime . MIN. equals(offsetDateTime)) {
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 );
}
LocalDateTime localDateTime = offsetDateTime. toLocalDateTime();
LocalDate localDate = localDateTime. toLocalDate();
appendDate(sbuf, localDate);
@@ -768,7 +822,7 @@ public synchronized String toString(OffsetDateTime offsetDateTime) {
* Do not use this method in {@link java.sql.ResultSet#getString(int)}
*/
public synchronized String toString (LocalDateTime localDateTime ) {
if (LocalDateTime . MAX . equals(localDateTime )) {
if (localDateTime . isAfter( MAX_LOCAL_DATETIME )) {
return " infinity" ;
} else if (LocalDateTime . MIN. equals(localDateTime)) {
return " -infinity" ;