tl;dr
You seem to be working much too hard.
You are using the wrong classes. Use modern java.time classes instead.
myPreparedStatement.setObject(
… ,
Instant.ofEpochMilli( 1_528_371_000_000L )
)
You are using the wrong data type in your database. The TIMEZONE WITHOUT TIME ZONE
type cannot be used to store a specific moment.
Avoid setting default time zone
TimeZone.setDefault
This call immediately affects all code in all threads of all apps within the JVM! So make this call only in the most desperate of circumstances.
Instead, pass the desired/expected time zone as an optional ZoneId
argument to many of the java.time methods.
The default time zone of your host OS and your JVM should both be irrelevant to your code. Specify explicitly your desired/expected time zone as a ZoneId
(or ZoneOffset
) object.
Avoid legacy date-time classes.
The old date-time classes bundled with the earliest versions of Java are terrible. This includes the GregorianCalendar
class seen in your code. Instead, use their replacement, the modern java.time classes.
Specifically, GregorianCalendar
is replaced by ZonedDateTime
.
Instant
The Instant
class in java.time is the basic building block. The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instant = Instant.now() ; // Capture the current moment in UTC.
Apparently you have a count of milliseconds since the epoch reference of first moment of 1970 UTC, 1970-01-01T00:00:00Z. Simply parse that number as a Instant
object by calling ofEpochMilli
.
long input = 1_528_371_000_000L ;
Instant instant = Instant.ofEpochMilli( input ) ;
instant.toString(): 2018-06-07T11:30:00Z
Time zones
You may want to present the Instant
to the user in a particular time zone. Apply a ZoneId
to get a ZonedDateTime
.
Specify a proper time zone name in the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter pseudo-zones such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ; // Or Europe/Paris, Africa/Tunis, etc.
Database
I do not know what you are doing with XMLGregorianCalendar
. And I cannot address Hibernate as I am not a user, though I know Hibernate supports java.time types.
But if you trying to record and retrieve a moment with Postgres, you are working much too hard.
Storing a moment in UTC into a column of type TIMESTAMP WTH TIME ZONE
(not WITHOUT
! — see below).
String sql = "INSERT INTO tbl ( event ) VALUES ( ? ) ;" ; // Writing a moment into a column of type `TIMESTAMP WTH TIME ZONE`.
…
Instant instant = Instant.ofEpochMilli( 1_528_371_000_000L ) ; // Representing a moment in UTC.
myPreparedStatement.setObject( 1 , instant ) ; // As of JDBC 4.2 and later, we can directly exchange java.time objects with our database.
Retrieval.
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Be aware that the SQL standard barely touches on the topic of date-time. Different databases behave very differently from one another in their handling of date-time.
The way Postgres works is that TIMESTAMP WITH TIME ZONE
is something of a misnomer. The time zone is not stored as part of the timestamp. Any supplied time zone of offset-from-UTC information submitted with input is used to adjust into UTC. The UTC value is written to the database. The zone/offset is then discarded/forgotten. If remembering the original zone/offset of the input is important to you, you must store it separately in another column.
When retrieving a TIMESTAMP WITH TIME ZONE
value from Postgres, you are always getting a value in UTC. This may not be apparent to you if your middleware tool connecting you (your app) with the database injects its own opinion as to a desirable time zone for presentation.
Avoiding this time zone injection confusion is easy with Java: Pass Instant
objects, and retrieve Instant
objects. An Instant
is always in UTC. So calling ResultSet::getObject( … , Instant.class)
will always represent faithfully the database’s TIMESTAMP WITH TIME ZONE
value.
One tricky point is resolution of the fractional second. The java.time classes use nanoseconds resolution while Postgres uses microseconds. So you may want to truncate your Instant
explicitly in your code to acknowledge this fact. Your JDBC driver will truncate for you, but I dislike such behind-the-scenes manipulation of data.
Instant instant = Instant.now().truncatedTo( ChronoUnit.MILLISECONDS ) ; // Lop off any nanoseconds to match Postgres storing microseconds.
Wrong data type in your column
The type of startTime is timestamp without timezone in postgres.
The TIMESTAMP WITHOUT TIME ZONE
is the wrong data type to store a moment. This type lacks any concept of time zone or offset-from-UTC. So it cannot represent a specific moment. It represents the set of potential moments along a range of about 26-27 hours (the range of all time zones).
Instead, you should be using the TIMESTAMP WITH TIME ZONE
type to store a a moment, a specific point on the timeline.
So when we save then it again converts it in UTC.
Nope, not at all. Just the opposite. Any included time zone or offset-from-UTC information is ignored. The date and time-of-day are take as-is, with no accounting for time zone, no adjusting to UTC.
The SQL-standard type TIMEZONE WITHOUT TIME ZONE
is equivalent to the java.time type LocalDateTime
.
You should use these zone-less types only in these three situations:
- The zone or offset is unknown.
This is bad. This is faulty data. Analogous to having a price/cost without knowing the currency. You should be rejecting such data, not storing it.
- The intention is “everywhere”, as in, every time zone.
Example, a corporate policy that states “All our factories will break for lunch at 12:30" means the factory in Delhi will break hours before the factory in Düsseldorf which breaks hours before the factory in Detroit.
- A specific moment in the future is intended, but we are afraid of politicians redefining the time zone.
Governments change the rules of their time zones with surprising frequency and with surprisingly little warning or even no warning at all. So if you want to book an appointment at 3 PM on a certain date, and you really mean 3 PM regardless of any crazy decision a government might make in the interim, then store a LocalDateTime
. To print a report or display a calendar, dynamically apply a time zone (ZoneId
) to generate a specific moment (ZonedDateTime
or Instant
). This must be done on-the-fly rather than storing the value.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.*
classes.
Where to obtain the java.time classes?
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.