7

I have a java.util.Date object, and I need to insert it into a datetime field in MySQL in UTC format.

java.util.Date date = myDateFromSomewhereElse;
PreparedStatement prep = con.prepareStatement(
    "INSERT INTO table (t1, t2) VALUES (?,?)");

java.sql.Timestamp t = new Timestamp(date.getTime());
prep.setTimestamp(1, t, Calendar.getInstance(TimeZone.getTimeZone("PST"));
prep.setTimestamp(2, t, Calendar.getInstance(TimeZone.getTimeZone("UTC"));
System.out.println(prep.toString());

Which gives me the prepared SQL statement string:

INSERT INTO table (t1, t2) VALUES ('2012-05-09 11:37:08','2012-05-09 11:37:08');

The timestamp returned is the same timestamp regardless of the timezone I specify. It's ignoring the Calendar object with timezone that I specify. What is going on and what am I doing wrong?

Eric Leschinski
  • 146,994
  • 96
  • 417
  • 335
Jordan
  • 3,998
  • 9
  • 45
  • 81
  • 2
    Time zones don't change the timestamp. They just change what is shown when the date is rendered. – Jeremy May 09 '12 at 18:52
  • Yes, I understand that a timestamp is just a number of milliseconds since epoch GMT, regardless of timezone. My problem is that even by specifying the timezone, it's rendering the exact same display date (see that even though I specified different time zones, the date was rendered exactly the same in the final SQL command). – Jordan May 09 '12 at 19:03
  • I think this may be what you are looking for : http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/ – Chris May 09 '12 at 19:33

3 Answers3

9

Jordan, actually you had the right idea. The problem is there's a bug in MySQL JDBC driver and the Calendar argument is completely ignored by default. Look at the source code for PreparedStatement to really see what's going on.

Notice it format's the Timestamp using the JVM's time zone. This will only work if your JVM is using UTC time zone. The Calendar object is completely ignored.

this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''", Locale.US);
timestampString = this.tsdf.format(x);

In order for MySQL to use the Calendar argument, you have to disable the legacy date/time code with the following connection option:

useLegacyDatetimeCode=false

So you might use it when connecting to the database like this:

String url = "jdbc:mysql://localhost/tz?useLegacyDatetimeCode=false"

If you disable the legacy datetime code using the above line, then it WILL render your Timestamp in the target Calendar's time zone:

if (targetCalendar != null) {
    targetCalendar.setTime(x);
    this.tsdf.setTimeZone(targetCalendar.getTimeZone());

     timestampString = this.tsdf.format(x);
} else {
    this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ());
    timestampString = this.tsdf.format(x);
}

It's pretty easy to see what's going on here. If you pass in a Calendar object, it will use this when formatting the data. Otherwise, it will use the database's time zone to format the data. Strangely, if you pass in a Calendar, it will also set the time to the given Timestamp value (which seems to be pointless).

nogridbag
  • 3,521
  • 4
  • 38
  • 51
3

Check this link for explanation for MySQL (and you shouldn't try to apply advices about Oracle to MySQL).

The TIMESTAMP data type is used for values that contain both date and time parts. TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.

MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME.) By default, the current time zone for each connection is the server's time.

Community
  • 1
  • 1
Vadim Ponomarev
  • 1,346
  • 9
  • 15
2

TimeZones are just different ways to view a date (which is a fixed point in time). I wrote a little example here (pay close attention to the assert):

// timezone independent date (usually interpreted by the timezone of 
// the default locale of the user machine)
Date now = new Date();

// now lets get explicit with how we wish to interpret the date
Calendar london =  Calendar.getInstance(TimeZone.getTimeZone("Europe/London"));
Calendar paris = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris"));

// now set the same date on two different calendar instance
london.setTime(now);
paris.setTime(now);

// the time is the same
assert london.getTimeInMillis() == paris.getTimeInMillis();

// London is interpreted one hour earlier than Paris (as of post date of 9th May 2012)
String londonTime = london.get(Calendar.HOUR) + ":" + london.get(Calendar.MINUTE);
String londonTZ = london.getTimeZone().getDisplayName(london.getTimeZone().inDaylightTime(london.getTime()), TimeZone.SHORT);
System.out.println(londonTime + " " + londonTZ);

// Paris is interpreted one hour later than Paris (as of post date of 9th May 2012)
String parisTime = paris.get(Calendar.HOUR) + ":" + paris.get(Calendar.MINUTE);
String parisTZ = paris.getTimeZone().getDisplayName(paris.getTimeZone().inDaylightTime(paris.getTime()), TimeZone.SHORT);
System.out.println(parisTime + " " + parisTZ);

The output to this snippet is (the result will be different depending on execution date/time):

8:18 BST
9:18 CEST

Your snippet in the question is simply not doing anything with regard to the date being stored. Usually databases are configured for a native TimeZone. I advise storing an extra field representing the TimeZone to be used when interpreting the date.

It is not (generally) a good idea to modify dates (which are essentially just milliseconds before/after a fixed point in time) as this would be a lossy modification that would be interpreted differently at different points in the year (due to daylight savings time).

Or this : http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/

Eric Leschinski
  • 146,994
  • 96
  • 417
  • 335
Chris
  • 4,450
  • 3
  • 38
  • 49
  • Actually he could receive different values in database, if his database was Oracle and he used column type `TIMESTAMP WITH TIME ZONE`. But where is no time zone support for dates in MySQL. – Vadim Ponomarev May 09 '12 at 19:45
  • Yes, I put a link in at the bottom for an Oracle specific implementation for storing/retrieving TimeZones along with the the dates. I think it more generic to store the timezone separately as it makes it more portable for non-Oracle databases. The great thing about dates even without a TimeZone context is that they can be compared relative to each other without issues. – Chris May 09 '12 at 19:50
  • If you really need to persist time zone info it's better to use database specific feature (like in Oracle and PostgreSQL) and forget about portability. Otherwise you will have problem with comparison and sorting. – Vadim Ponomarev May 09 '12 at 20:12
  • 1
    @VadimPonomarev: you're wrong about having problems with sorting if not using database specific timezone features. Dates/Times are stored as fixed point in time integers (representing time before or after a fixed point in time). As such, they can always be sorted and compared perfectly without understanding the timezone which they are representing. – Chris May 11 '12 at 17:49
  • Yeah, you got me here. Didn't thought about this possibility. +1 to comment. – Vadim Ponomarev May 11 '12 at 18:19