How to specify UTC timezone for Spring Boot JPA Timestamp - java

Environment
Spring Boot Starter Data JPA 1.4.2
Eclipselink 2.5.0
Postgresql 9.4.1211.jre7
Problem
I am building a Spring Boot microservice that shares a Postgresql database with a different service. The database gets initialized externally (out of our control) and the datetime column type used by the other service is timestamp without time zone. Therefore, since I want all dates on the db to have the same type, having that type is a requirement for my JPA entity dates.
The way I map them on my JPA entity objects is as follows:
#Column(name = "some_date", nullable = false)
private Timestamp someDate;
The problem is that when I create a Timestamp as follows:
new java.sql.Timestamp(System.currentTimeMillis())
and I look at the database, the timestamp contains my local timezone date time, but I want to store it in UTC. This is because my default Timezone is set to 'Europe/Brussels' and JPA/JDBC converts my java.sql.Timestamp object into my timezone before putting it into the database.
Found not ideal solutions
TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); has the effect that I want to achieve, but it's not suitable because it is not specific to my service. I.e. it will affect the whole JVM or the current thread plus children.
Starting the application with -Duser.timezone=GMT seems to also do the job for a single instance of a running JVM. Therefore, a better solution than the previous one.
But is there a way to specify the timezone within the JPA/datasource/spring boot configuration?

The most appropriate workaround I could figure out for the problem is to use an AttributeConverter to convert Java 8 ZonedDateTime objects into java.sql.Timestamp objects, so that they can be mapped to the PostgreSQL timestamp without time zone type.
The reason why you need an AttributeConverter is because the Java 8/Joda time date time types are not yet compatible with JPA.
The AttributeConverter looks like this:
#Converter(autoApply = true)
public class ZonedDateTimeAttributeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
return (zonedDateTime == null ? null : Timestamp.valueOf(zonedDateTime.toLocalDateTime()));
}
#Override
public ZonedDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime().atZone(ZoneId.of("UTC")));
}
}
This allows me to read the database timestamps that do not have timezone information as ZonedDateTime objects that have the UTC timezone. This way I keep the exact date time that can be seen on the db regardless of the timezone that my app runs in.
Since toLocalDateTime() also applies the system's default timezone conversion, this AttributeConverter basically cancels out the conversion applied by the JDBC driver.
Do you really need to use timestamp without timezone?
The reality is that if you are storing date time information on a time zone (even if it is UTC), the timestamp without timezone PostgreSQL type is the wrong choice. The right data type to use would be timestamp with timezone which does include the timezone information. More on this topic here.
However, if for whatever reason, you must use the timestamp without timezone, I think the ZonedDateTime approach above is a robust and consistent solution.
Are you also serializing the ZonedDateTime to JSON?
Then you are probably interested in the fact that you need at least version 2.6.0 of the jackson-datatype-jsr310 dependency for the serialization to work. More on that in this answer.

You can't. Eclipselink uses the single arg version of setTimestamp, delegating responsibility for time zone handling to the driver, and the postgresql jdbc driver doesn't allow the default time zone to be overridden. The postgres driver even propagates the client time zone to the session, so server side defaults will be of no use to you either.
There are some hackish things you could try to work around the problem, for example writing a JPA 2.1 AttributeConverter to shift your timestamps into the destination zone, but ultimately they're doomed because your client time zone has daylight savings adjustments, making some times ambiguous or unrepresentable.
You'll have to set the default time zone on your client, or drop into native SQL to set timestamps as strings with casts.

What about using Instant, instead of LocalDateTime? I think it may convert entity value to timestamp, in UTC
#Converter(autoApply = true)
public class ZonedDateTimeAttributeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
return (zonedDateTime == null ? null : Timestamp.from(zonedDateTime.toInstant()));
}
#Override
public ZonedDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
return (sqlTimestamp == null ? null : sqlTimestamp.toInstant().atZone(ZoneId.of("UTC")));
}
}

Related

Hibernate retrieve timestamps in UTC

I am using hibernate + spring and want to store/load timestamps in UTC. I've read that I should add a property, so I added this to my application.properties
spring.jpa.properties[hibernate.jdbc.time_zone]=UTC
This worked for one part of the problem - now dates are saved in utc in the database. But when I retrieve timestamps, they are transformed into default timezone. How can I fix this without setting default time zone to UTC?
The property of the entity has type LocalDateTime.
I ran the code, and noticed that the proper result set method is used during get(the one that accepts calendar) with instance that has zone info storing UTC. But after setting calendar's values to the one retrieved from the database, the calendar is transformed into Timestamp with this code
Timestamp ts = new Timestamp(c.getTimeInMillis());
In debug mode, I see that ts stores cdate field with value of timestamp in default time zone(not UTC).
First of all, if we are talking about Hibernate 5 (5.2.3 - 5.6.x if to be precise) the purpose of hibernate.jdbc.time_zone setting is not to give the ability for application developer to implement some kind of sophisticated date/time logic, but to synchronize persistence provider with underlying database, that is clearly stated in the corresponding CR:
Currently my database has implicit date times in UTC. No zoned data is appended to the end of the string (e.g. "2013-10-14 04:00:00").
When Hibernate reads this as a ZonedDateTime, it incorrectly reads it in as EST, as that is the TimeZone of the JVM.
It would be nice to be able to specify the TimeZone of a field by an annotation perhaps.
basically: you definitely need to set up hibernate.jdbc.time_zone if (mine: and only if) SQL statement like SELECT SYSDATE FROM DUAL (SELECT LOCALTIMESTAMP for PostgreSQL, etc) returns something, what you do not expect, in that case Hibernate will start adjusting non-timezone-aware JDBC data to something more or less reliable for application - that is exactly what you are observing (when I retrieve timestamps, they are transformed into default timezone)
At second, any speculations around JSR-310 and JDBC 4.2 (like for timezone-aware java types you need to define DB columns as timestamp with time zone), are not correct in case of Hibernate 5, that is mentioned in the corresponding CR as well:
The whole idea of "stored TZ" really depends on how the database/driver treats TIMESTAMP and whether it supports a "TIMESTAMP_WITH_TIMEZONE" type. I personally think it is a huge mistake to save the specific TZ differences to the DB, so I would personally continue to not support TIMESTAMP_WITH_TIMEZONE types. This would mean we never have to bind the Calendar because we could simply convert the value to to the JVM/JDBC TZ ourselves. Specifically I would suggest that we (continue to) assume that the driver has been set up such that the same TZ is used when ...
And indeed, if you try to find usage of java.sql.Types#TIMESTAMP_WITH_TIMEZONE in Hibernate 5 sources you will find nothing, just because that time Hibernate developers didn't get a common opinion about how timezone conversions should work in cases of different Java versions, Java types, DB engines and JDBC drivers (they are developing the most popular (mine: the only one) JPA implementation, that is definitely not the same as develop microservice), however, there are a lot of related changes in Hibernate 6 (check TimeZoneStorageType for example). In Hibernate 5 all timezone conversion logic passes through TimestampTypeDescriptor:
#Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( rs.getTimestamp( name ), options );
}
and as you can see, Hibernate 5 just gives a hint to JDBC driver, how the last should process #getTimestamp call:
Retrieves the value of a JDBC TIMESTAMP parameter as a java.sql.Timestamp object, using the given Calendar object to construct the Timestamp object. With a Calendar object, the driver can calculate the timestamp taking into account a custom timezone and locale. If no Calendar object is specified, the driver uses the default timezone and locale.
in regard to your case:
you either need to use timezone-aware java types (ZonedDateTime/OffsetDateTime, or even Instant) or code your own Hibernate type, which will handle timezone conversions - that is not so hard as it might seem.
we can also set it up` per-session basis:
session = HibernateUtil.getSessionFactory().withOptions()
.jdbcTimeZone(TimeZone.getTimeZone("UTC"))
.openSession();
My database timezone was in UTC and in my application timezone I solved this problem by having both the Entity and the table have a date in UTC so that there will need to be no conversion between them. Then I did the conversions between timestamps in code in the getters and setters. Then I think you do it manually.
Setter and getter for that field:
public void setCreatedDate(LocalDateTime createdAt)
{
this.createdAt = createdAt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
public LocalDateTime getCreatedDate()
{
return createdAt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
As alluded to be Andrey in his answer, in Hibernate 6 the way to normalize dates/times to UTC is to use the java.time types OffsetDateTime or ZonedDateTime, and either:
annotate the field #TimeZoneStorage(NORMALIZE_UTC), or
set the property hibernate.timezone.default_storage=NORMALIZE_UTC.
I'm not very certain what you mean about using LocalDateTime here. What would it even mean to normalize a local datetime to UTC? That statement just doesn't really make sense: you can't move a local datetime to a new time zone because it doesn't have an associated time zone to begin with.
I think what you mean is that your "local" date times are actually zoned datetimes in the current JVM time zone. But if that's the case, it's very easy to use localDateTime.atZone(ZoneId.systemDefault()) to represent that situation correctly.

JPA Instant/ZonedDateTime Database mapping

I came upon a problem with java.time classes and their mapping to DB types. I want to store Instant type, but it behaves quite unintuitively.
Simple project example here: https://gitlab.com/Gobanit/jpa-time-example
I have basic entity class:
#Entity
#Getter
#Setter
public class AbstractEntity {
#Id
#GeneratedValue
private Long id;
// #Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")
private Instant createdAt;
// #Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")
private ZonedDateTime createdAtZoned;
private LocalDateTime createdAtLocal;
private Instant modifiedAt;
#PrePersist
protected void prePersist() {
createdAt = Instant.now();
modifiedAt = createdAt;
createdAtZoned = createdAt.atZone(ZoneId.systemDefault());
createdAtLocal = createdAtZoned.toLocalDateTime();
System.out.println(String.format("Created: instant=%s, zoned=%s, local=%s", createdAt, createdAtZoned, createdAtLocal));
}
#PreUpdate
protected void preUpdate() {
modifiedAt = Instant.now();
}
}
Now, I will create and persist the entity:
#Transactional
#GetMapping("create")
public AbstractEntity createNew() {
System.out.println("create");
System.out.println("ZoneId=" + ZoneId.systemDefault());
var e = new AbstractEntity();
em.persist(e);
return e;
}
The data in Java are as expected - system output below:
ZoneId=Europe/Bratislava
Created: instant=2021-12-29T14:13:55.624902400Z, zoned=2021-12-29T15:13:55.624902400+01:00[Europe/Bratislava], local=2021-12-29T15:13:55.624902400
However, the data in DB are not. They are all stored as the same value. The default type of the column is set to TIMESTAMP for all of them. And they are all stored as if they were LocalDateTime in application JVM timezone.
I dont like this at all, there is no difference between these types and the "meaning" of the value is dependent on the application zone. If I look at the database without knowing which application wrote the data and what is its zone, i cannot say, what the datetime means. Even worse, if I run the application with different zone, it would assume that the values were written in its timezone, which they were not.
Some people say, the time data should be stored in UTC (which i partially agree) and they recommend setting following property, so that the values are converted to UTC zone before storing in DB by JDBC:
spring.jpa.properties.hibernate.jdbc.time_zone: UTC
However, this results in even worse behavior, since it applies not only to Instant/ZonedDateTime but also to LocalDateTime, which seems logically wrong to me. LocalDateTime value should not be affected by any timezone conversions at all, that is why it is LocalDateTime.
System Output:
ZoneId=Europe/Bratislava
Created: instant=2021-12-29T14:15:13.614799900Z, zoned=2021-12-29T15:15:13.614799900+01:00[Europe/Bratislava], local=2021-12-29T15:15:13.614799900
DB Result:
I would expect one of these to happen:
Mapping Instant and ZonedDateTime to TIMESTAMP_WITH_TIMEZONE by default. Also, Instant type should be always serialized with UTC zone - just as when printing to string or serializing with jackson. (ideal)
Mapping Instant and ZonedDateTime to TIMESTAMP but convert them to UTC when serializing, and back to JVM timezone when deserializing. (not ideal, but good enough)
I can override the default mapping explicitly, by uncommenting the line below.
// #Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")
That works fine, only Instant are not converted to UTC but take the JVM zone, just like ZonedDateTime. Technically it is totally fine, i cant see any inconsistency result happen because of that, but personally i would prefer them in UTC, so that they are always unified.
The second problem with this, is that i need to explicitly configure it for each field.
ZoneId=Europe/Bratislava
Created: instant=2021-12-29T14:19:14.600583400Z, zoned=2021-12-29T15:19:14.600583400+01:00[Europe/Bratislava], local=2021-12-29T15:19:14.600583400
In the end, I have few questions, I would really like someone to answer me :)
**
Is there any way to get the ideal solution (the first mentioned)?
Is there any way to at least globally configure the type mapping?
Can you see any risks in this approach?
Is there a better or more standardized approach, that I am not aware of? What is your strategy for working with time?
Thanks!
I cannot speak to JPA, but I can explain some of the JDBC issues.
I want to store Instant type
The JDBC specification does not map Instant.
For a column of a type akin to the SQL standard type TIMESTAMP WITHOUT TIME ZONE, use the Java class LocalDateTime.
For a column of a type akin to the SQL standard type TIMESTAMP WITH TIME ZONE, use the Java class OffsetDateTime.
In JDBC, use setObject & getObject to exchange data-time values as java.time values in JDBC 4.2 and later.
myPreparedStatement.setObject( … , myOffsetDateTime ) ;
Some databases such as Postgres automatically adjust the incoming data to UTC, an offset of zero hours-minutes-seconds. For portable code across databases, you may want to make that adjustment in Java code.
OffsetDateTime myOdt = otherOdt.withOffsetSameInstant( ZoneOffset.UTC ) ;
Capture the current moment as a OffsetDateTime.
OffsetDateTime myOdt = OffsetDateTime.now( ZoneOffset.UTC ) ;
If you have an Instant in hand, convert to OffsetDateTime for storage in a database via JDBC.
OffsetDateTime myOdt = myInstant.atOffset( ZoneOffset.UTC ) ;
There is no need for you to store the zoned or unzoned values in your database. You can easily adjust to time zones after database retrieval, in your Java code, as you would do for other localization needs.
ZoneId z = ZoneId.of( "America/Edmonton" ) ;
ZonedDateTime zdt = myOdt.atZoneSameInstant( z ) ;
I see no benefit generally to using LocalDateTime when dealing with moments, specific points on the timeline. That class purposely lacks the context of a time zone or offset. Such objects contain a date with time of day only, so are inherently ambiguous. You can convert from OffsetDateTime to LocalDateTime but would be stripping away vital information while gaining nothing in return.
they are all stored as if they were LocalDateTime in application JVM timezone.
That is a contradiction in terms. A LocalDateTime has no time zone or offset.
The default type of the column is set to TIMESTAMP for all of them.
There is no default for the data type of a column. You specify the data type when creating the column. The data type does not change while inserting rows. The data type can only change if you issue an ALTER TABLE command.
Also, we have no idea what type you mean with the word TIMESTAMP since you neglected to mention your specific database. As mentioned above, the SQL standard uses four words for each of the two very different timestamp types.
Mapping Instant and ZonedDateTime to TIMESTAMP but convert them to UTC when serializing,
No, JDBC does not require any such mapping of Instant nor ZonedDateTime.
You need to do such conversions in your Java code, outside of the JDBC calls.
The exception is that your particular JDBC driver may choose to go beyond what is required by the JDBC spec. A driver is free to handle the Instant and ZonedDateTime types if it’s creators saw fit. But beware that any such code you wrote may not be portable across other JDBC drivers.
and back to JVM timezone when deserializing.
I believe you’ll find this work much easier if, as a programmer/DBA/SysAdmin, you do most of your thinking, logging, debugging, data storage, and data exchange, in UTC (offset of zero).
Converting to Instant is a simple way of adjusting to UTC.
Instant instant = odt.toInstant() ;
Instant instant = zdt.toInstant() ;
Tip: Generally write your code to not depend on the default time zone of your JVM, your database session, or host OS. Those defaults are not under your control as a programmer. And those defaults can change at any moment during runtime. So use a default only where called for, such as localizing to default zone of a user’s mobile device for presentation in the user-interface. And make your use of such a default explicit, always specifying the otherwise optional zone/offset arguments to various calls.
Tip: Recording the moment a row is inserted or updated is generally best left to the database. Use a trigger where the database server captures the current moment and assigns it to the field. Then you are covered for operations outside your Java app, such as bulk data loads.

Hibernate Ignores DB Timezone Info when Binding to Java Timestamp Object

I have some Dates stored in Oracle with Oracle's TIMESTAMP(3) as its datatype. Now I'm writing a Spring boot app to read those values back. The code is like:
HibernateCallback callback = new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
Query query = session.createSQLQuery("SELECT date_field FROM some_table WHERE some_conditions");
return query.list();
}
};
So:
List results = (List)getHibernateTemplate().execute(callback);
// suppose there's only one row and one column returned
Timestamp ts = result.get(0)[0];
returns me the Java Timestamp object automatically created by Hibernate. The problem is that, when constructing the object, it ignores the timezone stored in Oracle, but instead uses JVM's default timezone. I tested is by setting different timezones for the JVM, and each time it generates a different timestamp.
It's obviously wrong. The Date should be unique on the time line. It shouldn't depend on JVM's timezone. I'm wondering what's the correct way to include the DB's timezone info when parsing the date. Right now it seems it's just using the String representation stored in Oracle and parse it with JMV's timezone. I'm using Hibernate 4.3.4.Final.
PS: The actual query is high customized so I have to write raw SQL.
Basically, that's not even an issue with Hibernate but with JDBC. By default, JDBC Driver will use system time zone on which JVM is running. If you are connecting to the DB server at a different time zone or even if you want to be independent of the current timezone of the system it is a good idea to set JDBC Time Zone explicitly.
You can use hibernate.jdbc.time_zone property to set the timezone or do it at runtime via.
session = sessionFactory.withOptions()
.jdbcTimeZone(TimeZone.getTimeZone("UTC"))
.openSession();
Also for Oracle, I would say you can use the TIMESTAMP WITH LOCAL TIME ZONE which will respect you JDBC client time zone.
The problem is with Oracle column data type you are using, If you go to official oracle docs, link, you will notice the TIMESTAMP doesn't respect timezone, so you should go with with either TIMESTAMP WITH TIME ZONE or TIMESTAMP WITH LOCAL TIME ZONE.
It seems the issue is with few concepts of date handling
Dates in DB should be in UTC, if they are to be used in multiple timezone.
Java program should convert the dates to required timezone.
If you take your date in database as based on UTC, and then check the output of your hibernate query, you should see that date is changed as per JVM's timezone.
If you want the same date back (as was in DB), maybe you should convert the date to UTC timezone.
Now, you can do either of following:
Set the JVM's timezone same as the database record timezone. Not recommended
Change your dates in database as per UTC, and let the dates be changed to JVM's timezone. Recommended

Copying TIMESTAMP to DATETIME on MySQL with Hibernate

I have these two classes
class Source {
// mapped to TIMESTAMP
#Version
#Column(columnDefinition="TIMESTAMP(3) DEFAULT '2016-01-01'")
Instant myInstant;
}
class Destination {
// mapped to DATETIME
#Basic(optional=true)
Instant myInstant;
}
When using Hibernate, I assign
destination.myInstant = source.myInstant;
and then the stored value is smaller by one hour than the original - both according to the command line MySQL client and in Java. My current timezone is UTC+1, so the reason is obviously a timezone conversion.
There are a few places where this can be fixed, but I'm looking for the best practice. The server should work world-wide, so it should continue to use UTC internally, right?
Should I just change the column type to TIMESTAMP? Then, why does Instant by default map to DATETIME?
According to this article, Instant does map to TIMESTAMP, but in my case it did not. Why?
If you want to work with timezones and Java 8 I would recommend using ZonedDateTime or OffsetTimeZone (the latter being prefered when working with Hibernate). For older versions use Calendar.
When you instance it should go by default with the timezone of your computer.
Check if the database is timestamp with or without timezone.
The default you set is also without timezone, and if it is "with timezone" it should automatically add the database's offset.
I hope some of this works. Here's how I did in one of my projects.
#Column(name = "registration_time")
private OffsetDateTime registrationTime;
[...]
subscriber.setRegistrationTime(OffsetDateTime.now());
In MySQL 5 & above, TIMESTAMP values are converted from the current time zone to UTC for storage, and converted back from UTC to the current time zone for retrieval. This occurs only for the TIMESTAMP data type but not for DATETIME. This is the reason you are seeing the difference while assigning a TIMESTAMP to DATETIME. So, having both the columns of same type should work. Hibernate by default maps InstantType to database TIMESTAMP type. Though you could use it for both TIMESTAMP and DATETIME in MYSQL, they are handled differently.

What is the most recommended way to store time in PostgreSQL using Java?

I'm storing two dates in the PostgreSQL database. First, is the data of visit of a webpage, and the second date is the date of last modification of the webpage(this is get as a long).
I have some doubts what is the best strategy to store these values.
I only need day/month/year and hour:seconds and this will only for statistical proposes.
So, some doubts:
is best store as long and convert on recover of information or store in the data format above?
is best set the date of visit on the software or in the insertion in the database?
in Java, how are the best classes to handle dates?
Any strategy for storing date-and-time data in PostgreSQL should, IMO, rely on these two points:
Your solution should never depend on the server or client timezone setting.
Currently, PostgreSQL (as most databases) doesn't have a datatype to store a full date-and-time with timezone. So, you need to decide between an Instant or a LocalDateTime datatype.
My recipe follows.
If you want to record the physical instant at when a particular event ocurred, (a true "timestamp" , typically some creation/modification/deletion event), then use:
Java: Instant (Java 8 , or Jodatime).
JDBC: java.sql.Timestamp
PostgreSQL: TIMESTAMP WITH TIMEZONE (TIMESTAMPTZ)
(Don't let PostgreSQL peculiar datatypes WITH TIMEZONE/WITHOUT TIMEZONE confuse you: none of them actually stores a timezone)
Some boilerplate code: the following assumes that ps is a PreparedStatement, rs a ResultSet and tzUTC is a static Calendar object corresponding to UTC timezone.
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Write Instant to database TIMESTAMPTZ:
Instant instant = ...;
Timestamp ts = instant != null ? Timestamp.from(instant) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
Read Instant from database TIMESTAMPTZ:
Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? ts.toInstant() : null;
This works safely if your PG type is TIMESTAMPTZ (In that case, the calendarUTC has no effect in that code ; but it's always advisable to not depend on defaults timezones).
"Safely" means that the result will not depend on server or database timezone, or timezones information: the operation is fully reversible, and whatever happens to timezones settings, you'll always get the same "instant of time" you originally had on the Java side.
If, instead of a timestamp (an instant on the physical timeline), you are dealing with a "civil" local date-time (that is, the set of fields {year-month-day hour:min:sec(:msecs)}), you'd use:
Java: LocalDateTime (Java 8 , or Jodatime).
JDBC: java.sql.Timestamp
PostgreSQL: TIMESTAMP WITHOUT TIMEZONE (TIMESTAMP)
Read LocalDateTime from database TIMESTAMP:
Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null )
localDt = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
Write LocalDateTime to database TIMESTAMP:
Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
Again, this strategy is safe and you can sleep peacefully: if you stored 2011-10-30 23:59:30 , you'll retrieve those precise fields (hour=23, minute=59... etc) always, no matter what - even if tomorrow the timezone of your Postgresql server (or client) changes, or your JVM or your OS timezone, or if your country modifies its DST rules, etc.
Added: If you want (it seems a natural requirement) to store the full datetime specification (a ZonedDatetime: the timestamp together with the timezone, which implicitly also includes the full civil datetime info - plus the timezone)... then I have bad news for you: PostgreSQL hasn't a datatype for this (neither other databases, to my knowledge). You must devise your own storage, perhaps in a pair of fields: could be the two above types (highly redundant, though efficient for retrieval and calculation), or one of them plus the time offset (you lose the timezone info, some calculations become difficult, and some impossible), or one of them plus the timezone (as string; some calculations can be extremely costly).
java.time
It's not pretty but this is what worked for me with a ZonedDateTime instance using the new java.time framework in Java 8 and later (Tutorial):
ZonedDateTime receivedTimestamp = some_ts_value;
Timestamp ts = new Timestamp(receivedTimestamp.toInstant().toEpochMilli());
ps.setTimestamp(
1,
ts,
Calendar.getInstance(TimeZone.getTimeZone(receivedTimestamp.getZone()))
);
I had used java.util.Date in our code to store Date in arguments of my query. I was using Spring JPA and using #Query annotations. The implementation worked.

Categories