2

I'm editing a piece of code which basically does:

timestamp = new Date();

And then persist that timestamp variable in a TIMESTAMP MySQL table column.
However, while via debug I see the Date object displayed in the correct time-zone, GMT+1, when persisted on database it is a GMT time-zone, so an hour back.

Using the function CURRENT_TIMESTAMP returns a GMT+1 date.
The connection string is jdbc:mysql://..../db-name, without any parameter.

EDIT: Found this piece of code

preparedStatement.setTimestamp(2, new Timestamp(timestamp.getTime()));

What's happening?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
LppEdd
  • 20,274
  • 11
  • 84
  • 139
  • 1
    You may want to use the MySQL `DATETIME` type, which is closer to the SQL standard `TIMESTAMP` than a MySQL `TIMESTAMP` type. The MySQL `TIMESTAMP` by definition is in UTC, while `DATETIME` is timezoneless and by JDBC requirements will use the JVM default timezone for conversion (haven't checked this in MySQL thoroughly, hence comment, not answer). – Mark Rotteveel Jan 23 '19 at 16:01
  • It's been a while, but it's my understanding that MYSQL always converts timestamp values to "raw" utc for internal storage, but converts them back to the local time zone (as per your connection settings) when you run a query. If you run a SELECT from the database (as opposed to just viewing the data in a grid), are you still seeing UTC, or do you get back utc+1? – markaaronky Jan 23 '19 at 16:02
  • @markaaronky I still see GMT timestamps – LppEdd Jan 23 '19 at 16:04
  • @MarkRotteveel which `PreparedStatement` method should I use with a `DATETIME` column? – LppEdd Jan 23 '19 at 16:08
  • `setTimestamp`. As I said, a MySQL `DATETIME` is closer to the SQL standard `TIMESTAMP` (and JDBC defers to the SQL standard for things like datatypes). – Mark Rotteveel Jan 23 '19 at 16:10
  • @MarkRotteveel Nada, tried but still the same result. I have really no idea – LppEdd Jan 23 '19 at 16:10
  • re#what's happening: it's a "short hand" conversion of `java.util.Date` into `java.sql.Timestamp` (via the "milliseconds since sine 1/1/1970") ..and `Date`is always **local** TZ based. – xerx593 Jan 23 '19 at 16:11
  • @xerx593 what's the better why to handle this? – LppEdd Jan 23 '19 at 16:11
  • db column is `TIMESTAMP`... – xerx593 Jan 23 '19 at 16:13
  • 1
    ..see this: https://stackoverflow.com/a/18615191/592355 – xerx593 Jan 23 '19 at 16:13
  • What happens if you set connection property `useLegacyDatetimeCode=false`? – Mark Rotteveel Jan 23 '19 at 16:14
  • @MarkRotteveel same result. The program uses this timestamp to check elapsed time, so being one hour back is a problem – LppEdd Jan 23 '19 at 16:17
  • but my link approves `preparedStatement.setTimestamp` ...it's in the mismatch of your db tz setting and your "jvm tz" settings ... – xerx593 Jan 23 '19 at 16:20
  • [this guy says](https://stackoverflow.com/a/36793896/592355): "just add `?serverTimezone=UTC` to url string". of course all of the article/question is interesting/ & realted. – xerx593 Jan 23 '19 at 16:23
  • ...mysqlDoc has a [whole chapter "Time Zone Support"](https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html) ;(;( – xerx593 Jan 23 '19 at 16:24
  • ..but in any case, you should know: what your "jvm tz" is, what your "db tz" is (maybe control them to your needs) ...and consider that java works internally with "local time" (since 1/1/1970:) – xerx593 Jan 23 '19 at 16:26
  • @xerx593 JVM default timestamp is set to "Europe/Rome" – LppEdd Jan 23 '19 at 16:28
  • ..then "try": `jdbc:mysql://..../db-name?serverTimezone=CET`, but what to do, when "summer comes"... maybe this can help/is worth research: `&useJDBCCompliantTimezoneShift=true` [see: older mysql version, same issue](https://stackoverflow.com/q/26515700/592355) – xerx593 Jan 23 '19 at 16:33
  • @xerx593 done everything, same result :( – LppEdd Jan 23 '19 at 16:42
  • Near duplicate of: [*Converting time to UTC time goes the opposite way*](https://stackoverflow.com/q/54314299/642706) – Basil Bourque Jan 23 '19 at 20:27
  • @BasilBourque Thanks for the comprehensive answer! And thanks also to the other guys – LppEdd Jan 23 '19 at 20:29

1 Answers1

1

tl;dr

myPreparedStatement
.setObject(  
    … ,                                   // Specify which placeholder `?` in your SQL statement. 
    OffsetDateTime.now( ZoneOffset.UTC )  // Capture the current moment as seen in the wall-clock time of UTC (an offset-from-UTC of zero).
) ;

Avoid legacy date-time classes

You are using terrible date-time classes that were supplanted years ago by the java.time classes.

Never use Date or Timestamp.

UTC

Capture the current moment, in UTC. Most databases store a moment in UTC. And generally you should do most of your business logic, debugging, logging, storage, and data exchange in UTC.

OffsetDateTime

Represent a moment with an offset-from-UTC using the aptly named OffsetDateTime class.

We want UTC itself, or an offset of zero. We can use a constant for that, ZoneOffset.UTC.

OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ) ;

JDBC 4.2

As of JDBC 4.2 we can directly exchange java.time objects with the database.

To save this moment to a column of a data type akin to the SQL-standard TIMESTAMP WITH TIME ZONE:

myPreparedStatement.setObject( … , odt ) ;

Retrieval:

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

ZonedDateTime

To present this retrieved moment to the user, you may want to adjust into the user’s expected/desired time zone.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

Never rely on default time zone

Notice in the code above that we always specified the desired/expected offset or zone.

If you do not specify, an offset or zone is silently implicitly applied. Better to specify your intentions explicitly as the current default of your JVM, database, and host OS are all out of your hands as a programmer. Which means code relying on the default will vary in behavior at runtime.

Java 6 & 7

However I'm still forced to deal with Java 6

The same man, Stephen Colebourne, who leads JSR 310 and the java.time implementation, as well as the famous Joda-Time project, also leads another project, ThreeTen-Backport. Most of the java.time functionality is back-ported to Java 6 & 7 in this library, with nearly identical API.

So do all your work in back-port classes. Then, at the last moment, convert to/from java.sql.Timestamp via the DateTimeUtils class.

Those conversion methods mostly use Instant objects. An Instant is a moment in UTC, always in UTC. You can adjust from your OffsetDateTime to UTC by extracting an Instant. The Instant class is the basic building-block class in java.time, with OffsetDateDate having more flexibility such as alternate formatting patterns when generating a string. But both Instant and OffsetDateTime represent a moment, a point on the timeline.

Instant instant = odt.toInstant() ;  
java.sql.Timestamp ts = org.threeten.bp.DateTimeUtils.toSqlTimestamp( instant ) ;

Going the other direction, retrieving a Timestamp from your database, then immediately converting to a Instant.

java.sql.Timestamp ts = myResultSet.getTimestamp( … ) ;
Instant instant = org.threeten.bp.DateTimeUtils.toInstant( ts ) ;

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Hi Basil, gonna read everything when I get home. However I'm still forced to deal with Java 6, so... – LppEdd Jan 23 '19 at 17:20
  • @LppEdd Most of the *java.time* functionality with virtually identical API is back-ported to Java 6 & 7 in the *ThreeTen-Backport* project. For JDBC, convert to/from `Timestamp` using the utils class in that library. – Basil Bourque Jan 23 '19 at 17:24
  • 1
    @LppEdd When restricted to an end-of-life’d version, you should note that fact on your Question. – Basil Bourque Jan 23 '19 at 17:25