Skip to content

Commit

Permalink
[#1442] Add test for TimeZoneStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
blafond committed Aug 7, 2023
1 parent 6d925fb commit f24a290
Showing 1 changed file with 322 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.timezones;

import java.time.Duration;
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.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;

import org.hibernate.annotations.TimeZoneColumn;
import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.cfg.Configuration;
import org.hibernate.reactive.BaseReactiveTest;
import org.hibernate.reactive.testing.DBSelectionExtension;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.vertx.junit5.Timeout;
import io.vertx.junit5.VertxTestContext;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;

import static java.util.concurrent.TimeUnit.MINUTES;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER;
import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType;

/**
* Adapted from org.hibernate.orm.test.mapping.basic.TimeZoneStorageMappingTests
*
* <p>
* Note that the tests below do not use ORM's annotations below to calculate
* if a Dialect supports Format or Timezone types:
* @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
* @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTimezoneTypes.class....)
* </p>
* <p>
* It appears that DB2, SQLServer and Oracle do not yet support FORMAT and none of reactive's supported Dialects
* support Timezone Types via Offset, so the ORM's testNormalizeOffset(...) method is not included in these tests.
* </p>
*/
@Timeout(value = 10, timeUnit = MINUTES)
public class TimeZoneStorageMappingTest extends BaseReactiveTest {

public static Configuration configuration;

@RegisterExtension
public DBSelectionExtension selectionRule = DBSelectionExtension.skipTestsFor( DB2, SQLSERVER );

private static final ZoneOffset JVM_TIMEZONE_OFFSET = OffsetDateTime.now().getOffset();
private static final OffsetTime OFFSET_TIME = OffsetTime.of(
LocalTime.of(
12,
0,
0
),
ZoneOffset.ofHoursMinutes( 5, 45 )
);
private static final OffsetDateTime OFFSET_DATE_TIME = OffsetDateTime.of(
LocalDateTime.of(
2022,
3,
1,
12,
0,
0
),
ZoneOffset.ofHoursMinutes( 5, 45 )
);
private static final ZonedDateTime ZONED_DATE_TIME = ZonedDateTime.of(
LocalDateTime.of(
2022,
3,
1,
12,
0,
0
),
ZoneOffset.ofHoursMinutes( 5, 45 )
);
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern( "HH:mm:ssxxx" );
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern( "dd/MM/yyyy 'at' HH:mm:ssxxx" );

@Override
protected Collection<Class<?>> annotatedEntities() {
return List.of( TimeZoneStorageEntity.class );
}

@Override
protected Configuration constructConfiguration() {
configuration = super.constructConfiguration();
return configuration;
}

public static boolean skipTestOffsetRetainedAuto() {
return dbType() == ORACLE;
}

public static boolean skipTestOffsetRetainedFormatAuto() {
return dbType() == ORACLE;
}

@Override
protected void setProperties(Configuration configuration) {
super.setProperties( configuration );
configuration.setProperty( TIMEZONE_DEFAULT_STORAGE, "AUTO" );
}

@BeforeEach
public void populateDb(VertxTestContext context) {
TimeZoneStorageEntity entity = new TimeZoneStorageEntity( 1, OFFSET_TIME, OFFSET_DATE_TIME, ZONED_DATE_TIME );

test( context, getMutinySessionFactory().withTransaction( (s, t) -> s.persist( entity ) ) );
}

@DisabledIf( "skipTestOffsetRetainedAuto" )
@Test
public void testOffsetRetainedAuto(VertxTestContext context) {
testOffsetRetained( context, "Auto" );
}

@Test
public void testOffsetRetainedColumn(VertxTestContext context) {
testOffsetRetained( context, "Column" );
}

@DisabledIf( "skipTestOffsetRetainedFormatAuto" )
@Test
public void testOffsetRetainedFormatAuto(VertxTestContext context) {
testOffsetRetainedFormat( context, "Auto" );
}

@Test
public void testOffsetRetainedFormatColumn(VertxTestContext context) {
testOffsetRetainedFormat( context, "Column" );
}

public void testOffsetRetained(VertxTestContext context, String suffix) {
test( context, openSession()
.thenCompose( session -> session.createQuery(
"select " +
"e.offsetTime" + suffix + ", " +
"e.offsetDateTime" + suffix + ", " +
"e.zonedDateTime" + suffix + ", " +
"extract(offset from e.offsetTime" + suffix + "), " +
"extract(offset from e.offsetDateTime" + suffix + "), " +
"extract(offset from e.zonedDateTime" + suffix + "), " +
"e.offsetTime" + suffix + " + 1 hour, " +
"e.offsetDateTime" + suffix + " + 1 hour, " +
"e.zonedDateTime" + suffix + " + 1 hour, " +
"e.offsetTime" + suffix + " + 1 hour - e.offsetTime" + suffix + ", " +
"e.offsetDateTime" + suffix + " + 1 hour - e.offsetDateTime" + suffix + ", " +
"e.zonedDateTime" + suffix + " + 1 hour - e.zonedDateTime" + suffix + ", " +
"1 from TimeZoneStorageEntity e " +
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
Tuple.class
).getSingleResult()
.thenAccept( result -> {
assertThat( result.get( 0, OffsetTime.class ) ).isEqualTo( OFFSET_TIME );
assertThat( result.get( 1, OffsetDateTime.class ) ).isEqualTo( OFFSET_DATE_TIME );
assertThat( result.get( 2, ZonedDateTime.class ) ).isEqualTo( ZONED_DATE_TIME );
assertThat( result.get( 3, ZoneOffset.class ) ).isEqualTo( OFFSET_TIME.getOffset() );
assertThat( result.get( 4, ZoneOffset.class ) ).isEqualTo( OFFSET_DATE_TIME.getOffset() );
assertThat( result.get( 5, ZoneOffset.class ) ).isEqualTo( ZONED_DATE_TIME.getOffset() );
assertThat( result.get( 6, OffsetTime.class ) ).isEqualTo( OFFSET_TIME.plusHours( 1L ) );
assertThat( result.get( 7, OffsetDateTime.class ) ).isEqualTo( OFFSET_DATE_TIME.plusHours( 1L ) );
assertThat( result.get( 8, ZonedDateTime.class ) ).isEqualTo( ZONED_DATE_TIME.plusHours( 1L ) );
assertThat( result.get( 9, Duration.class ) ).isEqualTo( Duration.ofHours( 1L ) );
assertThat( result.get( 10, Duration.class ) ).isEqualTo( Duration.ofHours( 1L ) );
assertThat( result.get( 11, Duration.class ) ).isEqualTo( Duration.ofHours( 1L ) );
} )
)
);
}

public void testOffsetRetainedFormat(VertxTestContext context, String suffix) {
test( context, openSession()
.thenCompose( session -> session.createQuery(
"select " +
"format(e.offsetTime" + suffix + " as 'HH:mm:ssxxx'), " +
"format(e.offsetDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
"format(e.zonedDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
"1 from TimeZoneStorageEntity e " +
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
Tuple.class
).getSingleResult()
.thenAccept( result -> {
assertThat( result.get( 0, String.class ) ).isEqualTo( TIME_FORMATTER.format( OFFSET_TIME ) );
assertThat( result.get( 1, String.class ) ).isEqualTo( FORMATTER.format( OFFSET_DATE_TIME ) );
assertThat( result.get( 2, String.class ) ).isEqualTo( FORMATTER.format( ZONED_DATE_TIME ) );
} )
)
);
}

@Test
public void testNormalize(VertxTestContext context) {
test( context, openSession()
.thenCompose( session -> session.createQuery(
"select " +
"e.offsetTimeNormalized, " +
"e.offsetDateTimeNormalized, " +
"e.zonedDateTimeNormalized, " +
"e.offsetTimeNormalizedUtc, " +
"e.offsetDateTimeNormalizedUtc, " +
"e.zonedDateTimeNormalizedUtc " +
"from TimeZoneStorageEntity e",
Tuple.class
).getSingleResult()
.thenAccept( result -> {
assertThat( result.get( 0, OffsetTime.class ).toLocalTime()).isEqualTo( OFFSET_TIME.withOffsetSameInstant( JVM_TIMEZONE_OFFSET ).toLocalTime() );
assertThat( result.get( 0, OffsetTime.class ).getOffset()).isEqualTo( JVM_TIMEZONE_OFFSET );
assertThat( result.get( 1, OffsetDateTime.class ).toInstant()).isEqualTo( OFFSET_DATE_TIME.toInstant() );
assertThat( result.get( 2, ZonedDateTime.class ).toInstant()).isEqualTo( ZONED_DATE_TIME.toInstant() );
assertThat( result.get( 3, OffsetTime.class ).toLocalTime()).isEqualTo( OFFSET_TIME.withOffsetSameInstant( ZoneOffset.UTC ).toLocalTime() );
assertThat( result.get( 3, OffsetTime.class ).getOffset()).isEqualTo( ZoneOffset.UTC );
assertThat( result.get( 4, OffsetDateTime.class ).toInstant()).isEqualTo( OFFSET_DATE_TIME.toInstant() );
assertThat( result.get( 5, ZonedDateTime.class ).toInstant()).isEqualTo( ZONED_DATE_TIME.toInstant() );
}
)
)
);
}

@Entity(name = "TimeZoneStorageEntity")
@Table(name = "TimeZoneStorageEntity")
public static class TimeZoneStorageEntity {
@Id
public Integer id;

//tag::time-zone-column-examples-mapping-example[]
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
@TimeZoneColumn(name = "birthtime_offset_offset")
@Column(name = "birthtime_offset")
public OffsetTime offsetTimeColumn;

@TimeZoneStorage(TimeZoneStorageType.COLUMN)
@TimeZoneColumn(name = "birthday_offset_offset")
@Column(name = "birthday_offset")
public OffsetDateTime offsetDateTimeColumn;

@TimeZoneStorage(TimeZoneStorageType.COLUMN)
@TimeZoneColumn(name = "birthday_zoned_offset")
@Column(name = "birthday_zoned")
public ZonedDateTime zonedDateTimeColumn;
//end::time-zone-column-examples-mapping-example[]

@TimeZoneStorage
@Column(name = "birthtime_offset_auto")
public OffsetTime offsetTimeAuto;

@TimeZoneStorage
@Column(name = "birthday_offset_auto")
public OffsetDateTime offsetDateTimeAuto;

@TimeZoneStorage
@Column(name = "birthday_zoned_auto")
public ZonedDateTime zonedDateTimeAuto;

@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
@Column(name = "birthtime_offset_normalized")
public OffsetTime offsetTimeNormalized;

@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
@Column(name = "birthday_offset_normalized")
public OffsetDateTime offsetDateTimeNormalized;

@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
@Column(name = "birthday_zoned_normalized")
public ZonedDateTime zonedDateTimeNormalized;

@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
@Column(name = "birthtime_offset_utc")
public OffsetTime offsetTimeNormalizedUtc;

@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
@Column(name = "birthday_offset_utc")
public OffsetDateTime offsetDateTimeNormalizedUtc;

@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
@Column(name = "birthday_zoned_utc")
private ZonedDateTime zonedDateTimeNormalizedUtc;

public TimeZoneStorageEntity() {
}

public TimeZoneStorageEntity(Integer id, OffsetTime offsetTime, OffsetDateTime offsetDateTime, ZonedDateTime zonedDateTime) {
this.id = id;
this.offsetTimeColumn = offsetTime;
this.offsetDateTimeColumn = offsetDateTime;
this.zonedDateTimeColumn = zonedDateTime;
this.offsetTimeAuto = offsetTime;
this.offsetDateTimeAuto = offsetDateTime;
this.zonedDateTimeAuto = zonedDateTime;
this.offsetTimeNormalized = offsetTime;
this.offsetDateTimeNormalized = offsetDateTime;
this.zonedDateTimeNormalized = zonedDateTime;
this.offsetTimeNormalizedUtc = offsetTime;
this.offsetDateTimeNormalizedUtc = offsetDateTime;
this.zonedDateTimeNormalizedUtc = zonedDateTime;
}
}
}

0 comments on commit f24a290

Please sign in to comment.