23

I have an java.time.OffsetDateTime which I would like to convert to a java.sql.Timestamp. Since Timestamp doesn't store any offset information, I am going to store all dates/times in the database as UTC.

How do I convert the OffsetDateTime to a Timestamp which is in UTC?

EDIT:

I believe this is the answer but it seems are rather convoluted way to covert to UTC:

OffsetDateTime dateTime = OffsetDateTime.now();
Timestamp timestamp = Timestamp.valueOf(dateTime.atZoneSameInstant(ZoneId.of("Z")).toLocalDateTime());
Cheetah
  • 13,785
  • 31
  • 106
  • 190

4 Answers4

23

This would be a way to do the conversion and ensure UTC is used. That I think is a little cleaner than solution proposed using the epoch seconds.

Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());
Notso
  • 376
  • 3
  • 5
  • This is wrong. If `dateTime` contains `2015-10-23T12:44:43Z` and you are in timezone UTC+2 then `timestamp` will hold `2015-10-23 14:44:43.0` and is different from the result provided by the solution in the answer (`2015-10-23 12:44:43.0`). – rve Feb 16 '16 at 12:47
  • Yes, Timestamp.from(instant) does seem to convert it to your local time zone. Another way to do this and ensure it remains in UTC is 'Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());' I have edited the answer above reflect that. – Notso Feb 17 '16 at 14:05
  • 4
    To clarify the above comments for future reference: Previously my answer had suggested that Timestamp.from(entityValue.toInstant) would do it. However, while instant is a essentially a instant on the timeline from epoch, Timestamp.from(instant) returns it in your local time zone as @rve noted. So, a way to do this and ensure it remains in UTC is 'Timestamp test = Timestamp.valueOf(entityValue.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());' – Notso Feb 17 '16 at 14:25
  • Which is exactly what @Cheetah had written in his update to the question :-) So I guess there only is a convoluted way to do this conversion. – rve Feb 17 '16 at 15:37
  • 1
    When working with `java.util.Date` and `Time` and `Timestamp` always use system time zone *NOT UTC*. This makes it unusable to represent LocalDateTime but at least you get precise Instant. – Eugen Pechanec May 18 '17 at 14:34
  • I would agree that using the system time is more reliable. Ensure the system time is UTC for example. Otherwise, doing things like converting the time to UTC in the code can cause a lot of confusion when doing things like matching logging timestamps to the timestamps of your app's operations. – Notso May 19 '17 at 18:25
11

Another solution would be:

Timestamp.valueOf(LocalDateTime.ofInstant(dateTime.toInstant(), ZoneOffset.UTC));

It converts the dateTime to UTC, strips the timezone information and then converts the result to a Timestamp. It is still convoluted but IMHO it's a bit cleaner.

Just using toInstance() or toEpochSeconds() will adjust the result with the offset provided.

The following shows the test results from this and the other answers:

OffsetDateTime dateTime = 
    OffsetDateTime.of(2015, 10, 23, 12, 44, 43, 0, ZoneOffset.UTC);
    // OffsetDateTime.of(2015, 10, 23, 12, 44, 43, 0, ZoneOffset.ofHours(-5));

err.println("dateTime            = " 
    + dateTime
);

err.println("as LocalDateTime    = " 
    + dateTime.toLocalDateTime()
);

err.println("as timestamp (mine) = " 
    + Timestamp.valueOf(LocalDateTime.ofInstant(dateTime.toInstant(), ZoneOffset.UTC))
);

err.println("@Cheetah (correct)  = " 
    + Timestamp.valueOf(dateTime.atZoneSameInstant(ZoneId.of("Z"))
        .toLocalDateTime())
);

err.println("@Notso (wrong)      = " 
    + Timestamp.from(dateTime.toInstant())
);

err.println("@Glorfindel (wrong) = " 
    + new Timestamp(1000 * dateTime.toEpochSecond())
);

which gives the following results (my timezone is CET) :

(with ZoneOffset.UTC)
dateTime            = 2015-10-23T12:44:43Z
as LocalDateTime    = 2015-10-23T12:44:43
as timestamp (mine) = 2015-10-23 12:44:43.0
@Cheetah (correct)  = 2015-10-23 12:44:43.0
@Notso (wrong)      = 2015-10-23 14:44:43.0
@Glorfindel (wrong) = 2015-10-23 14:44:43.0

(with ZoneOffset.ofHours(-5))
dateTime            = 2015-10-23T12:44:43-05:00
as LocalDateTime    = 2015-10-23T12:44:43
as timestamp (mine) = 2015-10-23 17:44:43.0
@Cheetah (correct)  = 2015-10-23 17:44:43.0
@Notso (wrong)      = 2015-10-23 19:44:43.0
@Glorfindel (wrong) = 2015-10-23 19:44:43.0

(The version from Notso above was before his edit of Feb 17 2016)

rve
  • 5,897
  • 3
  • 40
  • 64
6

Use .toEpochSecond() to get the # of seconds from the reference date (which is in UTC), multiply by 1000 and pass this to the Timestamp constructor (as it expects milliseconds).

new Timestamp(1000 * offsetDateTime.toEpochSecond());
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
6

I am providing the modern answer.

java.time and JDBC 4.2

You should avoid the Timestamp class. It’s poorly designed and very confusing, a true hack on top of the already poorly designed java.util.Date class. The fact that the other answers lead to different results as documented by the comparisons in the answer by rve in my opinion illustrates the confusion very well. You are already using OffsetDateTime from java.time, the modern Java date and time API, and provided that you have got a JDBC 4.2 compliant JDBC driver, you can and should stick to the classes from java.time.

Best to store as timestamp with time zone

Storing dates and times in UTC in the database as you say you want is a good and recommended practice. If you can, change the datatype in the database to timestamp with time zone. While this doesn’t store a time zone (despite the name), it makes sure that the database too “knows” that timestamps are in UTC, which already prevents many mistakes. Next advantage is that (provided that I have understood correctly) you can store your OffsetDateTime directly and let the conversion to UTC happen automatically.

    OffsetDateTime odt = OffsetDateTime.of(
            2015, 6, 4, 19, 15, 43, 210987000, ZoneOffset.ofHours(1));
    PreparedStatement stmt = yourDbConnection.prepareStatement(
            "insert into your_table (your_timestamp_with_time_zone) values (?);");
    stmt.setObject(1, odt);
    stmt.executeUpdate();

If you want to make it clearer in your Java code that the time is stored in UTC, convert explicitly first:

    odt = odt.withOffsetSameInstant(ZoneOffset.UTC);

If your database stores timestamp without time zone

If the datatype in your database is a mere timestamp (without time zone) (not recommended), the type to use on the Java side is LocalDateTime. I’d do the conversion to UTC like this:

    LocalDateTime ldt = odt.withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime();
    System.out.println("UTC datetime        = " + ldt);

Output is:

UTC datetime = 2015-06-04T18:15:43.210987

Storing into the database is similar to before:

    PreparedStatement stmt = yourDbConnection.prepareStatement(
            "insert into your_table (your_timestamp) values (?);");
    stmt.setObject(1, ldt);
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 2
    When you say: "If the datatype in your database is a mere timestamp (without time zone) (not recommended), the type to use on the Java side is LocalDateTime.", it's not completely true. If you set the property: hibernate.jdbc.time_zone=UTC, Hibernate will treat all your Timestamp columns as UTC and you can use the OffsetDateTime in your code and Hibernate will do the conversion for you. – Felipe Desiderati May 21 '20 at 01:49
  • 1
    Well, @FelipeDesiderati, I wasn't using any Hibernate in the answer. In any case thanks for the interesting information, it'll be useful, probably to many. – Ole V.V. May 21 '20 at 03:04