diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java index be6a0115d468..43db526fc8db 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java @@ -133,10 +133,15 @@ public Object unwrap(Date value, Class type, WrapperOptions options) { ? ( (java.sql.Time) value ) : new java.sql.Time( value.getTime() % 86_400_000 ); final LocalTime localTime = time.toLocalTime(); - final long millis = time.getTime() % 1000; + long millis = time.getTime() % 1000; if ( millis == 0 ) { return localTime; } + if ( millis < 0 ) { + // The milliseconds for a Time could be negative, + // which usually means the time is in a different time zone + millis += 1_000L; + } return localTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalTimeJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalTimeJavaType.java index df131e06a873..b7aae86256c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalTimeJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocalTimeJavaType.java @@ -136,10 +136,15 @@ public LocalTime wrap(X value, WrapperOptions options) { if (value instanceof Time) { final Time time = (Time) value; final LocalTime localTime = time.toLocalTime(); - final long millis = time.getTime() % 1000; + long millis = time.getTime() % 1000; if ( millis == 0 ) { return localTime; } + if ( millis < 0 ) { + // The milliseconds for a Time could be negative, + // which usually means the time is in a different time zone + millis += 1_000L; + } return localTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaType.java index 0d78c7226e02..3062e27c7c88 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OffsetTimeJavaType.java @@ -186,10 +186,15 @@ public OffsetTime wrap(X value, WrapperOptions options) { final OffsetTime offsetTime = time.toLocalTime() .atOffset( getCurrentJdbcOffset( options) ) .withOffsetSameInstant( getCurrentSystemOffset() ); - final long millis = time.getTime() % 1000; + long millis = time.getTime() % 1000; if ( millis == 0 ) { return offsetTime; } + if ( millis < 0 ) { + // The milliseconds for a Time could be negative, + // which usually means the time is in a different time zone + millis += 1_000L; + } return offsetTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/JdbcTimeJavaTypeDescriptorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/JdbcTimeJavaTypeDescriptorTest.java new file mode 100644 index 000000000000..68164ec381d6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/JdbcTimeJavaTypeDescriptorTest.java @@ -0,0 +1,36 @@ +package org.hibernate.orm.test.type.descriptor.java; + +import java.sql.Time; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Date; + +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.JdbcTimeJavaType; +import org.hibernate.type.descriptor.java.LocalTimeJavaType; + +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@BaseUnitTest +public class JdbcTimeJavaTypeDescriptorTest { + + @Test + @JiraKey("HHH-17229") + public void testUnwrap() { + final JavaType javaType = JdbcTimeJavaType.INSTANCE; + + final Time sqlTime = new Time( + LocalDate.EPOCH.atTime( LocalTime.of( 0, 1, 2, 0 ) ) + .toInstant( ZoneOffset.ofHours( 4 ) ) + .plusMillis( 123 ) + .toEpochMilli() + ); + final LocalTime wrappedSqlTime = javaType.unwrap( sqlTime, LocalTime.class, null ); + assertThat( wrappedSqlTime ).isEqualTo( LocalTime.of( 21, 1, 2, 123_000_000 ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/LocalTimeJavaTypeDescriptorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/LocalTimeJavaTypeDescriptorTest.java new file mode 100644 index 000000000000..51883090d22b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/LocalTimeJavaTypeDescriptorTest.java @@ -0,0 +1,33 @@ +package org.hibernate.orm.test.type.descriptor.java; + +import java.sql.Time; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; + +import org.hibernate.type.descriptor.java.LocalTimeJavaType; + +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@BaseUnitTest +public class LocalTimeJavaTypeDescriptorTest { + + @Test + @JiraKey("HHH-17229") + public void testWrap() { + final LocalTimeJavaType javaType = LocalTimeJavaType.INSTANCE; + + final Time sqlTime = new Time( + LocalDate.EPOCH.atTime( LocalTime.of( 0, 1, 2, 0 ) ) + .toInstant( ZoneOffset.ofHours( 4 ) ) + .plusMillis( 123 ) + .toEpochMilli() + ); + final LocalTime wrappedSqlTime = javaType.wrap( sqlTime, null ); + assertThat( wrappedSqlTime ).isEqualTo( LocalTime.of( 21, 1, 2, 123_000_000 ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/OffsetTimeJavaTypeDescriptorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/OffsetTimeJavaTypeDescriptorTest.java new file mode 100644 index 000000000000..2acfa5b6d3f0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/java/OffsetTimeJavaTypeDescriptorTest.java @@ -0,0 +1,76 @@ +package org.hibernate.orm.test.type.descriptor.java; + +import java.sql.Time; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; +import java.util.TimeZone; + +import org.hibernate.engine.jdbc.LobCreator; +import org.hibernate.engine.jdbc.NonContextualLobCreator; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.LocalTimeJavaType; +import org.hibernate.type.descriptor.java.OffsetTimeJavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@BaseUnitTest +public class OffsetTimeJavaTypeDescriptorTest { + + @Test + @JiraKey("HHH-17229") + public void testWrap() { + final OffsetTimeJavaType javaType = OffsetTimeJavaType.INSTANCE; + final WrapperOptions wrapperOptions = new WrapperOptions() { + @Override + public SharedSessionContractImplementor getSession() { + return null; + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return null; + } + + public boolean useStreamForLobBinding() { + return false; + } + + @Override + public int getPreferredSqlTypeCodeForBoolean() { + return 0; + } + + public LobCreator getLobCreator() { + return NonContextualLobCreator.INSTANCE; + } + + public JdbcType remapSqlTypeDescriptor(JdbcType sqlTypeDescriptor) { + return sqlTypeDescriptor; + } + + @Override + public TimeZone getJdbcTimeZone() { + return null; + } + }; + + final Time sqlTime = new Time( + LocalDate.EPOCH.atTime( LocalTime.of( 0, 1, 2, 0 ) ) + .toInstant( ZoneOffset.ofHours( 4 ) ) + .plusMillis( 123 ) + .toEpochMilli() + ); + final OffsetTime wrappedSqlTime = javaType.wrap( sqlTime, wrapperOptions ); + assertThat( wrappedSqlTime ).isEqualTo( LocalTime.of( 21, 1, 2, 123_000_000 ).atOffset( OffsetDateTime.now().getOffset() ) ); + } +}