1. Introduction
Handling date and time values is an essential task, especially when dealing with systems that track events, transactions, or logs. PostgreSQL is a powerful open-source relational database management system and it has data types: DATE
, TIME
, TIMESTAMP
, and TIMESTAMPTZ
to store date and time values. In this example, I will create a PostgreSQL store date time application in a Spring boot application.
Here is the mapping between PostgreSQL data types and Java Data types. In this example, I will use types from the java.time
package instead of the java.sql
package.
PostgreSQL Data Type | PostgreSQL Storage | Description | Java 7 or less Data Type | Java 8+ Data Type |
DATE | 4 bytes | represents the date via year, month, day | java.sql.Date | java.time.LocalDate |
TIME | 4 bytes | represents the time via hour, minute, second | java.sql.Time | java.time.LocalTime |
TIMESTAMP | 8 bytes | represents both the date and time, without a time zone. | java.sql.Timestamp | java.time.LocalDateTime |
TIMESTAMPTZ | 8 bytes | represents the date and time with a time zone. | java.sql.Timestamp and manage the offsets manually. | java.time.OffsetDateTime or java.time.ZonedDateTime |
2. Setup
2.1 Start PostgreSQL locally
Downloaded the postgresql-17.2-3-windows-x64
from here and started the PostgreSQL locally.
2.2 Generate Spring Boot Project
In this step, I will create a gradle project along with Lombok
and PostgreSQL libraries via Spring initializer.
Here is the generated build.gradle
file.
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '3.4.1' id 'io.spring.dependency-management' version '1.1.7' } group = 'org.zheng.demo' 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' implementation 'org.postgresql:postgresql:42.7.4' runtimeOnly 'org.postgresql:postgresql' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
Here is the generated spring boot application.
PostgresDateDemoApplication.java
package org.zheng.demo.postgresDateDemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PostgresDateDemoApplication { public static void main(String[] args) { SpringApplication.run(PostgresDateDemoApplication.class, args); } }
Here is the spring properties that include the PostgreSQL connection to the local PostgreSQL setup in step 2.1.
application.properties
spring.application.name=postgresDateDemo # DataSource configuration spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username={REPLACE_WITH_YOUR_USERNAME} spring.datasource.password={REPLACE_WITH_YOUR_PWD} spring.datasource.driver-class-name=org.postgresql.Driver # Hibernate Dialect for PostgreSQL spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # Hibernate Settings spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true logging.level.org.hibernate.orm.jdbc.bind=trace # PostgreSQL connection pool settings (optional) spring.datasource.hikari.maximum-pool-size=10
3. Java DemoDateTime
In this step, I will create a DemoDateTime.java
entity class that maps demo_date_time_table
. This table has columns with date
, time
, timestamp
, and timestamptz
types.
DemoDateTime.java
package org.zheng.demo.postgresDateDemo.entity; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZonedDateTime; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; @Entity @Table(name = "demo_date_time_table") @Data public class DemoDateTime { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id; @Column(name = "date_col") private LocalDate demoDate; @Column(name = "time_col") private LocalTime demoTime; @Column(name = "local_timestamp_col") private LocalDateTime demoLocalDateTime; @Column(name = "zoned_timestamp_zocol") private OffsetDateTime demoZonedDateTimeOffset; @Column(name = "zoned_timestamp_zcol") private ZonedDateTime demoZonedDateTime; @Column(name = "zoned_timestamp_pcol") private ZonedDateTime demoZonedDateTimePrecision; }
The @Column
annotation maps the Java variable to its mapped database column.
4. DemoDateTimeRepo
In this step, I will create a DemoDateTimeRepo.java
interface extended from the JpaRepository
.
DemoDateTimeRepo.java
package org.zheng.demo.postgresDateDemo.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import org.zheng.demo.postgresDateDemo.entity.DemoDateTime; @Repository public interface DemoDateTimeRepo extends JpaRepository<DemoDateTime, Integer> { }
I will create a DemoDateTimeRepoTest.java
that saves data in the database.
DemoDateTimeRepoTest.java
package org.zheng.demo.postgresDateDemo.repository; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.zheng.demo.postgresDateDemo.entity.DemoDateTime; @SpringBootTest class DemoDateTimeRepoTest { @Autowired private DemoDateTimeRepo testClass; @Test void test_save_found() { DemoDateTime demoObj = new DemoDateTime(); demoObj.setDemoDate(LocalDate.now()); demoObj.setDemoTime(LocalTime.now()); demoObj.setDemoLocalDateTime(LocalDateTime.now()); demoObj.setDemoZonedDateTime(ZonedDateTime.now()); demoObj.setDemoZonedDateTimePrecision(ZonedDateTime.now()); demoObj.setDemoZonedDateTimeOffset(OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-6))); testClass.save(demoObj); List<DemoDateTime> found = testClass.findAll(); assertTrue( found.size() > 0); } }
4.1. DemoRestController
In this step, I will create a DemoRestController.java
that can save and retrieve the data from the PostgreSQL database.
DemoRestController.java
package org.zheng.demo.postgresDateDemo.rest; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.zheng.demo.postgresDateDemo.entity.DemoDateTime; import org.zheng.demo.postgresDateDemo.repository.DemoDateTimeRepo; @RestController public class DemoRestController { @Autowired private DemoDateTimeRepo demoRepo; @GetMapping("/demo") public List getDemoDates() { return demoRepo.findAll(); } @PostMapping("/demo") public DemoDateTime saveDemo() { DemoDateTime demoObj = new DemoDateTime(); demoObj.setDemoDate(LocalDate.now()); demoObj.setDemoTime(LocalTime.now()); demoObj.setDemoLocalDateTime(LocalDateTime.now()); ZonedDateTime zonedNow = ZonedDateTime.now(); demoObj.setDemoZonedDateTime(zonedNow); demoObj.setDemoZonedDateTimePrecision(zonedNow); demoObj.setDemoZonedDateTimeOffset(OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-6))); return demoRepo.save(demoObj); } }
5. Demonstrate
In this step, I will start the spring boot application and capture the server log.
Spring boot Server Log
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.4.1) 2025-01-04T16:10:55.727-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.z.d.p.PostgresDateDemoApplication : Starting PostgresDateDemoApplication using Java 17.0.11 with PID 28800 (C:\MaryTools\workspace\postgresDateDemo\bin\main started by azpm0 in C:\MaryTools\workspace\postgresDateDemo) 2025-01-04T16:10:55.736-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.z.d.p.PostgresDateDemoApplication : No active profile set, falling back to 1 default profile: "default" 2025-01-04T16:10:56.667-06:00 INFO 28800 --- [postgresDateDemo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2025-01-04T16:10:56.766-06:00 INFO 28800 --- [postgresDateDemo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 80 ms. Found 1 JPA repository interface. 2025-01-04T16:10:57.585-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2025-01-04T16:10:57.602-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-01-04T16:10:57.602-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.34] 2025-01-04T16:10:57.696-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2025-01-04T16:10:57.699-06:00 INFO 28800 --- [postgresDateDemo] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1860 ms 2025-01-04T16:10:57.963-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2025-01-04T16:10:58.039-06:00 INFO 28800 --- [postgresDateDemo] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.4.Final 2025-01-04T16:10:58.083-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2025-01-04T16:10:58.457-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2025-01-04T16:10:58.499-06:00 INFO 28800 --- [postgresDateDemo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2025-01-04T16:10:58.823-06:00 INFO 28800 --- [postgresDateDemo] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@45571cfc 2025-01-04T16:10:58.825-06:00 INFO 28800 --- [postgresDateDemo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2025-01-04T16:10:58.880-06:00 WARN 28800 --- [postgresDateDemo] [ main] org.hibernate.orm.deprecation : HHH90000025: PostgreSQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default) 2025-01-04T16:10:58.907-06:00 INFO 28800 --- [postgresDateDemo] [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info: Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] Database driver: undefined/unknown Database version: 17.2 Autocommit mode: undefined/unknown Isolation level: undefined/unknown Minimum pool size: undefined/unknown Maximum pool size: undefined/unknown 2025-01-04T16:10:59.953-06:00 INFO 28800 --- [postgresDateDemo] [ 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: create table demo_date_time_table ( id integer not null, date_col date, local_timestamp_col timestamp(6), time_col time(6), zoned_timestamp_zcol timestamp(6) with time zone, zoned_timestamp_zocol timestamp(6) with time zone, zoned_timestamp_pcol timestamp(6) with time zone, primary key (id) ) 2025-01-04T16:11:00.033-06:00 INFO 28800 --- [postgresDateDemo] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2025-01-04T16:11:00.411-06:00 WARN 28800 --- [postgresDateDemo] [ 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 2025-01-04T16:11:00.909-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2025-01-04T16:11:00.928-06:00 INFO 28800 --- [postgresDateDemo] [ main] o.z.d.p.PostgresDateDemoApplication : Started PostgresDateDemoApplication in 5.749 seconds (process running for 6.25)
- Line 37-46: the create table DDL is generated by the Hibernate. Note: the generated timestamp has the default precision of 6. If you like, you can change the precision with the following SQL:
alter table demo_date_time_table alter column zoned_timestamp_pcol SET DATA TYPE TIMESTAMPTZ(3)
;
5.1 Test PostgreSQL Store Date Time via ThunderClient
In this step, I will test the POST http://localhost:8080/demo.
POST http://localhost:8080/demo Response
{ "id": 652, "demoDate": "2025-01-04", "demoTime": "16:16:26.0627956", "demoLocalDateTime": "2025-01-04T16:16:26.0627956", "demoZonedDateTimeOffset": "2025-01-04T16:16:26.0627956-06:00", "demoZonedDateTime": "2025-01-04T16:16:26.0627956-06:00", "demoZonedDateTimePrecision": "2025-01-04T16:16:26.0627956-06:00" }
Capture the server log.
POST http://localhost:8080/demo Server Log
Hibernate: insert into demo_date_time_table (date_col, local_timestamp_col, time_col, zoned_timestamp_zcol, zoned_timestamp_zocol, zoned_timestamp_pcol, id) values (?, ?, ?, ?, ?, ?, ?) 2025-01-04T16:16:26.140-06:00 TRACE 28800 --- [postgresDateDemo] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (1:DATE) <- [2025-01-04] 2025-01-04T16:16:26.143-06:00 TRACE 28800 --- [postgresDateDemo] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (2:TIMESTAMP) <- [2025-01-04T16:16:26.062795600] 2025-01-04T16:16:26.143-06:00 TRACE 28800 --- [postgresDateDemo] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (3:TIME) <- [16:16:26.062795600] 2025-01-04T16:16:26.143-06:00 TRACE 28800 --- [postgresDateDemo] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (4:TIMESTAMP_UTC) <- [2025-01-04T16:16:26.062795600-06:00[America/Chicago]] 2025-01-04T16:16:26.143-06:00 TRACE 28800 --- [postgresDateDemo] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (5:TIMESTAMP_UTC) <- [2025-01-04T16:16:26.062795600-06:00] 2025-01-04T16:16:26.143-06:00 TRACE 28800 --- [postgresDateDemo] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (6:TIMESTAMP_UTC) <- [2025-01-04T16:16:26.062795600-06:00[America/Chicago]] 2025-01-04T16:16:26.143-06:00 TRACE 28800 --- [postgresDateDemo] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (7:INTEGER) <- [652]
- Line 8-13: show the binding value for each data type.
5.2 View PostgreSQL Date Time via DBeaver
In this step, I will view the date and time data via DBeaver tool with a select statement.
Select SQL
select * from demo_date_time_table;
- DBeaver defaults the timestamp with the “yyyy-MM-dd HH:mm:ss.SSS” format. You can adjust it
6. Conclusion
In this example, I created a spring boot application that utilized PostgreSQL to store the date and time. By default, PostgreSQL’s TIMESTAMP
data type has microsecond precision (TIMESTAMP(6)
). In Java, the java.time
API does not specify the precision of fractional seconds. However, we can control the precision of the timestamp by formatting the timestamp appropriately before passing it to PostgreSQL. PostgreSQL will accept timestamps with fractional seconds (e.g., 2025-01-04 14:30:00.123456 for microseconds), and it will round or truncate the values based on the column precision.
7. Download
This was an example of a gradle project which stored the date and time values in a PostgreSQL database.
You can download the full source code of this example here: Storing Date and Time in PostgreSQL Example