Skip to content

Commit 839e451

Browse files
committed
[#1442] Test TimeZoneStorage annotation
1 parent 6d925fb commit 839e451

File tree

1 file changed

+337
-0
lines changed

1 file changed

+337
-0
lines changed
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.timezones;
7+
8+
import java.time.Duration;
9+
import java.time.LocalDateTime;
10+
import java.time.LocalTime;
11+
import java.time.OffsetDateTime;
12+
import java.time.OffsetTime;
13+
import java.time.ZoneOffset;
14+
import java.time.ZonedDateTime;
15+
import java.time.format.DateTimeFormatter;
16+
import java.util.Collection;
17+
import java.util.List;
18+
19+
import org.hibernate.annotations.TimeZoneColumn;
20+
import org.hibernate.annotations.TimeZoneStorage;
21+
import org.hibernate.annotations.TimeZoneStorageType;
22+
import org.hibernate.cfg.Configuration;
23+
import org.hibernate.dialect.TimeZoneSupport;
24+
import org.hibernate.reactive.BaseReactiveTest;
25+
import org.hibernate.reactive.testing.DBSelectionExtension;
26+
import org.hibernate.sql.ast.spi.StringBuilderSqlAppender;
27+
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.api.condition.EnabledIf;
31+
import org.junit.jupiter.api.extension.RegisterExtension;
32+
33+
import io.vertx.junit5.VertxTestContext;
34+
import jakarta.persistence.Column;
35+
import jakarta.persistence.Entity;
36+
import jakarta.persistence.Id;
37+
import jakarta.persistence.Table;
38+
import jakarta.persistence.Tuple;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE;
42+
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE;
43+
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER;
44+
import static org.hibernate.reactive.testing.DBSelectionExtension.skipTestsFor;
45+
46+
/**
47+
* Adapted from org.hibernate.orm.test.mapping.basic.TimeZoneStorageMappingTests
48+
*/
49+
public class TimeZoneStorageMappingTests extends BaseReactiveTest {
50+
51+
@RegisterExtension
52+
public DBSelectionExtension selectionRule = skipTestsFor( ORACLE, SQLSERVER );
53+
private static final ZoneOffset JVM_TIMEZONE_OFFSET = OffsetDateTime.now().getOffset();
54+
private static final OffsetTime OFFSET_TIME = OffsetTime.of(
55+
LocalTime.of(
56+
12,
57+
0,
58+
0
59+
),
60+
ZoneOffset.ofHoursMinutes( 5, 45 )
61+
);
62+
private static final OffsetDateTime OFFSET_DATE_TIME = OffsetDateTime.of(
63+
LocalDateTime.of(
64+
2022,
65+
3,
66+
1,
67+
12,
68+
0,
69+
0
70+
),
71+
ZoneOffset.ofHoursMinutes( 5, 45 )
72+
);
73+
private static final ZonedDateTime ZONED_DATE_TIME = ZonedDateTime.of(
74+
LocalDateTime.of(
75+
2022,
76+
3,
77+
1,
78+
12,
79+
0,
80+
0
81+
),
82+
ZoneOffset.ofHoursMinutes( 5, 45 )
83+
);
84+
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern( "HH:mm:ssxxx" );
85+
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern( "dd/MM/yyyy 'at' HH:mm:ssxxx" );
86+
87+
88+
@Override
89+
protected Collection<Class<?>> annotatedEntities() {
90+
return List.of( TimeZoneStorageEntity.class );
91+
}
92+
93+
@Override
94+
protected void setProperties(Configuration configuration) {
95+
super.setProperties( configuration );
96+
configuration.setProperty( TIMEZONE_DEFAULT_STORAGE, "AUTO" );
97+
}
98+
99+
@BeforeEach
100+
public void populateDb(VertxTestContext context) {
101+
TimeZoneStorageEntity entity = new TimeZoneStorageEntity( 1, OFFSET_TIME, OFFSET_DATE_TIME, ZONED_DATE_TIME );
102+
103+
test( context, getMutinySessionFactory().withTransaction( (s, t) -> s.persist( entity ) ) );
104+
}
105+
106+
@EnabledIf( "dialectSupportsFormat" )
107+
@Test
108+
public void testOffsetRetainedAuto(VertxTestContext context) {
109+
testOffsetRetained( context, "Auto" );
110+
}
111+
112+
@Test
113+
public void testOffsetRetainedColumn(VertxTestContext context) {
114+
testOffsetRetained( context, "Column" );
115+
}
116+
117+
@Test
118+
@EnabledIf( "dialectSupportsFormat" )
119+
public void testOffsetRetainedFormatAuto(VertxTestContext context) {
120+
testOffsetRetainedFormat( context, "Auto" );
121+
}
122+
123+
@Test
124+
@EnabledIf( "dialectSupportsFormat" )
125+
public void testOffsetRetainedFormatColumn(VertxTestContext context) {
126+
testOffsetRetainedFormat( context, "Column" );
127+
}
128+
129+
public void testOffsetRetained(VertxTestContext context, String suffix) {
130+
test( context, openSession()
131+
.thenCompose( session -> session.createQuery(
132+
"select " +
133+
"e.offsetTime" + suffix + ", " +
134+
"e.offsetDateTime" + suffix + ", " +
135+
"e.zonedDateTime" + suffix + ", " +
136+
"extract(offset from e.offsetTime" + suffix + "), " +
137+
"extract(offset from e.offsetDateTime" + suffix + "), " +
138+
"extract(offset from e.zonedDateTime" + suffix + "), " +
139+
"e.offsetTime" + suffix + " + 1 hour, " +
140+
"e.offsetDateTime" + suffix + " + 1 hour, " +
141+
"e.zonedDateTime" + suffix + " + 1 hour, " +
142+
"e.offsetTime" + suffix + " + 1 hour - e.offsetTime" + suffix + ", " +
143+
"e.offsetDateTime" + suffix + " + 1 hour - e.offsetDateTime" + suffix + ", " +
144+
"e.zonedDateTime" + suffix + " + 1 hour - e.zonedDateTime" + suffix + ", " +
145+
"1 from TimeZoneStorageEntity e " +
146+
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
147+
Tuple.class
148+
).getSingleResult()
149+
.thenAccept( result -> {
150+
assertThat( result.get( 0, OffsetTime.class ) ).isEqualTo( OFFSET_TIME );
151+
assertThat( result.get( 1, OffsetDateTime.class ) ).isEqualTo( OFFSET_DATE_TIME );
152+
assertThat( result.get( 2, ZonedDateTime.class ) ).isEqualTo( ZONED_DATE_TIME );
153+
assertThat( result.get( 3, ZoneOffset.class ) ).isEqualTo( OFFSET_TIME.getOffset() );
154+
assertThat( result.get( 4, ZoneOffset.class ) ).isEqualTo( OFFSET_DATE_TIME.getOffset() );
155+
assertThat( result.get( 5, ZoneOffset.class ) ).isEqualTo( ZONED_DATE_TIME.getOffset() );
156+
assertThat( result.get( 6, OffsetTime.class ) ).isEqualTo( OFFSET_TIME.plusHours( 1L ) );
157+
assertThat( result.get( 7, OffsetDateTime.class ) ).isEqualTo( OFFSET_DATE_TIME.plusHours( 1L ) );
158+
assertThat( result.get( 8, ZonedDateTime.class ) ).isEqualTo( ZONED_DATE_TIME.plusHours( 1L ) );
159+
assertThat( result.get( 9, Duration.class ) ).isEqualTo( Duration.ofHours( 1L ) );
160+
assertThat( result.get( 10, Duration.class ) ).isEqualTo( Duration.ofHours( 1L ) );
161+
assertThat( result.get( 11, Duration.class ) ).isEqualTo( Duration.ofHours( 1L ) );
162+
} )
163+
)
164+
);
165+
}
166+
167+
public void testOffsetRetainedFormat(VertxTestContext context, String suffix) {
168+
test( context, openSession()
169+
.thenCompose( session -> session.createQuery(
170+
"select " +
171+
"format(e.offsetTime" + suffix + " as 'HH:mm:ssxxx'), " +
172+
"format(e.offsetDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
173+
"format(e.zonedDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
174+
"1 from TimeZoneStorageEntity e " +
175+
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
176+
Tuple.class
177+
).getSingleResult()
178+
.thenAccept( result -> {
179+
assertThat( result.get( 0, String.class ) ).isEqualTo( TIME_FORMATTER.format( OFFSET_TIME ) );
180+
assertThat( result.get( 1, String.class ) ).isEqualTo( FORMATTER.format( OFFSET_DATE_TIME ) );
181+
assertThat( result.get( 2, String.class ) ).isEqualTo( FORMATTER.format( ZONED_DATE_TIME ) );
182+
} )
183+
)
184+
);
185+
}
186+
187+
@Test
188+
public void testNormalize(VertxTestContext context) {
189+
test( context, openSession()
190+
.thenCompose( session -> session.createQuery(
191+
"select " +
192+
"e.offsetTimeNormalized, " +
193+
"e.offsetDateTimeNormalized, " +
194+
"e.zonedDateTimeNormalized, " +
195+
"e.offsetTimeNormalizedUtc, " +
196+
"e.offsetDateTimeNormalizedUtc, " +
197+
"e.zonedDateTimeNormalizedUtc " +
198+
"from TimeZoneStorageEntity e",
199+
Tuple.class
200+
).getSingleResult()
201+
.thenAccept( result -> {
202+
assertThat( result.get( 0, OffsetTime.class ).toLocalTime()).isEqualTo( OFFSET_TIME.withOffsetSameInstant( JVM_TIMEZONE_OFFSET ).toLocalTime() );
203+
assertThat( result.get( 0, OffsetTime.class ).getOffset()).isEqualTo( JVM_TIMEZONE_OFFSET );
204+
assertThat( result.get( 1, OffsetDateTime.class ).toInstant()).isEqualTo( OFFSET_DATE_TIME.toInstant() );
205+
assertThat( result.get( 2, ZonedDateTime.class ).toInstant()).isEqualTo( ZONED_DATE_TIME.toInstant() );
206+
assertThat( result.get( 3, OffsetTime.class ).toLocalTime()).isEqualTo( OFFSET_TIME.withOffsetSameInstant( ZoneOffset.UTC ).toLocalTime() );
207+
assertThat( result.get( 3, OffsetTime.class ).getOffset()).isEqualTo( ZoneOffset.UTC );
208+
assertThat( result.get( 4, OffsetDateTime.class ).toInstant()).isEqualTo( OFFSET_DATE_TIME.toInstant() );
209+
assertThat( result.get( 5, ZonedDateTime.class ).toInstant()).isEqualTo( ZONED_DATE_TIME.toInstant() );
210+
}
211+
)
212+
)
213+
);
214+
}
215+
216+
@EnabledIf( "dialectSupportsFormatAndTimezoneTypes" )
217+
@Test
218+
public void testNormalizeOffset(VertxTestContext context) {
219+
// FAILS: PG, DB2, MySQL, CockroachDB & Oracle
220+
// ALL TESTS FAIL FOR DB2 & SQLServer
221+
test( context, openSession()
222+
.thenCompose( session -> session.createQuery(
223+
"select " +
224+
"extract(offset from e.offsetTimeNormalizedUtc), " +
225+
"extract(offset from e.offsetDateTimeNormalizedUtc), " +
226+
"extract(offset from e.zonedDateTimeNormalizedUtc) " +
227+
"from TimeZoneStorageEntity e",
228+
Tuple.class
229+
).getSingleResult()
230+
.thenAccept( result -> {
231+
assertThat( result.get( 0, ZoneOffset.class ) ).isEqualTo( ZoneOffset.UTC );
232+
assertThat( result.get( 1, ZoneOffset.class ) ).isEqualTo( ZoneOffset.UTC );
233+
assertThat( result.get( 2, ZoneOffset.class ) ).isEqualTo( ZoneOffset.UTC );
234+
}
235+
)
236+
)
237+
);
238+
}
239+
240+
public boolean dialectSupportsFormatAndTimezoneTypes() {
241+
boolean result = dialectSupportsFormat();
242+
243+
if( result ) {
244+
result = getDialect().getTimeZoneSupport() == TimeZoneSupport.NATIVE.NATIVE;
245+
}
246+
return result;
247+
}
248+
249+
public boolean dialectSupportsFormat() {
250+
try {
251+
getDialect().appendDatetimeFormat( new StringBuilderSqlAppender(), "" );
252+
return true;
253+
}
254+
catch (Exception ex) {
255+
return false;
256+
}
257+
}
258+
259+
@Entity(name = "TimeZoneStorageEntity")
260+
@Table(name = "TimeZoneStorageEntity")
261+
public static class TimeZoneStorageEntity {
262+
@Id
263+
public Integer id;
264+
265+
//tag::time-zone-column-examples-mapping-example[]
266+
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
267+
@TimeZoneColumn(name = "birthtime_offset_offset")
268+
@Column(name = "birthtime_offset")
269+
public OffsetTime offsetTimeColumn;
270+
271+
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
272+
@TimeZoneColumn(name = "birthday_offset_offset")
273+
@Column(name = "birthday_offset")
274+
public OffsetDateTime offsetDateTimeColumn;
275+
276+
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
277+
@TimeZoneColumn(name = "birthday_zoned_offset")
278+
@Column(name = "birthday_zoned")
279+
public ZonedDateTime zonedDateTimeColumn;
280+
//end::time-zone-column-examples-mapping-example[]
281+
282+
@TimeZoneStorage
283+
@Column(name = "birthtime_offset_auto")
284+
public OffsetTime offsetTimeAuto;
285+
286+
@TimeZoneStorage
287+
@Column(name = "birthday_offset_auto")
288+
public OffsetDateTime offsetDateTimeAuto;
289+
290+
@TimeZoneStorage
291+
@Column(name = "birthday_zoned_auto")
292+
public ZonedDateTime zonedDateTimeAuto;
293+
294+
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
295+
@Column(name = "birthtime_offset_normalized")
296+
public OffsetTime offsetTimeNormalized;
297+
298+
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
299+
@Column(name = "birthday_offset_normalized")
300+
public OffsetDateTime offsetDateTimeNormalized;
301+
302+
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
303+
@Column(name = "birthday_zoned_normalized")
304+
public ZonedDateTime zonedDateTimeNormalized;
305+
306+
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
307+
@Column(name = "birthtime_offset_utc")
308+
public OffsetTime offsetTimeNormalizedUtc;
309+
310+
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
311+
@Column(name = "birthday_offset_utc")
312+
public OffsetDateTime offsetDateTimeNormalizedUtc;
313+
314+
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
315+
@Column(name = "birthday_zoned_utc")
316+
private ZonedDateTime zonedDateTimeNormalizedUtc;
317+
318+
public TimeZoneStorageEntity() {
319+
}
320+
321+
public TimeZoneStorageEntity(Integer id, OffsetTime offsetTime, OffsetDateTime offsetDateTime, ZonedDateTime zonedDateTime) {
322+
this.id = id;
323+
this.offsetTimeColumn = offsetTime;
324+
this.offsetDateTimeColumn = offsetDateTime;
325+
this.zonedDateTimeColumn = zonedDateTime;
326+
this.offsetTimeAuto = offsetTime;
327+
this.offsetDateTimeAuto = offsetDateTime;
328+
this.zonedDateTimeAuto = zonedDateTime;
329+
this.offsetTimeNormalized = offsetTime;
330+
this.offsetDateTimeNormalized = offsetDateTime;
331+
this.zonedDateTimeNormalized = zonedDateTime;
332+
this.offsetTimeNormalizedUtc = offsetTime;
333+
this.offsetDateTimeNormalizedUtc = offsetDateTime;
334+
this.zonedDateTimeNormalizedUtc = zonedDateTime;
335+
}
336+
}
337+
}

0 commit comments

Comments
 (0)