Core Java

Hibernate @TimeZoneStorage Example

1. Introduction

Hibernate 6 introduces TimeZoneStorage annotation that specifies how the time zone information of a persistent property or field should be persisted. It supports six TimeZoneStorageTypes: AUTO, DEFAULT, NATIVE, COLUMN, NORMALIZE, and NORMALIZE_UTC. In this example, I will create a simple Spring boot JPA application that utilizes Hibernate TimeZoneStorage annotation to store the time zone data into a Database.

2. Setup

In this step, I will create a gradle project along with Spring Data JPA, Spring Web, Lombok, and H2 Database libraries via Spring initializer. See Figure 1 for details.

Hibernate TimeZoneStorage Project
Figure 1 Hibernate TimeZoneStorage Project

2.1 Generated Gradle Properties

Importing the generated gradle project into Eclipse IDE. Figure 2 shows that hibernate-core-6.5.3.Final.jar is included.

Hibernate TimeZoneStorage Jar
Figure 2. Hibernate Jar

No modification is needed for the generated build.gradle file.

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.5'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'org.jcg.zheng'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

2.2 Generated Spring Boot Application

No modification is needed for the generated DemoTimeZoneStorageApplication.java file.

DemoTimeZoneStorageApplication.java

package org.jcg.zheng.demo_TimeZoneStorage;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoTimeZoneStorageApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoTimeZoneStorageApplication.class, args);
	}

}

2.3 Application Properties

In this step, I will add several properties in application.properties to enable the trace log for hibernate binding and the SQL used.

application.properties

spring.application.name=demo-TimeZoneStorage

spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true

logging.level.org.hibernate.orm.jdbc.bind=trace

spring.h2.console.enabled=true
  • Line 3, 4: enable the spring Jpa properties to show formatted SQL statements.
  • Line 6: enable the trace level of hibernate binding logging.
  • Line 8: enable the H2 console.

3. Hibernate TimeZoneStorage Entity

In this step, I will create an EntityWithDateExample.java that has eight date fields. Four of them annotate with @TimeZoneStorage.

EntityWithDateExample.java

package org.jcg.zheng.demo_TimeZoneStorage.entity;

import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Date;

import org.hibernate.annotations.TimeZoneColumn;
import org.hibernate.annotations.TimeZoneStorage;
import org.hibernate.annotations.TimeZoneStorageType;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@Entity
@Table(name = "TABLE_WITH_DATES")
public class EntityWithDateExample {

	@Column(name = "Column_OffsetTime")
	@TimeZoneColumn(name = "offset_zone")
	@TimeZoneStorage(TimeZoneStorageType.COLUMN)
	public OffsetDateTime columnOffsetTime;

	@Column(name = "Column_OffsetTime9", columnDefinition = "TIMESTAMP(9)")
	@TimeZoneColumn(name = "offset_zone9")
	@TimeZoneStorage(TimeZoneStorageType.COLUMN)
	private OffsetDateTime columnOffsetTime9;

	@Column(name = "Normal_date")
	private Date normalDate;

	@Id
	@GeneratedValue
	private Long id;

	@Column(name = "Native_OffsetTime")
	@TimeZoneStorage(TimeZoneStorageType.NATIVE)
	public OffsetDateTime nativeOffsetTime;

	@Column(name = "Native_ZonedTime9", columnDefinition = "TIMESTAMP(9) WITH TIME ZONE")
	@TimeZoneStorage(TimeZoneStorageType.NATIVE)
	private ZonedDateTime nativeZonedDateTime9;

	@Column(name = "Normal_OffsetTime")
	private OffsetDateTime normalOffsetTime;

	@Column(name = "Normal_ZonedTime")
	private ZonedDateTime normalTime;

	@Column(name = "UTC_OffsetTime9", columnDefinition = "TIMESTAMP(9)")
	@TimeZoneStorage(TimeZoneStorageType.NORMALIZE_UTC)
	private OffsetDateTime utcOffsetTime9;

}
  • Line 21, 22: Entity and Table annotations
  • Line 25-27: defines columnOffsetTime from the OffsetDateTime class. It stores microseconds up to 6 digits.
  • Line 30-32: defines columnOffsetTime9 from the OffsetDateTime class. It stores microseconds up to 9 digits.
  • Line 35: defines normalDate from the Date class ( default with 6 digits of microseconds ).
  • Line 42-44: defines nativeOffsetTime from the OffsetDateTime class. It stores microseconds up to 6 digits.
  • Line 46-48: defines nativeZonedDateTime9 from the OffsetDateTime class. It stores microseconds up to 9 digits.
  • Line 50-51: defines normalOffsetTime from the OffsetDateTime class.
  • Line 53-54: defines normalTime from the ZonedDateTime class.
  • Line 56-57: defines utcOffsetTime9 from the OffsetDateTime class. It stores microseconds up to 9 digits.

4. Repository

In this step, I will create an EntityWithDateExampleRepo.java that extends from JpaRepository.

EntityWithDateExampleRepo.java

package org.jcg.zheng.demo_TimeZoneStorage.repo;

import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EntityWithDateExampleRepo extends JpaRepository<EntityWithDateExample, Long> {

}

4.1 Repository Test

In this step, I will create an EntityExampleRepoTest.java to test the save and findAll methods.

EntityExampleRepoTest.java

package org.jcg.zheng.demo_TimeZoneStorage.repo;

import static org.junit.jupiter.api.Assertions.assertFalse;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;

import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class EntityExampleRepoTest {

	@Autowired
	private EntityWithDateExampleRepo testClass;

	@Test
	void test_save_get() {
		EntityWithDateExample example = new EntityWithDateExample();

		example.setNativeZonedDateTime9(ZonedDateTime.now().plusDays(10));
		example.setNormalTime(ZonedDateTime.now());
		example.setNormalOffsetTime(OffsetDateTime.now());
		example.setColumnOffsetTime(OffsetDateTime.now().minusYears(1));
		example.setUtcOffsetTime9(OffsetDateTime.now(ZoneOffset.UTC));
		example.setNormalDate(new Date());
		example.setColumnOffsetTime(OffsetDateTime.of(1970, 1, 1, 23, 59, 59, 999, ZoneOffset.UTC));
		example.setColumnOffsetTime9(OffsetDateTime.of(1980, 1, 1, 23, 59, 59, 999, ZoneOffset.UTC));

		testClass.save(example);

		List<EntityWithDateExample> savedData = testClass.findAll();
		assertFalse(savedData.isEmpty());
	}

}

Run the Junit test and capture the console log.

EntityExampleRepoTest Log

13:29:21.593 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [org.jcg.zheng.demo_TimeZoneStorage.repo.EntityExampleRepoTest]: EntityExampleRepoTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
13:29:21.695 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration org.jcg.zheng.demo_TimeZoneStorage.DemoTimeZoneStorageApplication for test class org.jcg.zheng.demo_TimeZoneStorage.repo.EntityExampleRepoTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.5)

2024-11-03T13:29:22.166-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.j.z.d.repo.EntityExampleRepoTest       : Starting EntityExampleRepoTest using Java 17.0.11 with PID 36984 (started by azpm0 in C:\MaryTools\workspace\demo-TimeZoneStorage)
2024-11-03T13:29:22.167-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.j.z.d.repo.EntityExampleRepoTest       : No active profile set, falling back to 1 default profile: "default"
2024-11-03T13:29:22.836-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-11-03T13:29:22.908-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 58 ms. Found 1 JPA repository interface.
2024-11-03T13:29:23.437-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-11-03T13:29:23.665-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:6f376f8f-f050-47c9-a004-6e9062a5eb5d user=SA
2024-11-03T13:29:23.667-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-11-03T13:29:23.728-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-11-03T13:29:23.799-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.5.3.Final
2024-11-03T13:29:23.844-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
2024-11-03T13:29:24.204-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-11-03T13:29:25.159-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
Hibernate: 
    drop table if exists table_with_dates cascade 
Hibernate: 
    drop sequence if exists table_with_dates_seq
Hibernate: 
    create sequence table_with_dates_seq start with 1 increment by 50
Hibernate: 
    create table table_with_dates (
        offset_zone9 integer,
        column_offset_time timestamp(6) with time zone,
        column_offset_time9 TIMESTAMP(9),
        id bigint not null,
        native_offset_time timestamp(6) with time zone,
        native_zoned_time9 TIMESTAMP(9) WITH TIME ZONE,
        normal_date timestamp(6),
        normal_offset_time timestamp(6) with time zone,
        normal_zoned_time timestamp(6) with time zone,
        utc_offset_time9 TIMESTAMP(9),
        primary key (id)
    )
2024-11-03T13:29:25.211-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-11-03T13:29:25.631-06:00  WARN 36984 --- [demo-TimeZoneStorage] [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-11-03T13:29:26.041-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:6f376f8f-f050-47c9-a004-6e9062a5eb5d'
2024-11-03T13:29:26.118-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.j.z.d.repo.EntityExampleRepoTest       : Started EntityExampleRepoTest in 4.224 seconds (process running for 5.282)
Hibernate: 
    select
        next value for table_with_dates_seq
Hibernate: 
    insert 
    into
        table_with_dates
        (column_offset_time, column_offset_time9, offset_zone9, native_offset_time, native_zoned_time9, normal_date, normal_offset_time, normal_zoned_time, utc_offset_time9, id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2024-11-03T13:29:26.221-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:TIMESTAMP_WITH_TIMEZONE) <- [1970-01-01T23:59:59.000000999Z]
2024-11-03T13:29:26.227-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:TIMESTAMP_UTC) <- [1980-01-02T05:59:59.000000999Z]
2024-11-03T13:29:26.227-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (3:INTEGER) <- [-06:00]
2024-11-03T13:29:26.228-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (4:TIMESTAMP_WITH_TIMEZONE) <- [2023-11-03T13:29:26.123093400-06:00]
2024-11-03T13:29:26.228-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (5:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-13T13:29:26.123093400-06:00[America/Chicago]]
2024-11-03T13:29:26.228-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (6:TIMESTAMP) <- [2024-11-03 13:29:26.123]
2024-11-03T13:29:26.229-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (7:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.123093400-06:00]
2024-11-03T13:29:26.230-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (8:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.123093400-06:00[America/Chicago]]
2024-11-03T13:29:26.230-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (9:TIMESTAMP_UTC) <- [2024-11-03T19:29:26.123093400Z]
2024-11-03T13:29:26.230-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (10:BIGINT) <- [1]
2024-11-03T13:29:26.242-06:00  INFO 36984 --- [demo-TimeZoneStorage] [           main] o.j.z.d.LoadSampleData                   : Preloading EntityWithDateExample(columnOffsetTime=1970-01-01T23:59:59.000000999Z, columnOffsetTime9=1980-01-01T23:59:59.000000999-06:00, normalDate=Sun Nov 03 13:29:26 CST 2024, id=1, nativeOffsetTime=2023-11-03T13:29:26.123093400-06:00, nativeZonedDateTime9=2024-11-13T13:29:26.123093400-06:00[America/Chicago], normalOffsetTime=2024-11-03T13:29:26.123093400-06:00, normalTime=2024-11-03T13:29:26.123093400-06:00[America/Chicago], utcOffsetTime9=2024-11-03T19:29:26.123093400Z)
Hibernate: 
    select
        next value for table_with_dates_seq
Hibernate: 
    insert 
    into
        table_with_dates
        (column_offset_time, column_offset_time9, offset_zone9, native_offset_time, native_zoned_time9, normal_date, normal_offset_time, normal_zoned_time, utc_offset_time9, id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2024-11-03T13:29:26.780-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:TIMESTAMP_WITH_TIMEZONE) <- [1970-01-01T23:59:59.000000999Z]
2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:TIMESTAMP_UTC) <- [1980-01-01T23:59:59.000000999Z]
2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (3:INTEGER) <- [Z]
2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (4:TIMESTAMP_WITH_TIMEZONE) <- [null]
2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (5:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-13T13:29:26.778942300-06:00[America/Chicago]]
2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (6:TIMESTAMP) <- [2024-11-03 13:29:26.779]
2024-11-03T13:29:26.781-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (7:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.778942300-06:00]
2024-11-03T13:29:26.782-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (8:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:29:26.778942300-06:00[America/Chicago]]
2024-11-03T13:29:26.782-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (9:TIMESTAMP_UTC) <- [2024-11-03T19:29:26.779928100Z]
2024-11-03T13:29:26.782-06:00 TRACE 36984 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (10:BIGINT) <- [2]
Hibernate: 
    select
        ewde1_0.id,
        ewde1_0.column_offset_time,
        ewde1_0.column_offset_time9,
        ewde1_0.offset_zone9,
        ewde1_0.native_offset_time,
        ewde1_0.native_zoned_time9,
        ewde1_0.normal_date,
        ewde1_0.normal_offset_time,
        ewde1_0.normal_zoned_time,
        ewde1_0.utc_offset_time9 
    from
        table_with_dates ewde1_0
2024-11-03T13:29:26.933-06:00  INFO 36984 --- [demo-TimeZoneStorage] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
Hibernate: 
    drop table if exists table_with_dates cascade 
Hibernate: 
    drop sequence if exists table_with_dates_seq
2024-11-03T13:29:26.938-06:00  INFO 36984 --- [demo-TimeZoneStorage] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-11-03T13:29:26.940-06:00  INFO 36984 --- [demo-TimeZoneStorage] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
  • Line 32-42: the create table statement.
  • Line 80-81: the data binding parameters.

5. Hibernate TimeZoneStorage Controller

In this step, I will create an EntityWithDateController.java that includes a GET method.

EntityWithDateController.java

package org.jcg.zheng.demo_TimeZoneStorage.controller;

import java.util.List;

import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample;
import org.jcg.zheng.demo_TimeZoneStorage.repo.EntityWithDateExampleRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EntityWithDateController {
	@Autowired
	private EntityWithDateExampleRepo rep;

	@GetMapping("/examples")
	List<EntityWithDateExample> all() {
		return rep.findAll();
	}

}

5.1 Load Sample Data

In this step, I will create a LoadSampleData.java to save testing data.

LoadSampleData.java

package org.jcg.zheng.demo_TimeZoneStorage;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;

import org.jcg.zheng.demo_TimeZoneStorage.entity.EntityWithDateExample;
import org.jcg.zheng.demo_TimeZoneStorage.repo.EntityWithDateExampleRepo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoadSampleData {

	private static final Logger LOGGER = LoggerFactory.getLogger(LoadSampleData.class);

	@Bean
	CommandLineRunner initDatabase(EntityWithDateExampleRepo repository) {

		return args -> {

			EntityWithDateExample example = new EntityWithDateExample();
			example.setNativeZonedDateTime9(ZonedDateTime.now().plusDays(10));
			example.setNativeOffsetTime(OffsetDateTime.now().minusYears(1));
			example.setNormalTime(ZonedDateTime.now());
			example.setNormalOffsetTime(OffsetDateTime.now());
			example.setUtcOffsetTime9(OffsetDateTime.now(ZoneOffset.UTC));
			example.setNormalDate(new Date());
			example.setColumnOffsetTime(OffsetDateTime.of(1970, 1, 1, 23, 59, 59, 999, ZoneOffset.UTC));
			example.setColumnOffsetTime9(OffsetDateTime.of(1980, 1, 1, 23, 59, 59, 999, ZoneOffset.of("-06:00")));

			LOGGER.info("Preloading " + repository.save(example));

		};
	}
}
  • Line 31, 33: the time zone is set as ZoneOffset.UTC.
  • Line 34: the time zone is set as ZoneOffset.of("-06:00").

6. Demo Hibernate TimeZoneStorage

6.1 Start the Spring Boot Application

In this step, I will start the Spring boot application and capture the console log.

Spring Boot Console Log

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.5)

2024-11-03T13:30:55.860-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.j.z.d.DemoTimeZoneStorageApplication   : Starting DemoTimeZoneStorageApplication using Java 17.0.11 with PID 35080 (C:\MaryTools\workspace\demo-TimeZoneStorage\bin\main started by azpm0 in C:\MaryTools\workspace\demo-TimeZoneStorage)
2024-11-03T13:30:55.865-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.j.z.d.DemoTimeZoneStorageApplication   : No active profile set, falling back to 1 default profile: "default"
2024-11-03T13:30:56.586-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-11-03T13:30:56.662-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 64 ms. Found 1 JPA repository interface.
2024-11-03T13:30:57.321-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2024-11-03T13:30:57.353-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-11-03T13:30:57.354-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.31]
2024-11-03T13:30:57.441-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-11-03T13:30:57.445-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1522 ms
2024-11-03T13:30:57.486-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-11-03T13:30:57.663-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:8c8060a5-3821-4778-9178-b4050bc94b6a user=SA
2024-11-03T13:30:57.667-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-11-03T13:30:57.681-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:8c8060a5-3821-4778-9178-b4050bc94b6a'
2024-11-03T13:30:57.873-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-11-03T13:30:57.936-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.5.3.Final
2024-11-03T13:30:57.978-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
2024-11-03T13:30:58.302-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-11-03T13:30:59.162-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
Hibernate: 
    drop table if exists table_with_dates cascade 
Hibernate: 
    drop sequence if exists table_with_dates_seq
Hibernate: 
    create sequence table_with_dates_seq start with 1 increment by 50
Hibernate: 
    create table table_with_dates (
        offset_zone9 integer,
        column_offset_time timestamp(6) with time zone,
        column_offset_time9 TIMESTAMP(9),
        id bigint not null,
        native_offset_time timestamp(6) with time zone,
        native_zoned_time9 TIMESTAMP(9) WITH TIME ZONE,
        normal_date timestamp(6),
        normal_offset_time timestamp(6) with time zone,
        normal_zoned_time timestamp(6) with time zone,
        utc_offset_time9 TIMESTAMP(9),
        primary key (id)
    )
2024-11-03T13:30:59.197-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-11-03T13:30:59.503-06:00  WARN 35080 --- [demo-TimeZoneStorage] [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-11-03T13:30:59.881-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2024-11-03T13:30:59.895-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.j.z.d.DemoTimeZoneStorageApplication   : Started DemoTimeZoneStorageApplication in 4.457 seconds (process running for 4.907)
Hibernate: 
    select
        next value for table_with_dates_seq
Hibernate: 
    insert 
    into
        table_with_dates
        (column_offset_time, column_offset_time9, offset_zone9, native_offset_time, native_zoned_time9, normal_date, normal_offset_time, normal_zoned_time, utc_offset_time9, id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2024-11-03T13:31:00.014-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (1:TIMESTAMP_WITH_TIMEZONE) <- [1970-01-01T23:59:59.000000999Z]
2024-11-03T13:31:00.024-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (2:TIMESTAMP_UTC) <- [1980-01-02T05:59:59.000000999Z]
2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (3:INTEGER) <- [-06:00]
2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (4:TIMESTAMP_WITH_TIMEZONE) <- [2023-11-03T13:30:59.900076700-06:00]
2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (5:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-13T13:30:59.899077700-06:00[America/Chicago]]
2024-11-03T13:31:00.025-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (6:TIMESTAMP) <- [2024-11-03 13:30:59.9]
2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (7:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:30:59.900076700-06:00]
2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (8:TIMESTAMP_WITH_TIMEZONE) <- [2024-11-03T13:30:59.900076700-06:00[America/Chicago]]
2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (9:TIMESTAMP_UTC) <- [2024-11-03T19:30:59.900076700Z]
2024-11-03T13:31:00.027-06:00 TRACE 35080 --- [demo-TimeZoneStorage] [           main] org.hibernate.orm.jdbc.bind              : binding parameter (10:BIGINT) <- [1]
2024-11-03T13:31:00.045-06:00  INFO 35080 --- [demo-TimeZoneStorage] [           main] o.j.z.d.LoadSampleData                   : Preloading EntityWithDateExample(columnOffsetTime=1970-01-01T23:59:59.000000999Z, columnOffsetTime9=1980-01-01T23:59:59.000000999-06:00, normalDate=Sun Nov 03 13:30:59 CST 2024, id=1, nativeOffsetTime=2023-11-03T13:30:59.900076700-06:00, nativeZonedDateTime9=2024-11-13T13:30:59.899077700-06:00[America/Chicago], normalOffsetTime=2024-11-03T13:30:59.900076700-06:00, normalTime=2024-11-03T13:30:59.900076700-06:00[America/Chicago], utcOffsetTime9=2024-11-03T19:30:59.900076700Z)

6.2 Get Rest Data

In this step, I will call GET Rest API to http://localhost:8080/examples and capture the response JSON. The date fields include the timezone exactly as it’s saved.

http://localhost:8080/examples Response JSON

[
  {
    "columnOffsetTime": "1970-01-01T23:59:59.000001Z",
    "columnOffsetTime9": "1980-01-01T23:59:59.000000999-06:00",
    "normalDate": "2024-11-03T19:30:59.900+00:00",
    "id": 1,
    "nativeOffsetTime": "2023-11-03T13:30:59.900077-06:00",
    "nativeZonedDateTime9": "2024-11-13T13:30:59.8990777-06:00",
    "normalOffsetTime": "2024-11-03T13:30:59.900077-06:00",
    "normalTime": "2024-11-03T13:30:59.900077-06:00",
    "utcOffsetTime9": "2024-11-03T19:30:59.9000767Z"
  }
]
  • Line 4: "columnOffsetTime9": "1980-01-01T23:59:59.000000999-06:00". Note: the time zone with -06:00 and 9 digits of microseconds are saved into Database table.
  • Line 7: "nativeOffsetTime": "2023-11-03T13:30:59.900077-06:00". The default 6 digits of ms along with -06:00 are saved.
  • Line 8: similar to above, but the precision is 9 instead of 6.
  • Line 11: the Z letter for UTC.

6.3 H2 Database Table

In this step, I will open H2 console web page and capture the data in TABLE_WITH_DATES table.

Figure 3. H2 Database

Note: the value in the OFFSET_ZONE9 is the offset value in seconds from UTC. Here is the math formula: -6 (hours) x 3600 (seconds/hour) = -21600 (seconds).

7. Conclusion

In this example, I showed several date fields annotated with @TimeZoneStorage to store the time zone data. It simplifies the time zone storage in the Database by providing built-in strategies, making it easier to control how time zone or offset information is stored and retrieved. If the applications require historical accuracy in the time zone, for example, the financial systems need to record the original time zone, then using Hibernate TimeZoneStorage simplifies the solution.

8. Download

This was an example of a gradle project which included Hibernate TimeZoneStorage annotation.

Download
You can download the full source code of this example here: Hibernate @TimeZoneStorage Example

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button