I search for a way to get a list of Objects created on a certain LocalDateTime date saved in the Postgresql database in a field of type TIMESTAMPTZ.
To do so, I tried to
Your current solution is fine.
But in @Query
you can cast column using DATE()
@Query(value = "SELECT * FROM Entity u WHERE DATE(creation_date) = ?1", nativeQuery = true)
List<Entity> findByCreationDate(LocalDate date);
I do not use JPA, Hibernate, or Spring, so I must ignore that aspect of your Question. I am using straight JDBC instead.
You seem to be ignoring the crucial issue of time zone. For any given moment, the date varies around the globe by time zone. At some moment, it may be "tomorrow" in Tokyo Japan while still "yesterday" in Toledo Ohio US.
So you must have a time zone in mind when you specify the range of a date from start of day to start of the following day.
You said:
Postgresql database in a field of type TIMESTAMPTZ
The type name TIMESTAMPTZ
in Postgres is a non-standard abbreviation for the standard type name TIMESTAMP WITH TIME ZONE
.
Postgres stores all submitted data in a column of that type in UTC, with an offset of zero hours-minutes-seconds from UTC. Any offset or time zone accompanying submitted inputs is used to first adjust the value to UTC for storage. After storage, any submitted zone/offset info is discarded. Postgres stores all TIMESTAMP WITH TIME ZONE
values in UTC.
So if you want to query for values that lay within the duration of a day, you must first define a day in your desired time zone, then adjust the start and end of that day into UTC values.
String input = "2020-12-12" ;
LocalDate ld = LocalDate.parse( input ) ;
Determine the first moment as seen in the time zone of your choice. Always let java.time determine first moment of the day, never assume a day starts at 00:00:00. Some days on some dates in some zones start at other times such as 01:00:00.
Usually best in date-time handling to define a span-of-time using Half-Open approach. The beginning is inclusive, while the ending is exclusive. This allows spans to neatly abut one another.
So your SQL query does not use BETWEEN
. Your SQL will look something like the following. Note that a shorter way to say "is equal to or later than" is "is not before" (!<
in SQL).
SELECT *
FROM event_
WHERE when_ !< ?
AND when_ < ?
;
Your Java looks like this.
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtStart = ld.atStartOfDay( z ) ;
ZonedDateTime zdtEnd = ld.plusDays( 1 ).atStartOfDay( z ) ;
2020-12-12T00:00+13:00[Pacific/Auckland]/2020-12-13T00:00+13:00[Pacific/Auckland]
Adjust to UTC by extracting Instant
objects. Instant
objects are always in UTC, by definition.
Instant start = zdtStart.toInstant() ;
Instant end = zdtEnd.toInstant() ;
2020-12-11T11:00:00Z/2020-12-12T11:00:00Z
See this code run live at IdeOne.com.
And your JDBC looks like this:
myPreparedStatement.setObject( 1 , start ) ;
myPreparedStatement.setObject( 2 , end ) ;
In that JDBC code, we are passing Instant
objects. That may or may not work, depending on your JDBC driver. Oddly, the JDBC 4.2 spec requires support for OffsetDateTime
but not the more commonly used Instant
or ZonedDateTime
types. No matter, we can easily convert.
myPreparedStatement.setObject( 1 , start.atOffset( ZoneOffset.UTC ) ) ;
myPreparedStatement.setObject( 2 , end.atOffset( ZoneOffset.UTC ) ) ;
And we could have gone from ZonedDateTime
to OffsetDateTime
by calling ZonedDateTime::toOffsetDateTime
. But then the offset of those objects would not be UTC (zero). Your JDBC driver and database should be able to handle that. But out of an abundance of caution, and for the sake of debugging and logging, I would use UTC as seen above. But FYI, the code:
myPreparedStatement.setObject( 1 , zdtStart.toOffsetDateTime() ) ;
myPreparedStatement.setObject( 2 , zdtEnd.toOffsetDateTime() ) ;
LocalDateTime
for momentsNever use LocalDateTime
when dealing with moments, with specific points on the timeline. Lacking the context of a zone/offset, a LocalDateTime
cannot represent a moment.
Using an answer that was quickly deleted after it was posted (I had to use the column and not the property name, mark the query as nativeQuery and also select with '.*' to avoid org.postgresql.util.PSQLException: The column name id was not found in this ResultSet
error), I found this solution:
@Query(value = "SELECT e.* FROM Entity e WHERE DATE(creation_date) =:date", nativeQuery = true)
List<Entity> findByCreationDate(LocalDate date);
Alternatively, following this answer, I tested with success a second solution:
default List<Entity> findByCreationDate(LocalDate localDate) {
return findByPublicationDateBetween(localDate.atStartOfDay(), localDate.plusDays(1).atStartOfDay());
}
List<Entity> findByCreationDateBetween(LocalDateTime from, LocalDateTime to);