[Git][java-team/libpostgresql-jdbc-java][master] 3 commits: New upstream version 42.3.4

Christoph Berg (@myon) gitlab at salsa.debian.org
Mon May 2 15:01:18 BST 2022



Christoph Berg pushed to branch master at Debian Java Maintainers / libpostgresql-jdbc-java


Commits:
98ccfa54 by Christoph Berg at 2022-05-02T15:56:32+02:00
New upstream version 42.3.4
- - - - -
6326e0b4 by Christoph Berg at 2022-05-02T15:56:35+02:00
Update upstream source from tag 'upstream/42.3.4'

Update to upstream version '42.3.4'
with Debian dir 3cfc65a7c93b5b80f338d5df10dccad07bfeed34
- - - - -
27013f2d by Christoph Berg at 2022-05-02T15:57:40+02:00
releasing package libpgjava version 42.3.4-1

- - - - -


19 changed files:

- debian/changelog
- pom.xml
- src/main/java/org/postgresql/Driver.java
- src/main/java/org/postgresql/PGProperty.java
- src/main/java/org/postgresql/ds/common/BaseDataSource.java
- src/main/java/org/postgresql/gss/GssAction.java
- src/main/java/org/postgresql/gss/GssEncAction.java
- src/main/java/org/postgresql/gss/MakeGSS.java
- src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
- src/main/java/org/postgresql/jdbc/PgResultSet.java
- src/main/java/org/postgresql/jdbc/TimestampUtils.java
- src/main/java/org/postgresql/util/DriverInfo.java
- src/main/java/org/postgresql/util/PGInterval.java
- src/main/resources/META-INF/MANIFEST.MF
- src/test/java/org/postgresql/test/jdbc2/DateTest.java
- src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
- src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java
- src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java
- src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+libpgjava (42.3.4-1) unstable; urgency=medium
+
+  * New upstream version 42.3.4.
+
+ -- Christoph Berg <myon at debian.org>  Mon, 02 May 2022 15:56:41 +0200
+
 libpgjava (42.3.3-1) unstable; urgency=medium
 
   * New upstream version 42.3.3.


=====================================
pom.xml
=====================================
@@ -10,7 +10,7 @@
     <artifactId>postgresql</artifactId>
     <packaging>jar</packaging>
     <name>PostgreSQL JDBC Driver - JDBC 4.2</name>
-    <version>42.3.3</version>
+    <version>42.3.4</version>
     <description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
     <url>https://github.com/pgjdbc/pgjdbc</url>
 


=====================================
src/main/java/org/postgresql/Driver.java
=====================================
@@ -240,7 +240,9 @@ public class Driver implements java.sql.Driver {
     }
     // parse URL and add more properties
     if ((props = parseURL(url, props)) == null) {
-      return null;
+      throw new PSQLException(
+          GT.tr("Unable to parse URL "),
+          PSQLState.UNEXPECTED_ERROR);
     }
     try {
 
@@ -654,9 +656,9 @@ public class Driver implements java.sql.Driver {
    * @return the address portion of the URL
    */
   private static HostSpec[] hostSpecs(Properties props) {
-    String[] hosts = castNonNull(props.getProperty("PGHOST")).split(",");
-    String[] ports = castNonNull(props.getProperty("PGPORT")).split(",");
-    String localSocketAddress = props.getProperty("localSocketAddress");
+    String[] hosts = castNonNull(PGProperty.PG_HOST.get(props)).split(",");
+    String[] ports = castNonNull(PGProperty.PG_PORT.get(props)).split(",");
+    String localSocketAddress = PGProperty.LOCAL_SOCKET_ADDRESS.get(props);
     HostSpec[] hostSpecs = new HostSpec[hosts.length];
     for (int i = 0; i < hostSpecs.length; ++i) {
       hostSpecs[i] = new HostSpec(hosts[i], Integer.parseInt(ports[i]), localSocketAddress);


=====================================
src/main/java/org/postgresql/PGProperty.java
=====================================
@@ -292,6 +292,15 @@ public enum PGProperty {
       "false",
       "If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates"),
 
+  /**
+   * <p>If this is set then the client side will bind to this address. This is useful if you need
+   * to choose which interface to connect to.</p>
+   */
+  LOCAL_SOCKET_ADDRESS(
+      "localSocketAddress",
+      null,
+      "Local Socket address, if set bind the client side of the socket to this address"),
+
   /**
    * This property is no longer used by the driver and will be ignored.
    * Logging is configured via java.util.logging.
@@ -742,7 +751,6 @@ public enum PGProperty {
   private final boolean required;
   private final String description;
   private final String /* @Nullable */ [] choices;
-  private final boolean deprecated;
 
   PGProperty(String name, /* @Nullable */ String defaultValue, String description) {
     this(name, defaultValue, description, false);
@@ -759,11 +767,6 @@ public enum PGProperty {
     this.required = required;
     this.description = description;
     this.choices = choices;
-    try {
-      this.deprecated = PGProperty.class.getField(name()).getAnnotation(Deprecated.class) != null;
-    } catch (NoSuchFieldException e) {
-      throw new RuntimeException(e);
-    }
   }
 
   private static final Map<String, PGProperty> PROPS_BY_NAME = new HashMap<String, PGProperty>();
@@ -822,15 +825,6 @@ public enum PGProperty {
     return choices;
   }
 
-  /**
-   * Returns whether this connection parameter is deprecated.
-   *
-   * @return whether this connection parameter is deprecated
-   */
-  public boolean isDeprecated() {
-    return deprecated;
-  }
-
   /**
    * Returns the value of the connection parameters according to the given {@code Properties} or the
    * default value.


=====================================
src/main/java/org/postgresql/ds/common/BaseDataSource.java
=====================================
@@ -1207,6 +1207,22 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     return PGProperty.REPLICATION.get(properties);
   }
 
+  /**
+   * @return the localSocketAddress
+   * @see PGProperty#LOCAL_SOCKET_ADDRESS
+   */
+  public /* @Nullable */ String getLocalSocketAddress() {
+    return PGProperty.LOCAL_SOCKET_ADDRESS.get(properties);
+  }
+
+  /**
+   * @param localSocketAddress local address to bind client side to
+   * @see PGProperty#LOCAL_SOCKET_ADDRESS
+   */
+  public void setLocalSocketAddress( String localSocketAddress ) {
+    PGProperty.LOCAL_SOCKET_ADDRESS.set(properties,localSocketAddress);
+  }
+
   /**
    * This property is no longer used by the driver and will be ignored.
    * @return loggerLevel in properties


=====================================
src/main/java/org/postgresql/gss/GssAction.java
=====================================
@@ -35,15 +35,17 @@ class GssAction implements PrivilegedAction</* @Nullable */ Exception> {
   private final PGStream pgStream;
   private final String host;
   private final String kerberosServerName;
+  private final String user;
   private final boolean useSpnego;
   private final Subject subject;
   private final boolean logServerErrorDetail;
 
-  GssAction(PGStream pgStream, Subject subject, String host,
+  GssAction(PGStream pgStream, Subject subject, String host, String user,
       String kerberosServerName, boolean useSpnego, boolean logServerErrorDetail) {
     this.pgStream = pgStream;
     this.subject = subject;
     this.host = host;
+    this.user = user;
     this.kerberosServerName = kerberosServerName;
     this.useSpnego = useSpnego;
     this.logServerErrorDetail = logServerErrorDetail;
@@ -68,25 +70,44 @@ class GssAction implements PrivilegedAction</* @Nullable */ Exception> {
       GSSManager manager = GSSManager.getInstance();
       GSSCredential clientCreds = null;
       Oid[] desiredMechs = new Oid[1];
-      if (useSpnego && hasSpnegoSupport(manager)) {
-        desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
-      } else {
-        desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+
+      //Try to get credential from subject first.
+      GSSCredential gssCredential = null;
+      if (subject != null) {
+        Set<GSSCredential> gssCreds = subject.getPrivateCredentials(GSSCredential.class);
+        if (gssCreds != null && !gssCreds.isEmpty()) {
+          gssCredential = gssCreds.iterator().next();
+        }
       }
-      Set<Principal> principals = subject.getPrincipals();
-      Iterator<Principal> principalIterator = principals.iterator();
 
-      Principal principal = null;
-      if (principalIterator.hasNext()) {
-        principal = principalIterator.next();
+      //If failed to get credential from subject,
+      //then call createCredential to create one.
+      if (gssCredential == null) {
+        if (useSpnego && hasSpnegoSupport(manager)) {
+          desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
+        } else {
+          desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+        }
+        String principalName = this.user;
+        if (subject != null) {
+          Set<Principal> principals = subject.getPrincipals();
+          Iterator<Principal> principalIterator = principals.iterator();
+
+          Principal principal = null;
+          if (principalIterator.hasNext()) {
+            principal = principalIterator.next();
+            principalName = principal.getName();
+          }
+        }
+
+        GSSName clientName = manager.createName(principalName, GSSName.NT_USER_NAME);
+        clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
+            GSSCredential.INITIATE_ONLY);
       } else {
-        return new Exception("No principal found, unexpected error please report");
+        desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+        clientCreds = gssCredential;
       }
 
-      GSSName clientName = manager.createName(principal.getName(), GSSName.NT_USER_NAME);
-      clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
-          GSSCredential.INITIATE_ONLY);
-
       GSSName serverName =
           manager.createName(kerberosServerName + "@" + host, GSSName.NT_HOSTBASED_SERVICE);
 


=====================================
src/main/java/org/postgresql/gss/GssEncAction.java
=====================================
@@ -69,24 +69,43 @@ public class GssEncAction implements PrivilegedAction</* @Nullable */ Exception>
       GSSManager manager = GSSManager.getInstance();
       GSSCredential clientCreds = null;
       Oid[] desiredMechs = new Oid[1];
-      if (useSpnego && hasSpnegoSupport(manager)) {
-        desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
-      } else {
-        desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
-      }
-      Set<Principal> principals = subject.getPrincipals();
-      Iterator<Principal> principalIterator = principals.iterator();
-      Principal principal = null;
-      if (principalIterator.hasNext()) {
-        principal = principalIterator.next();
-      } else {
-        return new Exception("No principal found, unexpected error please report");
+
+      //Try to get credential from subject first.
+      GSSCredential gssCredential = null;
+      if (subject != null) {
+        Set<GSSCredential> gssCreds = subject.getPrivateCredentials(GSSCredential.class);
+        if (gssCreds != null && !gssCreds.isEmpty()) {
+          gssCredential = gssCreds.iterator().next();
+        }
       }
 
-      GSSName clientName = manager.createName(principal.getName(), GSSName.NT_USER_NAME);
-      clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
-          GSSCredential.INITIATE_ONLY);
+      //If failed to get credential from subject,
+      //then call createCredential to create one.
+      if (gssCredential == null) {
+        if (useSpnego && hasSpnegoSupport(manager)) {
+          desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
+        } else {
+          desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+        }
+        String principalName = this.user;
+        if (subject != null) {
+          Set<Principal> principals = subject.getPrincipals();
+          Iterator<Principal> principalIterator = principals.iterator();
+
+          Principal principal = null;
+          if (principalIterator.hasNext()) {
+            principal = principalIterator.next();
+            principalName = principal.getName();
+          }
+        }
 
+        GSSName clientName = manager.createName(principalName, GSSName.NT_USER_NAME);
+        clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
+            GSSCredential.INITIATE_ONLY);
+      } else {
+        desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+        clientCreds = gssCredential;
+      }
       GSSName serverName =
           manager.createName(kerberosServerName + "@" + host, GSSName.NT_HOSTBASED_SERVICE);
 


=====================================
src/main/java/org/postgresql/gss/MakeGSS.java
=====================================
@@ -14,9 +14,12 @@ import org.postgresql.util.PSQLException;
 import org.postgresql.util.PSQLState;
 
 // import org.checkerframework.checker.nullness.qual.Nullable;
+import org.ietf.jgss.GSSCredential;
 
 import java.io.IOException;
+import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -45,16 +48,27 @@ public class MakeGSS {
     try {
       boolean performAuthentication = jaasLogin;
 
-      LoginContext lc = new LoginContext(castNonNull(jaasApplicationName), new GSSCallbackHandler(user, password));
-      lc.login();
-      Subject sub = lc.getSubject();
+      //Check if we can get credential from subject to avoid login.
+      Subject sub = Subject.getSubject(AccessController.getContext());
+      if (sub != null) {
+        Set<GSSCredential> gssCreds = sub.getPrivateCredentials(GSSCredential.class);
+        if (gssCreds != null && !gssCreds.isEmpty()) {
+          performAuthentication = false;
+        }
+      }
+      if (performAuthentication) {
+        LoginContext lc = new LoginContext(castNonNull(jaasApplicationName), new GSSCallbackHandler(user, password));
+        lc.login();
+        sub = lc.getSubject();
+      }
+
       if ( encrypted ) {
         PrivilegedAction</* @Nullable */ Exception> action = new GssEncAction(pgStream, sub, host, user,
             kerberosServerName, useSpnego, logServerErrorDetail);
 
         result = Subject.doAs(sub, action);
       } else {
-        PrivilegedAction</* @Nullable */ Exception> action = new GssAction(pgStream, sub, host,
+        PrivilegedAction</* @Nullable */ Exception> action = new GssAction(pgStream, sub, host, user,
             kerberosServerName, useSpnego, logServerErrorDetail);
 
         result = Subject.doAs(sub, action);


=====================================
src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
=====================================
@@ -69,6 +69,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
+import java.time.OffsetTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Map;
@@ -614,6 +615,9 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
           } else if (in instanceof LocalTime) {
             setTime(parameterIndex, (LocalTime) in);
             break;
+          } else if (in instanceof OffsetTime) {
+            setTime(parameterIndex, (OffsetTime) in);
+            break;
           } else {
             tmpt = getTimestampUtils().toTime(getDefaultCalendar(), in.toString());
           }
@@ -988,6 +992,8 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
       setDate(parameterIndex, (LocalDate) x);
     } else if (x instanceof LocalTime) {
       setTime(parameterIndex, (LocalTime) x);
+    } else if (x instanceof OffsetTime) {
+      setTime(parameterIndex, (OffsetTime) x);
     } else if (x instanceof LocalDateTime) {
       setTimestamp(parameterIndex, (LocalDateTime) x);
     } else if (x instanceof OffsetDateTime) {
@@ -1439,6 +1445,11 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
     bindString(i, getTimestampUtils().toString(localTime), oid);
   }
 
+  private void setTime(/* @Positive */ int i, OffsetTime offsetTime) throws SQLException {
+    int oid = Oid.TIMETZ;
+    bindString(i, getTimestampUtils().toString(offsetTime), oid);
+  }
+
   private void setTimestamp(/* @Positive */ int i, LocalDateTime localDateTime)
       throws SQLException {
     int oid = Oid.TIMESTAMP;


=====================================
src/main/java/org/postgresql/jdbc/PgResultSet.java
=====================================
@@ -8,7 +8,6 @@ package org.postgresql.jdbc;
 import static org.postgresql.util.internal.Nullness.castNonNull;
 
 import org.postgresql.PGResultSetMetaData;
-import org.postgresql.PGStatement;
 import org.postgresql.core.BaseConnection;
 import org.postgresql.core.BaseStatement;
 import org.postgresql.core.Encoding;
@@ -74,6 +73,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
+import java.time.OffsetTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -586,29 +586,6 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     return getTimestampUtils().toTime(cal, string);
   }
 
-  private /* @Nullable */ 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 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 getTimestampUtils().toLocalTime(string);
-  }
-
   /* @Pure */
   @Override
   public /* @Nullable */ Timestamp getTimestamp(
@@ -672,6 +649,9 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
 
   }
 
+  // TODO: In Java 8 this constant is missing, later versions (at least 11) have LocalDate#EPOCH:
+  private static final LocalDate LOCAL_DATE_EPOCH = LocalDate.of(1970, 1, 1);
+
   private /* @Nullable */ OffsetDateTime getOffsetDateTime(int i) throws SQLException {
     byte[] value = getRawValue(i);
     if (value == null) {
@@ -681,35 +661,48 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     int col = i - 1;
     int oid = fields[col].getOID();
 
+    // TODO: Disallow getting OffsetDateTime from a non-TZ field
     if (isBinary(i)) {
       if (oid == Oid.TIMESTAMPTZ || oid == Oid.TIMESTAMP) {
         return 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 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);
+        return getTimestampUtils().toOffsetTimeBin(value).atDate(LOCAL_DATE_EPOCH);
+      }
+    } else {
+      // string
+      if (oid == Oid.TIMESTAMPTZ || oid == Oid.TIMESTAMP || oid == Oid.TIMETZ) {
+        return getTimestampUtils().toOffsetDateTime(castNonNull(getString(i)), oid != Oid.TIMETZ);
       }
     }
-    // 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 = castNonNull(getString(i));
+
+    throw new PSQLException(
+        GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+            Oid.toString(oid), "java.time.OffsetDateTime"),
+        PSQLState.DATA_TYPE_MISMATCH);
+  }
+
+  private /* @Nullable */ OffsetTime getOffsetTime(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.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 = getTimestampUtils().toTime(cal, string);
-      return getTimestampUtils().toOffsetDateTime(time);
+      if (isBinary(i)) {
+        return getTimestampUtils().toOffsetTimeBin(value);
+      } else {
+        return getTimestampUtils().toOffsetTime(castNonNull(getString(i)));
+      }
     }
-    return getTimestampUtils().toOffsetDateTime(string);
+
+    throw new PSQLException(
+        GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+            Oid.toString(oid), "java.time.OffsetTime"),
+        PSQLState.DATA_TYPE_MISMATCH);
   }
 
   private /* @Nullable */ LocalDateTime getLocalDateTime(int i) throws SQLException {
@@ -720,18 +713,70 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
 
     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 (oid == Oid.TIMESTAMP) {
+      if (isBinary(i)) {
+        return getTimestampUtils().toLocalDateTimeBin(value);
+      } else {
+        return getTimestampUtils().toLocalDateTime(castNonNull(getString(i)));
+      }
     }
+
+    throw new PSQLException(
+        GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+            Oid.toString(oid), "java.time.LocalDateTime"),
+        PSQLState.DATA_TYPE_MISMATCH);
+  }
+
+  private /* @Nullable */ LocalDate getLocalDate(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)) {
-      return getTimestampUtils().toLocalDateTimeBin(value);
+      if (oid == Oid.DATE) {
+        return getTimestampUtils().toLocalDateBin(value);
+      } else if (oid == Oid.TIMESTAMP) {
+        return getTimestampUtils().toLocalDateTimeBin(value).toLocalDate();
+      }
+    } else {
+      // string
+      if (oid == Oid.DATE || oid == Oid.TIMESTAMP) {
+        return getTimestampUtils().toLocalDateTime(castNonNull(getString(i))).toLocalDate();
+      }
     }
 
-    String string = castNonNull(getString(i));
-    return getTimestampUtils().toLocalDateTime(string);
+    throw new PSQLException(
+        GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+            Oid.toString(oid), "java.time.LocalDate"),
+        PSQLState.DATA_TYPE_MISMATCH);
+  }
+
+  private /* @Nullable */ LocalTime getLocalTime(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.TIME) {
+      if (isBinary(i)) {
+        return getTimestampUtils().toLocalTimeBin(value);
+      } else {
+        return getTimestampUtils().toLocalTime(getString(i));
+      }
+    }
+
+    throw new PSQLException(
+        GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+            Oid.toString(oid), "java.time.LocalTime"),
+        PSQLState.DATA_TYPE_MISMATCH);
   }
 
   public java.sql./* @Nullable */ Date getDate(
@@ -3696,51 +3741,15 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
       }
       // JSR-310 support
     } else if (type == 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(LocalDate.MAX);
-        }
-        if (time == PGStatement.DATE_NEGATIVE_INFINITY) {
-          return type.cast(LocalDate.MIN);
-        }
-        return type.cast(dateValue.toLocalDate());
-      } else if (sqlType == Types.TIMESTAMP) {
-        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);
-      }
+      return type.cast(getLocalDate(columnIndex));
     } else if (type == 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);
-      }
+      return type.cast(getLocalTime(columnIndex));
     } else if (type == 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);
-      }
+      return type.cast(getLocalDateTime(columnIndex));
     } else if (type == OffsetDateTime.class) {
-      if (sqlType == Types.TIMESTAMP_WITH_TIMEZONE || sqlType == Types.TIMESTAMP) {
-        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);
-      }
+      return type.cast(getOffsetDateTime(columnIndex));
+    } else if (type == OffsetTime.class) {
+      return type.cast(getOffsetTime(columnIndex));
     } else if (PGobject.class.isAssignableFrom(type)) {
       Object object;
       if (isBinary(columnIndex)) {


=====================================
src/main/java/org/postgresql/jdbc/TimestampUtils.java
=====================================
@@ -30,6 +30,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
+import java.time.OffsetTime;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.chrono.IsoEra;
@@ -38,6 +39,7 @@ import java.time.temporal.ChronoField;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
+import java.util.Objects;
 import java.util.SimpleTimeZone;
 import java.util.TimeZone;
 
@@ -63,6 +65,8 @@ public class TimestampUtils {
   private static final LocalDate MIN_LOCAL_DATE = LocalDate.of(4713, 1, 1).with(ChronoField.ERA, IsoEra.BCE.getValue());
   private static final LocalDateTime MIN_LOCAL_DATETIME = MIN_LOCAL_DATE.atStartOfDay();
   private static final OffsetDateTime MIN_OFFSET_DATETIME = MIN_LOCAL_DATETIME.atOffset(ZoneOffset.UTC);
+  private static final Duration PG_EPOCH_DIFF =
+      Duration.between(Instant.EPOCH, LocalDate.of(2000, 1, 1).atStartOfDay().toInstant(ZoneOffset.UTC));
 
   private static final /* @Nullable */ Field DEFAULT_TIME_ZONE_FIELD;
 
@@ -108,7 +112,6 @@ public class TimestampUtils {
         tzField = TimeZone.class.getDeclaredField("defaultTimeZone");
         tzField.setAccessible(true);
         TimeZone defaultTz = TimeZone.getDefault();
-        @SuppressWarnings("nulllability")
         Object tzFromField = tzField.get(null);
         if (defaultTz == null || !defaultTz.equals(tzFromField)) {
           tzField = null;
@@ -125,10 +128,10 @@ public class TimestampUtils {
   // This calendar is used when user provides calendar in setX(, Calendar) method.
   // It ensures calendar is Gregorian.
   private final Calendar calendarWithUserTz = new GregorianCalendar();
-  private final TimeZone utcTz = TimeZone.getTimeZone("UTC");
+  private final TimeZone utcTz = TimeZone.getTimeZone(ZoneOffset.UTC);
 
   private /* @Nullable */ Calendar calCache;
-  private int calCacheZone;
+  private /* @Nullable */ ZoneOffset calCacheZone;
 
   /**
    * True if the backend uses doubles for time values. False if long is used.
@@ -141,30 +144,18 @@ public class TimestampUtils {
     this.timeZoneProvider = timeZoneProvider;
   }
 
-  private Calendar getCalendar(int sign, int hr, int min, int sec) {
-    int rawOffset = sign * (((hr * 60 + min) * 60 + sec) * 1000);
-    if (calCache != null && calCacheZone == rawOffset) {
+  private Calendar getCalendar(ZoneOffset offset) {
+    if (calCache != null && Objects.equals(offset, calCacheZone)) {
       return calCache;
     }
 
-    StringBuilder zoneID = new StringBuilder("GMT");
-    zoneID.append(sign < 0 ? '-' : '+');
-    if (hr < 10) {
-      zoneID.append('0');
-    }
-    zoneID.append(hr);
-    if (min < 10) {
-      zoneID.append('0');
-    }
-    zoneID.append(min);
-    if (sec < 10) {
-      zoneID.append('0');
-    }
-    zoneID.append(sec);
-
-    TimeZone syntheticTZ = new SimpleTimeZone(rawOffset, zoneID.toString());
+    // normally we would use:
+    // calCache = new GregorianCalendar(TimeZone.getTimeZone(offset));
+    // But this seems to cause issues for some crazy offsets as returned by server for BC dates!
+    final String tzid = (offset.getTotalSeconds() == 0) ? "UTC" : "GMT".concat(offset.getId());
+    final TimeZone syntheticTZ = new SimpleTimeZone(offset.getTotalSeconds() * 1000, tzid);
     calCache = new GregorianCalendar(syntheticTZ);
-    calCacheZone = rawOffset;
+    calCacheZone = offset;
     return calCache;
   }
 
@@ -181,7 +172,8 @@ public class TimestampUtils {
     int second = 0;
     int nanos = 0;
 
-    /* @Nullable */ Calendar tz = null;
+    boolean hasOffset = false;
+    ZoneOffset offset = ZoneOffset.UTC;
   }
 
   private static class ParsedBinaryTimestamp {
@@ -312,6 +304,8 @@ public class TimestampUtils {
       // Possibly read timezone.
       sep = charAt(s, start);
       if (sep == '-' || sep == '+') {
+        result.hasOffset = true;
+
         int tzsign = (sep == '-') ? -1 : 1;
         int tzhr;
         int tzmin;
@@ -338,10 +332,7 @@ public class TimestampUtils {
           start = end;
         }
 
-        // Setting offset does not seem to work correctly in all
-        // cases.. So get a fresh calendar for a synthetic timezone
-        // instead
-        result.tz = getCalendar(tzsign, tzhr, tzmin, tzsec);
+        result.offset = ZoneOffset.ofHoursMinutesSeconds(tzsign * tzhr, tzsign * tzmin, tzsign * tzsec);
 
         start = skipWhitespace(s, start); // Skip trailing whitespace
       }
@@ -368,7 +359,7 @@ public class TimestampUtils {
 
     } catch (NumberFormatException nfe) {
       throw new PSQLException(
-          GT.tr("Bad value for type timestamp/date/time: {1}", str),
+          GT.tr("Bad value for type timestamp/date/time: {0}", str),
           PSQLState.BAD_DATETIME_FORMAT, nfe);
     }
 
@@ -401,7 +392,7 @@ public class TimestampUtils {
     }
 
     ParsedTimestamp ts = parseBackendTimestamp(s);
-    Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
+    Calendar useCal = ts.hasOffset ? getCalendar(ts.offset) : setupCalendar(cal);
     useCal.set(Calendar.ERA, ts.era);
     useCal.set(Calendar.YEAR, ts.year);
     useCal.set(Calendar.MONTH, ts.month - 1);
@@ -436,12 +427,60 @@ public class TimestampUtils {
       return LocalTime.parse(s);
     } catch (DateTimeParseException nfe) {
       throw new PSQLException(
-          GT.tr("Bad value for type timestamp/date/time: {1}", s),
+          GT.tr("Bad value for type timestamp/date/time: {0}", s),
           PSQLState.BAD_DATETIME_FORMAT, nfe);
     }
 
   }
 
+  /**
+   * Returns the offset time object matching the given bytes with Oid#TIMETZ or Oid#TIME.
+   *
+   * @param bytes The binary encoded TIMETZ/TIME value.
+   * @return The parsed offset time object.
+   * @throws PSQLException If binary format could not be parsed.
+   */
+  public OffsetTime toOffsetTimeBin(byte[] bytes) throws PSQLException {
+    if (bytes.length != 12) {
+      throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "time"),
+          PSQLState.BAD_DATETIME_FORMAT);
+    }
+
+    final long micros;
+
+    if (usesDouble) {
+      double seconds = ByteConverter.float8(bytes, 0);
+      micros = (long) (seconds * 1_000_000d);
+    } else {
+      micros = ByteConverter.int8(bytes, 0);
+    }
+
+    // postgres offset is negative, so we have to flip sign:
+    final ZoneOffset timeOffset = ZoneOffset.ofTotalSeconds(-ByteConverter.int4(bytes, 8));
+
+    return OffsetTime.of(LocalTime.ofNanoOfDay(Math.multiplyExact(micros, 1000L)), timeOffset);
+  }
+
+  /**
+   * Parse a string and return a OffsetTime representing its value.
+   *
+   * @param s The ISO formated time string to parse.
+   * @return null if s is null or a OffsetTime of the parsed string s.
+   * @throws SQLException if there is a problem parsing s.
+   */
+  public /* @PolyNull */ OffsetTime toOffsetTime(/* @PolyNull */ String s) throws SQLException {
+    if (s == null) {
+      return null;
+    }
+
+    if (s.startsWith("24:00:00")) {
+      return OffsetTime.MAX;
+    }
+
+    final ParsedTimestamp ts = parseBackendTimestamp(s);
+    return OffsetTime.of(ts.hour, ts.minute, ts.second, ts.nanos, ts.offset);
+  }
+
   /**
    * Parse a string and return a LocalDateTime representing its value.
    *
@@ -478,14 +517,16 @@ public class TimestampUtils {
   }
 
   /**
-   * Parse a string and return a LocalDateTime representing its value.
+   * Parse a string and return a OffsetDateTime 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.
+   * @param adaptToUTC if true the timezone is adapted to be UTC;
+   *     this must be done for timestamp and timestamptz as they have no zone on server side
+   * @return null if s is null or a OffsetDateTime of the parsed string s.
    * @throws SQLException if there is a problem parsing s.
    */
   public /* @PolyNull */ OffsetDateTime toOffsetDateTime(
-      /* @PolyNull */ String s) throws SQLException {
+      /* @PolyNull */ String s, boolean adaptToUTC) throws SQLException {
     if (s == null) {
       return null;
     }
@@ -501,19 +542,12 @@ public class TimestampUtils {
       return 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;
+    final ParsedTimestamp ts = parseBackendTimestamp(s);
+    OffsetDateTime result =
+        OffsetDateTime.of(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.nanos, ts.offset);
+    if (adaptToUTC) {
+      result = result.withOffsetSameInstant(ZoneOffset.UTC);
     }
-    ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(offsetSeconds);
-    // Postgres is always UTC
-    OffsetDateTime result = OffsetDateTime.of(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.nanos, zoneOffset)
-        .withOffsetSameInstant(ZoneOffset.UTC);
     if (ts.era == GregorianCalendar.BC) {
       return result.with(ChronoField.ERA, IsoEra.BCE.getValue());
     } else {
@@ -521,18 +555,6 @@ public class TimestampUtils {
     }
   }
 
-  /**
-   * 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 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(LocalDate.of(1970, 1, 1)).atOffset(ZoneOffset.UTC);
-  }
-
   /**
    * Returns the offset date time object matching the given bytes with Oid#TIMESTAMPTZ.
    *
@@ -561,8 +583,8 @@ public class TimestampUtils {
       return null;
     }
     ParsedTimestamp ts = parseBackendTimestamp(s);
-    Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
-    if (ts.tz == null) {
+    Calendar useCal = ts.hasOffset ? getCalendar(ts.offset) : setupCalendar(cal);
+    if (!ts.hasOffset) {
       // When no time zone provided (e.g. time or timestamp)
       // We get the year-month-day from the string, then truncate the day to 1970-01-01
       // This is used for timestamp -> time conversion
@@ -589,7 +611,7 @@ public class TimestampUtils {
     useCal.set(Calendar.MILLISECOND, 0);
 
     long timeMillis = useCal.getTimeInMillis() + ts.nanos / 1000000;
-    if (ts.tz != null || (ts.year == 1970 && ts.era == GregorianCalendar.AD)) {
+    if (ts.hasOffset || (ts.year == 1970 && ts.era == GregorianCalendar.AD)) {
       // time with time zone has proper time zone, so the value can be returned as is
       return new Time(timeMillis);
     }
@@ -873,6 +895,29 @@ public class TimestampUtils {
     return sbuf.toString();
   }
 
+  public synchronized String toString(OffsetTime offsetTime) {
+
+    sbuf.setLength(0);
+
+    final LocalTime localTime = offsetTime.toLocalTime();
+    if (localTime.isAfter(MAX_TIME)) {
+      sbuf.append("24:00:00");
+      appendTimeZone(sbuf, offsetTime.getOffset());
+      return sbuf.toString();
+    }
+
+    int nano = offsetTime.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
+      offsetTime = offsetTime.plus(ONE_MICROSECOND);
+    }
+    appendTime(sbuf, localTime);
+    appendTimeZone(sbuf, offsetTime.getOffset());
+
+    return sbuf.toString();
+  }
+
   public synchronized String toString(OffsetDateTime offsetDateTime) {
     if (offsetDateTime.isAfter(MAX_OFFSET_DATETIME)) {
       return "infinity";
@@ -1019,7 +1064,6 @@ public class TimestampUtils {
     // Fast path to getting the default timezone.
     if (DEFAULT_TIME_ZONE_FIELD != null) {
       try {
-        @SuppressWarnings("nullability")
         TimeZone defaultTimeZone = (TimeZone) DEFAULT_TIME_ZONE_FIELD.get(null);
         if (defaultTimeZone == prevDefaultZoneFieldValue) {
           return castNonNull(defaultTimeZoneCache);
@@ -1108,7 +1152,7 @@ public class TimestampUtils {
       micros = ByteConverter.int8(bytes, 0);
     }
 
-    return LocalTime.ofNanoOfDay(micros * 1000);
+    return LocalTime.ofNanoOfDay(Math.multiplyExact(micros, 1000L));
   }
 
   /**
@@ -1229,7 +1273,7 @@ public class TimestampUtils {
     long secs = ts.millis / 1000L;
 
     // postgres epoc to java epoc
-    secs += 946684800L;
+    secs += PG_EPOCH_DIFF.getSeconds();
     long millis = secs * 1000L;
 
     ts.millis = millis;
@@ -1258,6 +1302,29 @@ public class TimestampUtils {
     return LocalDateTime.ofEpochSecond(parsedTimestamp.millis / 1000L, parsedTimestamp.nanos, ZoneOffset.UTC);
   }
 
+  /**
+   * Returns the local date time object matching the given bytes with {@link Oid#DATE} or
+   * {@link Oid#TIMESTAMP}.
+   * @param bytes The binary encoded local date value.
+   *
+   * @return The parsed local date object.
+   * @throws PSQLException If binary format could not be parsed.
+   */
+  public LocalDate toLocalDateBin(byte[] bytes) throws PSQLException {
+    if (bytes.length != 4) {
+      throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "date"),
+          PSQLState.BAD_DATETIME_FORMAT);
+    }
+    int days = ByteConverter.int4(bytes, 0);
+    if (days == Integer.MAX_VALUE) {
+      return LocalDate.MAX;
+    } else if (days == Integer.MIN_VALUE) {
+      return LocalDate.MIN;
+    }
+    // adapt from different Postgres Epoch and convert to LocalDate:
+    return LocalDate.ofEpochDay(PG_EPOCH_DIFF.toDays() + days);
+  }
+
   /**
    * <p>Given a UTC timestamp {@code millis} finds another point in time that is rendered in given time
    * zone {@code tz} exactly as "millis in UTC".</p>
@@ -1446,7 +1513,7 @@ public class TimestampUtils {
    */
   private static long toJavaSecs(long secs) {
     // postgres epoc to java epoc
-    secs += 946684800L;
+    secs += PG_EPOCH_DIFF.getSeconds();
 
     // Julian/Gregorian calendar cutoff point
     if (secs < -12219292800L) { // October 4, 1582 -> October 15, 1582
@@ -1470,7 +1537,7 @@ public class TimestampUtils {
    */
   private static long toPgSecs(long secs) {
     // java epoc to postgres epoc
-    secs -= 946684800L;
+    secs -= PG_EPOCH_DIFF.getSeconds();
 
     // Julian/Gregorian calendar cutoff point
     if (secs < -13165977600L) { // October 15, 1582 -> October 4, 1582


=====================================
src/main/java/org/postgresql/util/DriverInfo.java
=====================================
@@ -16,13 +16,13 @@ public final class DriverInfo {
   // Driver name
   public static final String DRIVER_NAME = "PostgreSQL JDBC Driver";
   public static final String DRIVER_SHORT_NAME = "PgJDBC";
-  public static final String DRIVER_VERSION = "42.3.3";
+  public static final String DRIVER_VERSION = "42.3.4";
   public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION;
 
   // Driver version
   public static final int MAJOR_VERSION = 42;
   public static final int MINOR_VERSION = 3;
-  public static final int PATCH_VERSION = 3;
+  public static final int PATCH_VERSION = 4;
 
   // JDBC specification
   public static final String JDBC_VERSION = "4.2";


=====================================
src/main/java/org/postgresql/util/PGInterval.java
=====================================
@@ -98,13 +98,12 @@ public class PGInterval extends PGobject implements Serializable, Cloneable {
       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);
+            setHours(Integer.parseInt(timeValue.substring(i, lookAhead)));
           } else if (timeValue.charAt(lookAhead) == 'M') {
-            setMinutes(number);
+            setMinutes(Integer.parseInt(timeValue.substring(i, lookAhead)));
           } else if (timeValue.charAt(lookAhead) == 'S') {
-            setSeconds(number);
+            setSeconds(Double.parseDouble(timeValue.substring(i, lookAhead)));
           }
           i = lookAhead;
         }


=====================================
src/main/resources/META-INF/MANIFEST.MF
=====================================
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Implementation-Title: PostgreSQL JDBC Driver
 Bundle-License: BSD-2-Clause
 Automatic-Module-Name: org.postgresql.jdbc
-Implementation-Version: 42.3.3
+Implementation-Version: 42.3.4
 Specification-Vendor: Oracle Corporation
 Specification-Title: JDBC
 Implementation-Vendor-Id: org.postgresql


=====================================
src/test/java/org/postgresql/test/jdbc2/DateTest.java
=====================================
@@ -8,37 +8,77 @@ package org.postgresql.test.jdbc2;
 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.postgresql.test.TestUtil;
 
 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.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 /*
  * Some simple tests based on problems reported by users. Hopefully these will help prevent previous
  * problems from re-occurring ;-)
  *
  */
-public class DateTest {
-  private Connection con;
+ at RunWith(Parameterized.class)
+public class DateTest extends BaseTest4 {
+  private static final TimeZone saveTZ = TimeZone.getDefault();
+
+  private final String type;
+  private final String zoneId;
+
+  public DateTest(String type, String zoneId, BinaryMode binaryMode) {
+    this.type = type;
+    this.zoneId = zoneId;
+    TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
+    setBinaryMode(binaryMode);
+  }
+
+  @Parameterized.Parameters(name = "type = {0}, zoneId = {1}, binary = {2}")
+  public static Iterable<Object[]> data() {
+    final List<Object[]> data = new ArrayList<>();
+    for (String type : Arrays.asList("date", "timestamp", "timestamptz")) {
+      Stream<String> tzIds = Stream.of("Africa/Casablanca", "America/New_York", "America/Toronto",
+          "Europe/Berlin", "Europe/Moscow", "Pacific/Apia", "America/Los_Angeles");
+      // some selection of static GMT offsets (not all, as this takes too long):
+      tzIds = Stream.concat(tzIds, IntStream.of(-12, -11, -5, -1, 0, 1, 3, 12, 13)
+          .mapToObj(i -> String.format(Locale.ROOT, "GMT%+02d", i)));
+      for (String tzId : (Iterable<String>) tzIds::iterator) {
+        for (BinaryMode binaryMode : BinaryMode.values()) {
+          data.add(new Object[]{type, tzId, binaryMode});
+        }
+      }
+    }
+    return data;
+  }
 
   @Before
   public void setUp() throws Exception {
-    con = TestUtil.openDB();
-    TestUtil.createTable(con, "testdate", "dt date");
+    super.setUp();
+    TestUtil.createTable(con, "test", "dt ".concat(type));
   }
 
   @After
-  public void tearDown() throws Exception {
-    TestUtil.dropTable(con, "testdate");
-    TestUtil.closeDB(con);
+  public void tearDown() throws SQLException {
+    TimeZone.setDefault(saveTZ);
+    TestUtil.dropTable(con, "test");
+    super.tearDown();
   }
 
   /*
@@ -46,32 +86,37 @@ public class DateTest {
    */
   @Test
   public void testGetDate() throws SQLException {
-    Statement stmt = con.createStatement();
-
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1950-02-07'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1970-06-02'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1999-08-11'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2001-02-13'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1950-04-02'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1970-11-30'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1988-01-01'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2003-07-09'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1934-02-28'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1969-04-03'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1982-08-03'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2012-03-15'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1912-05-01'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1971-12-15'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1984-12-03'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2000-01-01'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'3456-01-01'")));
-    assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'0101-01-01 BC'")));
-
-    /* dateTest() contains all of the tests */
-    dateTest();
-
-    assertEquals(18, stmt.executeUpdate("DELETE FROM " + "testdate"));
-    stmt.close();
+    assumeTrue("TODO: Test fails on some server versions with local time zones (not GMT based)",
+        false == Objects.equals(type, "timestamptz") || zoneId.startsWith("GMT"));
+    try (Statement stmt = con.createStatement()) {
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1950-02-07'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1970-06-02'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1999-08-11'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2001-02-13'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1950-04-02'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1970-11-30'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1988-01-01'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2003-07-09'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1934-02-28'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1969-04-03'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1982-08-03'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2012-03-15'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1912-05-01'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1971-12-15'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1984-12-03'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2000-01-01'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'3456-01-01'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0101-01-01 BC'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-01-01'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-01-01 BC'")));
+      assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-12-31 BC'")));
+
+      /* dateTest() contains all of the tests */
+      dateTest();
+
+      assertEquals(21, stmt.executeUpdate("DELETE FROM test"));
+      stmt.close();
+    }
   }
 
   /*
@@ -79,70 +124,81 @@ public class DateTest {
    */
   @Test
   public void testSetDate() throws SQLException {
-    Statement stmt = con.createStatement();
-    PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testdate", "?"));
+    try (Statement stmt = con.createStatement()) {
+      PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("test", "?"));
+
+      ps.setDate(1, makeDate(1950, 2, 7));
+      assertEquals(1, ps.executeUpdate());
+
+      ps.setDate(1, makeDate(1970, 6, 2));
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setDate(1, makeDate(1950, 2, 7));
-    assertEquals(1, ps.executeUpdate());
+      ps.setDate(1, makeDate(1999, 8, 11));
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setDate(1, makeDate(1970, 6, 2));
-    assertEquals(1, ps.executeUpdate());
+      ps.setDate(1, makeDate(2001, 2, 13));
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setDate(1, makeDate(1999, 8, 11));
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Timestamp.valueOf("1950-04-02 12:00:00"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setDate(1, makeDate(2001, 2, 13));
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Timestamp.valueOf("1970-11-30 3:00:00"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Timestamp.valueOf("1950-04-02 12:00:00"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Timestamp.valueOf("1988-01-01 13:00:00"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Timestamp.valueOf("1970-11-30 3:00:00"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Timestamp.valueOf("2003-07-09 12:00:00"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Timestamp.valueOf("1988-01-01 13:00:00"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, "1934-02-28", java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Timestamp.valueOf("2003-07-09 12:00:00"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, "1969-04-03", java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, "1934-02-28", java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, "1982-08-03", java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, "1969-04-03", java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, "2012-03-15", java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, "1982-08-03", java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Date.valueOf("1912-05-01"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, "2012-03-15", java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Date.valueOf("1971-12-15"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Date.valueOf("1912-05-01"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Date.valueOf("1984-12-03"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Date.valueOf("1971-12-15"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Date.valueOf("2000-01-01"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Date.valueOf("1984-12-03"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, java.sql.Date.valueOf("3456-01-01"), java.sql.Types.DATE);
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Date.valueOf("2000-01-01"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      // We can't use valueOf on BC dates.
+      ps.setObject(1, makeDate(-100, 1, 1));
+      assertEquals(1, ps.executeUpdate());
 
-    ps.setObject(1, java.sql.Date.valueOf("3456-01-01"), java.sql.Types.DATE);
-    assertEquals(1, ps.executeUpdate());
+      ps.setObject(1, makeDate(1, 1, 1));
+      assertEquals(1, ps.executeUpdate());
 
-    // We can't use valueOf on BC dates.
-    ps.setObject(1, makeDate(-100, 1, 1));
-    assertEquals(1, ps.executeUpdate());
+      // Note: Year 0 in Java is year '0001-01-01 BC' in PostgreSQL.
+      ps.setObject(1, makeDate(0, 1, 1));
+      assertEquals(1, ps.executeUpdate());
 
-    ps.close();
+      ps.setObject(1, makeDate(0, 12, 31));
+      assertEquals(1, ps.executeUpdate());
 
-    dateTest();
+      ps.close();
 
-    assertEquals(18, stmt.executeUpdate("DELETE FROM testdate"));
-    stmt.close();
+      dateTest();
+
+      assertEquals(21, stmt.executeUpdate("DELETE FROM test"));
+      stmt.close();
+    }
   }
 
   /*
@@ -153,7 +209,7 @@ public class DateTest {
     ResultSet rs;
     java.sql.Date d;
 
-    rs = st.executeQuery(TestUtil.selectSQL("testdate", "dt"));
+    rs = st.executeQuery(TestUtil.selectSQL("test", "dt"));
     assertNotNull(rs);
 
     assertTrue(rs.next());
@@ -246,6 +302,21 @@ public class DateTest {
     assertNotNull(d);
     assertEquals(makeDate(-100, 1, 1), d);
 
+    assertTrue(rs.next());
+    d = rs.getDate(1);
+    assertNotNull(d);
+    assertEquals(makeDate(1, 1, 1), d);
+
+    assertTrue(rs.next());
+    d = rs.getDate(1);
+    assertNotNull(d);
+    assertEquals(makeDate(0, 1, 1), d);
+
+    assertTrue(rs.next());
+    d = rs.getDate(1);
+    assertNotNull(d);
+    assertEquals(makeDate(0, 12, 31), d);
+
     assertTrue(!rs.next());
 
     rs.close();


=====================================
src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
=====================================
@@ -304,6 +304,16 @@ public class IntervalTest {
     assertEquals(-1, pgi.getYears());
     assertEquals(-2, pgi.getMonths());
     assertEquals(-4, pgi.getHours());
+
+    pgi = new PGInterval("PT6.123456S");
+    assertEquals(6.123456, pgi.getSeconds(), .0);
+    assertEquals(6, pgi.getWholeSeconds());
+    assertEquals(123456, pgi.getMicroSeconds());
+
+    pgi = new PGInterval("PT-6.123456S");
+    assertEquals(-6.123456, pgi.getSeconds(), .0);
+    assertEquals(-6, pgi.getWholeSeconds());
+    assertEquals(-123456, pgi.getMicroSeconds());
   }
 
   @Test


=====================================
src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java
=====================================
@@ -8,6 +8,7 @@ package org.postgresql.test.jdbc42;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -30,17 +31,17 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
+import java.time.OffsetTime;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
+import java.time.chrono.IsoChronology;
 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;
 
@@ -54,6 +55,8 @@ public class GetObject310Test extends BaseTest4 {
   private static final ZoneOffset GMT05 = ZoneOffset.of("-05:00"); // -0500 always
   private static final ZoneOffset GMT13 = ZoneOffset.of("+13:00"); // +1300 always
 
+  private static final IsoChronology ISO = IsoChronology.INSTANCE;
+
   public GetObject310Test(BinaryMode binaryMode) {
     setBinaryMode(binaryMode);
   }
@@ -90,17 +93,101 @@ public class GetObject310Test extends BaseTest4 {
    */
   @Test
   public void testGetLocalDate() throws SQLException {
-    Statement stmt = con.createStatement();
-    stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","DATE '1999-01-08'"));
+    assumeTrue(TestUtil.haveIntegerDateTimes(con));
 
-    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();
+    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/Berlin"); // It is something like GMT+1..GMT+2
+    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("1998-01-08",
+            // Some random dates
+            "1981-12-11", "2022-02-22",
+            "2015-09-03", "2015-06-30",
+            "1997-06-30", "1997-07-01", "2012-06-30", "2012-07-01",
+            "2015-06-30", "2015-07-01", "2005-12-31", "2006-01-01",
+            "2008-12-31", "2009-01-01", "2015-06-30", "2015-07-31",
+            "2015-07-31",
+
+            // On 2000-03-26 02:00:00 Moscow went to DST, thus local time became 03:00:00
+            "2003-03-25", "2000-03-26", "2000-03-27",
+
+            // This is a pre-1970 date, so check if it is rounded properly
+            "1950-07-20",
+
+            // Ensure the calendar is proleptic
+            "1582-01-01", "1582-12-31",
+            "1582-09-30", "1582-10-16",
+
+            // https://github.com/pgjdbc/pgjdbc/issues/2221
+            "0001-01-01",
+            "1000-01-01", "1000-06-01", "0999-12-31",
+
+            // On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00
+            "2000-10-28", "2000-10-29", "2000-10-30");
+
+    for (String zoneId : zoneIdsToTest) {
+      ZoneId zone = ZoneId.of(zoneId);
+      for (String date : datesToTest) {
+        localDate(zone, date);
+      }
+    }
+  }
+
+  public void localDate(ZoneId zoneId, String date) throws SQLException {
+    TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
+    try (Statement stmt = con.createStatement(); ) {
+      stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","DATE '" + date + "'"));
+
+      try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "date_column")); ) {
+        assertTrue(rs.next());
+        LocalDate localDate = LocalDate.parse(date);
+        assertEquals(localDate, rs.getObject("date_column", LocalDate.class));
+        assertEquals(localDate, rs.getObject(1, LocalDate.class));
+      }
+      stmt.executeUpdate("DELETE FROM table1");
+    }
+  }
+
+  /**
+   * Test the behavior getObject for timetz columns.
+   */
+  @Test
+  public void testGetOffsetTime() throws SQLException {
+    List<String> timesToTest = Arrays.asList("00:00:00+00:00", "00:00:00+00:30",
+        "01:02:03.333444+02:00", "23:59:59.999999-12:00",
+        "11:22:59.4711-08:00", "23:59:59.0-12:00",
+        "11:22:59.4711+15:59:12", "23:59:59.0-15:59:12"
+    );
+
+    for (String time : timesToTest) {
+      try (Statement stmt = con.createStatement(); ) {
+        stmt.executeUpdate(TestUtil.insertSQL("table1","time_with_time_zone_column","time with time zone '" + time + "'"));
+
+        try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column")); ) {
+          assertTrue(rs.next());
+          OffsetTime offsetTime = OffsetTime.parse(time);
+          assertEquals(offsetTime, rs.getObject("time_with_time_zone_column", OffsetTime.class));
+          assertEquals(offsetTime, rs.getObject(1, OffsetTime.class));
+
+          //Also test that we get the correct values when retrieving the data as OffsetDateTime objects on EPOCH (required by JDBC)
+          OffsetDateTime offsetDT = offsetTime.atDate(LocalDate.of(1970, 1, 1));
+          assertEquals(offsetDT, rs.getObject("time_with_time_zone_column", OffsetDateTime.class));
+          assertEquals(offsetDT, rs.getObject(1, OffsetDateTime.class));
+
+          assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDate.class);
+          assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalTime.class);
+          assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDateTime.class);
+        }
+        stmt.executeUpdate("DELETE FROM table1");
+      }
     }
   }
 
@@ -109,17 +196,21 @@ public class GetObject310Test extends BaseTest4 {
    */
   @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'"));
+    try (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();
+      try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"))) {
+        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));
+
+        assertDataTypeMismatch(rs, "time_without_time_zone_column", OffsetTime.class);
+        assertDataTypeMismatch(rs, "time_without_time_zone_column", OffsetDateTime.class);
+        assertDataTypeMismatch(rs, "time_without_time_zone_column", LocalDate.class);
+        assertDataTypeMismatch(rs, "time_without_time_zone_column", LocalDateTime.class);
+      }
+      stmt.executeUpdate("DELETE FROM table1");
     }
   }
 
@@ -128,44 +219,33 @@ public class GetObject310Test extends BaseTest4 {
    */
   @Test
   public void testGetLocalTimeNull() throws SQLException {
-    Statement stmt = con.createStatement();
-    stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","NULL"));
+    try (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();
+      try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"))) {
+        assertTrue(rs.next());
+        assertNull(rs.getObject("time_without_time_zone_column", LocalTime.class));
+        assertNull(rs.getObject(1, LocalTime.class));
+      }
+      stmt.executeUpdate("DELETE FROM table1");
     }
   }
 
   /**
-   * Test the behavior getObject for time columns with null.
+   * Test the behavior getObject for time columns with invalid type.
    */
   @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'"));
+    try (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()));
+      try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column"))) {
+        assertTrue(rs.next());
+        assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalTime.class);
+        assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDateTime.class);
+        assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDate.class);
       }
-    } finally {
-      rs.close();
+      stmt.executeUpdate("DELETE FROM table1");
     }
   }
 
@@ -204,6 +284,11 @@ public class GetObject310Test extends BaseTest4 {
             // Ensure the calendar is proleptic
             "1582-09-30T00:00:00", "1582-10-16T00:00:00",
 
+            // https://github.com/pgjdbc/pgjdbc/issues/2221
+            "0001-01-01T00:00:00",
+            "1000-01-01T00:00:00",
+            "1000-01-01T23:59:59", "1000-06-01T01:00:00", "0999-12-31T23:59:59",
+
             // 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",
@@ -219,12 +304,10 @@ public class GetObject310Test extends BaseTest4 {
 
   public void localTimestamps(ZoneId zoneId, String timestamp) throws SQLException {
     TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
-    Statement stmt = con.createStatement();
-    try {
+    try (Statement stmt = con.createStatement()) {
       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 {
+      try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_without_time_zone_column"))) {
         assertTrue(rs.next());
         LocalDateTime localDateTime = LocalDateTime.parse(timestamp);
         assertEquals(localDateTime, rs.getObject("timestamp_without_time_zone_column", LocalDateTime.class));
@@ -233,12 +316,12 @@ public class GetObject310Test extends BaseTest4 {
         //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();
+
+        assertDataTypeMismatch(rs, "timestamp_without_time_zone_column", OffsetTime.class);
+        // TODO: this should also not work, but that's an open discussion (see https://github.com/pgjdbc/pgjdbc/pull/2467):
+        // assertDataTypeMismatch(rs, "timestamp_without_time_zone_column", OffsetDateTime.class);
       }
       stmt.executeUpdate("DELETE FROM table1");
-    } finally {
-      stmt.close();
     }
   }
 
@@ -254,60 +337,55 @@ public class GetObject310Test extends BaseTest4 {
   }
 
   private void runGetOffsetDateTime(ZoneOffset offset) throws SQLException {
-    Statement stmt = con.createStatement();
-    try {
+    try (Statement stmt = con.createStatement()) {
       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 {
+      try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_with_time_zone_column"))) {
         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();
+
+        assertDataTypeMismatch(rs, "timestamp_with_time_zone_column", LocalTime.class);
+        assertDataTypeMismatch(rs, "timestamp_with_time_zone_column", LocalDateTime.class);
       }
       stmt.executeUpdate("DELETE FROM table1");
-    } finally {
-      stmt.close();
     }
   }
 
   @Test
-  public void testBcTimestamp() throws SQLException {
+  public void testBcDate() throws SQLException {
+    try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 BC'::date")) {
+      assertTrue(rs.next());
+      LocalDate expected = ISO.date(IsoEra.BCE, 1582, 9, 30);
+      LocalDate actual = rs.getObject(1, LocalDate.class);
+      assertEquals(expected, actual);
+      assertFalse(rs.next());
+    }
+  }
 
-    Statement stmt = con.createStatement();
-    ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56 BC'::timestamp");
-    try {
+  @Test
+  public void testBcTimestamp() throws SQLException {
+    try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56 BC'::timestamp")) {
       assertTrue(rs.next());
-      LocalDateTime expected = LocalDateTime.of(1582, 9, 30, 12, 34, 56)
-          .with(ChronoField.ERA, IsoEra.BCE.getValue());
+      LocalDateTime expected = ISO.date(IsoEra.BCE, 1582, 9, 30).atTime(12, 34, 56);
       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 {
+    try (Statement stmt = con.createStatement();
+        ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56Z BC'::timestamp")) {
       assertTrue(rs.next());
-      OffsetDateTime expected = OffsetDateTime.of(1582, 9, 30, 12, 34, 56, 0, UTC)
-          .with(ChronoField.ERA, IsoEra.BCE.getValue());
+      OffsetDateTime expected = ISO.date(IsoEra.BCE, 1582, 9, 30).atTime(OffsetTime.of(12, 34, 56, 0, UTC));
       OffsetDateTime actual = rs.getObject(1, OffsetDateTime.class);
       assertEquals(expected, actual);
       assertFalse(rs.next());
-    } finally {
-      rs.close();
-      stmt.close();
     }
   }
 
@@ -318,7 +396,7 @@ public class GetObject310Test extends BaseTest4 {
     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())
+    List<LocalDateTime> range = Stream.iterate(start, x -> x.plusDays(1))
         .limit(numberOfDays)
         .collect(Collectors.toList());
 
@@ -332,7 +410,7 @@ public class GetObject310Test extends BaseTest4 {
     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())
+    List<OffsetDateTime> range = Stream.iterate(start, x -> x.plusDays(1))
         .limit(numberOfDays)
         .collect(Collectors.toList());
 
@@ -342,34 +420,23 @@ public class GetObject310Test extends BaseTest4 {
   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 {
+    try (PreparedStatement stmt = con.prepareStatement("SELECT * FROM generate_series(" + selectRange + ", '1 day');");
+        ResultSet rs = stmt.executeQuery()) {
       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> {
+  /** checks if getObject with given column name or index 1 throws an exception with DATA_TYPE_MISMATCH as SQLState */
+  private static void assertDataTypeMismatch(ResultSet rs, String columnName, Class<?> typeToGet) {
+    PSQLException ex = assertThrows(PSQLException.class, () -> rs.getObject(columnName, typeToGet));
+    assertEquals(PSQLState.DATA_TYPE_MISMATCH.getState(), ex.getSQLState());
 
-    @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);
-    }
+    ex = assertThrows(PSQLException.class, () -> rs.getObject(1, typeToGet));
+    assertEquals(PSQLState.DATA_TYPE_MISMATCH.getState(), ex.getSQLState());
   }
 
 }


=====================================
src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java
=====================================
@@ -29,6 +29,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
+import java.time.OffsetTime;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.chrono.IsoChronology;
@@ -145,6 +146,10 @@ public class SetObject310Test extends BaseTest4 {
   }
 
   private <T> T insertThenReadWithoutType(Object data, String columnName, Class<T> expectedType) throws SQLException {
+    return insertThenReadWithoutType(data, columnName, expectedType, true);
+  }
+
+  private <T> T insertThenReadWithoutType(Object data, String columnName, Class<T> expectedType, boolean checkRoundtrip) throws SQLException {
     PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
     try {
       ps.setObject(1, data);
@@ -160,6 +165,10 @@ public class SetObject310Test extends BaseTest4 {
         assertNotNull(rs);
 
         assertTrue(rs.next());
+        if (checkRoundtrip) {
+          assertEquals("Roundtrip set/getObject with type should return same result",
+              data, rs.getObject(1, data.getClass()));
+        }
         return expectedType.cast(rs.getObject(1));
       } finally {
         rs.close();
@@ -170,6 +179,10 @@ public class SetObject310Test extends BaseTest4 {
   }
 
   private <T> T insertThenReadWithType(Object data, int sqlType, String columnName, Class<T> expectedType) throws SQLException {
+    return insertThenReadWithType(data, sqlType, columnName, expectedType, true);
+  }
+
+  private <T> T insertThenReadWithType(Object data, int sqlType, String columnName, Class<T> expectedType, boolean checkRoundtrip) throws SQLException {
     PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
     try {
       ps.setObject(1, data, sqlType);
@@ -185,6 +198,10 @@ public class SetObject310Test extends BaseTest4 {
         assertNotNull(rs);
 
         assertTrue(rs.next());
+        if (checkRoundtrip) {
+          assertEquals("Roundtrip set/getObject with type should return same result",
+              data, rs.getObject(1, data.getClass()));
+        }
         return expectedType.cast(rs.getObject(1));
       } finally {
         rs.close();
@@ -336,7 +353,7 @@ public class SetObject310Test extends BaseTest4 {
     // TODO: fix for binary
     assumeBinaryModeRegular();
     LocalTime time = LocalTime.parse("23:59:59.999999500");
-    Time actual = insertThenReadWithoutType(time, "time_without_time_zone_column", Time.class);
+    Time actual = insertThenReadWithoutType(time, "time_without_time_zone_column", Time.class, false/*no roundtrip*/);
     assertEquals(Time.valueOf("24:00:00"), actual);
   }
 
@@ -346,7 +363,7 @@ public class SetObject310Test extends BaseTest4 {
     assumeBinaryModeRegular();
     LocalTime time = LocalTime.parse("23:59:59.999999500");
     Time actual =
-        insertThenReadWithType(time, Types.TIME, "time_without_time_zone_column", Time.class);
+        insertThenReadWithType(time, Types.TIME, "time_without_time_zone_column", Time.class, false/*no roundtrip*/);
     assertEquals(Time.valueOf("24:00:00"), actual);
   }
 
@@ -428,4 +445,22 @@ public class SetObject310Test extends BaseTest4 {
     assertEquals(expected, actual);
   }
 
+  /**
+   * Test the behavior setObject for time columns.
+   */
+  @Test
+  public void testSetOffsetTimeWithType() throws SQLException {
+    OffsetTime data = OffsetTime.parse("16:21:51+12:34");
+    insertThenReadWithType(data, Types.TIME, "time_with_time_zone_column", Time.class);
+  }
+
+  /**
+   * Test the behavior setObject for time columns.
+   */
+  @Test
+  public void testSetOffsetTimeWithoutType() throws SQLException {
+    OffsetTime data = OffsetTime.parse("16:21:51+12:34");
+    insertThenReadWithoutType(data, "time_with_time_zone_column", Time.class);
+  }
+
 }


=====================================
src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java
=====================================
@@ -7,62 +7,124 @@ package org.postgresql.test.jdbc42;
 
 import static org.junit.Assert.assertEquals;
 
-import org.postgresql.core.Provider;
 import org.postgresql.jdbc.TimestampUtils;
 
+import org.junit.Before;
 import org.junit.Test;
 
 import java.sql.SQLException;
 import java.time.LocalTime;
+import java.time.OffsetTime;
 import java.util.TimeZone;
 
 public class TimestampUtilsTest {
+  private TimestampUtils timestampUtils;
+
+  @Before
+  public void setUp() {
+    timestampUtils = new TimestampUtils(true, TimeZone::getDefault);
+  }
+
   @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
+    assertToStringOfLocalTime("00:00:00", "00:00:00");
+    assertToStringOfLocalTime("00:00:00.1", "00:00:00.1");
+    assertToStringOfLocalTime("00:00:00.12", "00:00:00.12");
+    assertToStringOfLocalTime("00:00:00.123", "00:00:00.123");
+    assertToStringOfLocalTime("00:00:00.1234", "00:00:00.1234");
+    assertToStringOfLocalTime("00:00:00.12345", "00:00:00.12345");
+    assertToStringOfLocalTime("00:00:00.123456", "00:00:00.123456");
+
+    assertToStringOfLocalTime("00:00:00.999999", "00:00:00.999999");
+    assertToStringOfLocalTime("00:00:00.999999", "00:00:00.999999499"); // 499 NanoSeconds
+    assertToStringOfLocalTime("00:00:01", "00:00:00.999999500"); // 500 NanoSeconds
+
+    assertToStringOfLocalTime("23:59:59", "23:59:59");
+
+    assertToStringOfLocalTime("23:59:59.999999", "23:59:59.999999");
+    assertToStringOfLocalTime("23:59:59.999999", "23:59:59.999999499"); // 499 NanoSeconds
+    assertToStringOfLocalTime("24:00:00", "23:59:59.999999500"); // 500 NanoSeconds
+    assertToStringOfLocalTime("24:00:00", "23:59:59.999999999"); // 999 NanoSeconds
+  }
+
+  private void assertToStringOfLocalTime(String expectedOutput, String inputTime) {
+    assertEquals("timestampUtils.toString(LocalTime.parse(" + inputTime + "))",
+        expectedOutput,
+        timestampUtils.toString(LocalTime.parse(inputTime)));
   }
 
   @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"));
+    assertToLocalTime("00:00:00", "00:00:00");
+
+    assertToLocalTime("00:00:00.1", "00:00:00.1");
+    assertToLocalTime("00:00:00.12", "00:00:00.12");
+    assertToLocalTime("00:00:00.123", "00:00:00.123");
+    assertToLocalTime("00:00:00.1234", "00:00:00.1234");
+    assertToLocalTime("00:00:00.12345", "00:00:00.12345");
+    assertToLocalTime("00:00:00.123456", "00:00:00.123456");
+    assertToLocalTime("00:00:00.999999", "00:00:00.999999");
+
+    assertToLocalTime("23:59:59", "23:59:59");
+    assertToLocalTime("23:59:59.999999", "23:59:59.999999"); // 0 NanoSeconds
+    assertToLocalTime("23:59:59.9999999", "23:59:59.9999999"); // 900 NanoSeconds
+    assertToLocalTime("23:59:59.99999999", "23:59:59.99999999"); // 990 NanoSeconds
+    assertToLocalTime("23:59:59.999999998", "23:59:59.999999998"); // 998 NanoSeconds
+    assertToLocalTime(LocalTime.MAX.toString(), "24:00:00");
+  }
+
+  private void assertToLocalTime(String expectedOutput, String inputTime) throws SQLException {
+    assertEquals("timestampUtils.toLocalTime(" + inputTime + ")",
+        LocalTime.parse(expectedOutput),
+        timestampUtils.toLocalTime(inputTime));
+  }
+
+  @Test
+  public void testToStringOfOffsetTime() {
+    assertToStringOfOffsetTime("00:00:00+00", "00:00:00+00:00");
+    assertToStringOfOffsetTime("00:00:00.1+01", "00:00:00.1+01:00");
+    assertToStringOfOffsetTime("00:00:00.12+12", "00:00:00.12+12:00");
+    assertToStringOfOffsetTime("00:00:00.123-01", "00:00:00.123-01:00");
+    assertToStringOfOffsetTime("00:00:00.1234-02", "00:00:00.1234-02:00");
+    assertToStringOfOffsetTime("00:00:00.12345-12", "00:00:00.12345-12:00");
+    assertToStringOfOffsetTime("00:00:00.123456+01:30", "00:00:00.123456+01:30");
+    assertToStringOfOffsetTime("00:00:00.123456-12:34", "00:00:00.123456-12:34");
+
+    assertToStringOfOffsetTime("23:59:59+01", "23:59:59+01:00");
+
+    assertToStringOfOffsetTime("23:59:59.999999+01", "23:59:59.999999+01:00");
+    assertToStringOfOffsetTime("23:59:59.999999+01", "23:59:59.999999499+01:00"); // 499 NanoSeconds
+    assertToStringOfOffsetTime("24:00:00+01", "23:59:59.999999500+01:00"); // 500 NanoSeconds
+    assertToStringOfOffsetTime("24:00:00+01", "23:59:59.999999999+01:00"); // 999 NanoSeconds
+  }
+
+  private void assertToStringOfOffsetTime(String expectedOutput, String inputTime) {
+    assertEquals("timestampUtils.toString(OffsetTime.parse(" + inputTime + "))",
+        expectedOutput,
+        timestampUtils.toString(OffsetTime.parse(inputTime)));
+  }
+
+  @Test
+  public void testToOffsetTime() throws SQLException {
+    assertToOffsetTime("00:00:00+00:00", "00:00:00+00");
+    assertToOffsetTime("00:00:00.1+01:00", "00:00:00.1+01");
+    assertToOffsetTime("00:00:00.12+12:00", "00:00:00.12+12");
+    assertToOffsetTime("00:00:00.123-01:00", "00:00:00.123-01");
+    assertToOffsetTime("00:00:00.1234-02:00", "00:00:00.1234-02");
+    assertToOffsetTime("00:00:00.12345-12:00", "00:00:00.12345-12");
+    assertToOffsetTime("00:00:00.123456+01:30", "00:00:00.123456+01:30");
+    assertToOffsetTime("00:00:00.123456-12:34", "00:00:00.123456-12:34");
+
+    assertToOffsetTime("23:59:59.999999+01:00", "23:59:59.999999+01"); // 0 NanoSeconds
+    assertToOffsetTime("23:59:59.9999999+01:00", "23:59:59.9999999+01"); // 900 NanoSeconds
+    assertToOffsetTime("23:59:59.99999999+01:00", "23:59:59.99999999+01"); // 990 NanoSeconds
+    assertToOffsetTime("23:59:59.999999998+01:00", "23:59:59.999999998+01"); // 998 NanoSeconds
+    assertToOffsetTime(OffsetTime.MAX.toString(), "24:00:00+01");
   }
 
-  private TimestampUtils createTimestampUtils() {
-    return new TimestampUtils(true, (Provider<TimeZone>) TimeZone::getDefault);
+  private void assertToOffsetTime(String expectedOutput, String inputTime) throws SQLException {
+    assertEquals("timestampUtils.toOffsetTime(" + inputTime + ")",
+        OffsetTime.parse(expectedOutput),
+        timestampUtils.toOffsetTime(inputTime));
   }
 }



View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/c28df98f1b484312a1b34bb0b7ae0c4bf6b11161...27013f2d51f4b555b9d7266e09b2ec78eda0a8b3

-- 
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/c28df98f1b484312a1b34bb0b7ae0c4bf6b11161...27013f2d51f4b555b9d7266e09b2ec78eda0a8b3
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20220502/9efc35b1/attachment.htm>


More information about the pkg-java-commits mailing list