Time.valueOf method returning wrong value - java

I used the method Time.valueOf to convert the String "09:00:00" into Time object as follows: Time.valueOf (LocalTime.parse("09:00:00")).
When I call getTime () to display the value I get: 28800000 ms instead of 32400000‬‬ ms (calculated from calculator).
Did I make an error when I used Time.value Of ? Because I don't understand why I get the wrong value.
Thanks.

That's caused by time zone issues. If you do
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
Before your code you will get 32400000‬‬ as you expect.
Time.valueOf (LocalTime.parse("09:00:00")) assumes the nine is at your local time zone so it converts it to UTC.
For example if the time zone is "Asia/Jerusalem" (you can simulate it with TimeZone.setDefault(TimeZone.getTimeZone("Asia/Jerusalem")); which was UTC+2 at 1 January 1970 it will convert 9 o'clock to 7 o'clock. You can see it with System.out.println(time.toGMTString());
As always in questions about the old java time api I have to give you an obligatory recommendation to always use only the modern post java8 api.

Stick to java.time.LocalTime
I recommend you stick to LocalTime from java.time, the modern Java date and time API, and don’t use java.sql.Time. The latter class is poorly designed, a true hack, indeed, on top of the already poorly designed java.util.Date class. Fortunately it’s also long outdated. There was a time when we needed a Time object for storing a time of day into a database column of datatype time or transferring a time to an SQL query using that datatype. Since JDBC 4.2 this is no longer the case. Now your JDBC driver accepts a LocalTime object and passes its time value on to the database.
So if you’ve got a string, parse it into a LocalTime object the way you already did:
LocalTime time = LocalTime.parse("09:00:00");
If you don’t need to go through a string, you may obtain the same result using the of factory method, for example:
LocalTime time = LocalTime.of(9, 0);
I don’t know why you should want to convert it to milliseconds, but you can:
int milliOfDay = time.get(ChronoField.MILLI_OF_DAY);
System.out.println(milliOfDay);
Output is:
32400000
This is the value you said you expected.
To insert the LocalTime into your database:
PreparedStatement ps = yourDatabaseConnection.prepareStatement(
"insert into your_table(your_time_col) values (?)");
ps.setObject(1, time);
int rowsInserted = ps.executeUpdate();
Note the use of setObject(), not setTime().
If you do need a Time object for some legacy API that you don’t want to upgrade just now, the conversion you made is correct.
Your expectations were wrong, your conversion was correct
Did I make an error when I used Time.value Of ? Because I don't
understand why I get the wrong value.
No, the other way around. You made no error. And in this case you got the correct value. It’s the poor and confusing design of Time playing a trick on you (I said you shouldn’t want to use that class). I am not sure it’s documented, but Time.valueOf (LocalTime.parse("09:00:00")) gives you a Time object that internally holds a point in time of Jan 1, 1970 09:00 in the time zone of your JVM. From the millisecond value you got, 28 800 000, it seems that this time is equal to 08:00 UTC. Was your time zone at UTC offset +01:00 in the winter of 1970? The getTime() method returns the number of milliseconds since Jan 1, 1970 00:00 UTC.

Related

How can I get the UTC-converted Java timestamp of current local time?

Could somebody please help with getting UTC-converted Java timestamp of current local time?
The main goal is to get current date and time, convert into UTC Timestamp and then store in Ignite cache as a Timestamp yyyy-MM-dd hh:mm:ss[.nnnnnnnnn].
My attempt was Timestamp.from(Instant.now()). However, it still considers my local timezone +03:00. I am getting '2020-02-20 10:57:56' as a result instead of desirable '2020-02-20 07:57:56'.
How can I get UTC-converted Timestamp?
You can do it like this :
LocalDateTime localDateTime = Instant.now().atOffset(ZoneOffset.UTC).toLocalDateTime();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(localDateTime.format(formatter));
Don’t use Timestamp
You most probably don’t need a Timestamp. Which is good because the Timestamp class is poorly designed, indeed a true hack on top of the already poorly designed Date class. Both classes are also long outdated. Instead nearly 6 years ago we got java.time, the modern Java date and time API. Since JDBC 4.2 this works with your JDBC driver too, and also with your modern JPA implementation.
Use OffsetDateTime
For a timestamp the recommended datatype in your database is timestamp with time zone. In this case in Java use an OffsetDateTime with an offset of zero (that is, UTC). For example:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(now);
PreparedStatement statement = yourDatabaseConnection
.prepareStatement("insert into your_table (tswtz) values (?);");
statement.setObject(1, now);
int rowsInserted = statement.executeUpdate();
Example output from the System.out.println() just now:
2020-02-22T13:04:06.320Z
Or use LocalDateTime if your database timestamp is without time zone
From your question I get the impression that the datatype in your database is timestamp without time zone. It’s only the second best option, but you can pass a LocalDateTime to it.
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
The rest is the same as before. Example output:
2020-02-22T13:05:08.776
If you do need an old-fashioned java.sql.Timestamp
You asked for a Timestamp in UTC. A Timestamp is always in UTC. More precisely, it’s a point in time independent of time zone, so converting it into a different time zone does not make sense. Internally it’s implemented as a count of milliseconds and nanoseconds since the epoch. The epoch is defined as the first moment of 1970 in UTC.
The Timestamp class is a confusing class though. One thing that might have confused you is when you print it, thereby implicitly calling its toString method. The toString method uses the default time zone of the JVM for rendering the string, so prints the time in your local time zone. Confusing. If your datatype in SQL is timestamp without time zone, your JDBC driver most probably interprets the Timestamp in your time zone for the conversion into an SQL timestamp. Which in your case is incorrect since your database uses UTC (a recommended practice). I can think of three possible solutions:
Some database engines allow you to set a time zone on the session. I haven’t got any experience with it myself, it’s something I have read; but it may force the correct conversion from your Java Timestamp to your SQL timestamp in UTC to be performed.
You may make an incorrect conversion in Java to compensate for the opposite incorrect conversion being performed between Java and SQL. It’s a hack, not something that I would want to have in my code. I present it as a last resort.
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
Timestamp ts = Timestamp.valueOf(now);
System.out.println(ts);
2020-02-22 13:05:08.776
You notice that it only appears to agree with the UTC time above. It‘s the same result you get from the answer by Vipin Sharma except (1) my code is simpler and (2) you’re getting a higher precision, fraction of second is included.
Have you database generate the current timestamp in UTC instead of generating it in Java.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Related question: Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
Despite what the Ignite docs say you can pass in a 24hr time.
The docs says yyyy-MM-dd hh:mm:ss[.nnnnnnnnn] so you may be tempted in your code to use this to format your dates but this will lead to times after midday being wrong. Instead, format your dates with yyyy-MM-dd HH:mm:ss[.nnnnnnnnn].
Notice the upper case HH. If you're using ZonedDateTime or Joda's DateTime when you call now with UTC now(UTC) and then toString("yyyy-MM-dd HH:mm:ss") will store the correct time in UTC.

How to convert seconds to java.sql.Timestamp?

I needed to truncate milliseconds to seconds and implemented it in this way:
private static Long millisToSeconds(Long millisValue) {
return TimeUnit.MILLISECONDS.toSeconds(millisValue);
}
So now it truncates millis as expected, for example:
Long secondsValue = millisToSeconds(1554052265830L);
System.out.println("millisToSeconds ---> " + toSeconds);
// Prints millisToSeconds ---> 1554052265
But then I want to convert secondsValue to java.sql.Timestamp but the following implementation results in an error:
java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
What should I fix in my implementation to convert seconds to timestamp so that the resulting timestamp looks like 2019-03-31 11:45:06 ?
java.time
I am assuming that you are asking for a java.sql.Timestamp for use with your SQL database. In most cases you shouldn’t ask for that. The Timestamp class is poorly designed and long outdated, and a modern JDBC driver or JPA implementation will be happy to accept a type from java.time, the modern Java date and time API, instead.
long millisValue = 1_554_052_265_830L;
Instant i = Instant.ofEpochMilli(millisValue);
i = i.truncatedTo(ChronoUnit.SECONDS);
System.out.println(i);
2019-03-31T17:11:05Z
I don’t know why you wanted to truncate to seconds, but you can see that it has been done (or it’s easy to leave that line out).
Some JDBC drivers accept an Instant directly when you pass it to PreparedStatement.setObject (one of the overloaded versions of that method) even though the JDBC specification doesn’t require this. If yours doesn’t, use an OffsetDateTime instead. Convert like this:
OffsetDateTime odt = i.atOffset(ZoneOffset.UTC);
System.out.println(odt);
2019-03-31T17:11:05Z
You can see that the value is still the same, only the type is different.
What should I fix in my implementation to convert seconds to timestamp
so that the resulting timestamp looks like 2019-03-31 11:45:06 ?
First, as I said, you should fix your code not to require a Timestamp, but also you are asking the impossible. As far as I know, Timestamp.toString would always produce at least one decimal on the seconds, so it would at least look like 2019-03-31 11:45:06.0.
If you do indispensably need a Timestamp for a legacy API that you cannot or don’t want to change just now, convert the Instant from before:
Timestamp ts = Timestamp.from(i);
System.out.println(ts);
2019-03-31 19:11:05.0
Don’t be fooled by the time looking different (19:11 instead of 17:11). Timestamp prints in my local time zone, which is Europe/Copenhagen, 2 hours ahead of UTC since summer time (DST) began on March 31. So we have still got the same point in time.
Link: Oracle tutorial: Date Time explaining how to use java.time.
You can use SimpleDateFormatto format the date as per your requirement. See below
Long secondsValue = millisToSeconds(1554052265830L);
System.out.println("millisToSeconds ---> " + secondsValue);
Timestamp timeStamp = new Timestamp(secondsValue);
String formattedDate = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss").format(timeStamp.getTime());
System.out.println(formattedDate);
The error suggest that you are using Timestamp.valueOf(String) (possibly with secondsValue.toString() as the argument?).
A java.sql.Timestamp is a special version of java.util.Date with nanosecond precision to serialize/deserialize SQL TIMESTAMP values. It is not a second value at all.
The constructor of Timestamp take a millisecond value, not a second value (for nanosecond precision, you need to use the separate setNanos with the sub-second nanoseconds).
In any case the proper way would be to use:
long milliseconds = ...;
long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds);
long truncatedMilliseconds = TimeUnit.SECONDS.toMillis(seconds);
// or truncatedMilliseconds = (milliseconds / 1000) * 1000;
Timestamp value = new Timestamp(truncatedMilliseconds);
However, since you are talking about needing a specific string format, I'm not sure you need this at all. Unless you are using JDBC to store this value in a database, you should not be using java.sql.Timestamp at all (and even when using JDBC, then it would probably be better to use java.time.LocalDatetime instead).

Create company specific timezone for 'working day' in java

I work at a company where part of the work for a day is done in the early hours of the next day (i.e. shipping orders). Now for several processes (mainly reporting), we want to let the 'working day' end at 04:00 the next morning so we get more consistent reporting values per day.
We want this to always be at 04:00 the next morning and since we are affected by daylight saving times in our area (Europe - Netherlands) we effectively want a 4 hour shifted variant of our normal timezone 'Europe/Amsterdam' (in our case).
To make this as easy to use for all applications in our company I would like to create a small library that simply contains the code to provide my coworkers to get a modified instance of TimeZone that does this. That way all normal time/date manipulation methods can be used in conjunction with this special time zone.
I did a deep dive into the standard Java 8 code/Javadoc related to the TimeZone/ZoneInfo instances and at this moment I do not understand what the correct field is to change in the returned TimeZone/ZoneInfo instance.
At this point, my best guess is setting the RawOffset to 4 hours, but I'm not sure.
What is the correct way to achieve my goal?
Update:
I had a look at the suggested LocalTime and as I expected: It needs a timezone definition as being what it should use as "Local" when converting an existing timestamp (usually epoch milliseconds) into the "Local" timezone.
Looking at all these classes seems like I'll be using the LocalDate more often than the LocalTime.
Effectively the code I expect to have is something like this:
long epoch = 1525033875230L; // Obtained from some dataset
LocalDate localDate = LocalDateTime
.ofInstant(Instant.ofEpochMilli(epoch),
ZoneId.of("Europe/Amsterdam"))
.toLocalDate();
Where I expect that I need to change that Zone into the 'right one'.
If I have got that correctly, what you really need is a way to convert a milliseconds value since the epoch to a date in a way where days don’t change a 00:00 but not until 04:00.
static ZoneId zone = ZoneId.of("Europe/Amsterdam");
static LocalTime lastShiftEnds = LocalTime.of(4, 0);
public static LocalDate epochMilliToDate(long epoch) {
ZonedDateTime dateTime = Instant.ofEpochMilli(epoch)
.atZone(zone);
if (dateTime.toLocalTime().isAfter(lastShiftEnds)) { // normal date-time
return dateTime.toLocalDate();
} else { // belonging to previous day’s night shift
return dateTime.toLocalDate().minusDays(1);
}
}
Use for example like this:
long epoch = 1_525_050_875_230L;
System.out.println(Instant.ofEpochMilli(epoch));
LocalDate date = epochMilliToDate(epoch);
System.out.println(date);
Output is:
2018-04-30T01:14:35.230Z
2018-04-29
From printing the Instant you can see that the time is after midnight (really 03:14:35.230 in Amsterdam time zone). And the method has correctly deemed that this time belongs to April 29 rather than April 30.
Perhaps I am missing something? On the other hand, if that were me I’d go quite a long way to avoid inventing a time zone that doesn’t exist in real life. Such a time zone would be bound to confuse your coworkers.

Java Date to milliseconds

I'm storing messages from an amazon cloud and ordering them by their timestamp in a sorted map.
I am parsing the timestamp from the cloud with the following code:
Date timestamp = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'Z'", Locale.ENGLISH).parse(time);
and then I am storing in them in a sorted map with the key being the date.
The issue is that the date only comes down to seconds precision.
I can have several messages sent in 1 second, so I need them to be ordered with millisecond precision. Is there a data structure that allows this?
Well as long as your source has a higher resolution than 1 second. Looks like that from the pattern, but you haven't shown us any input example.
Date is just a wrapper around a long milliseconds since 1970-01-01. So you have that already. Date.getTime() will return that, with millisecond precision.
Why would you think that Date only has one second precision? Date.compareTo(Date anotherDate) compares on a millisecond level.
So your SortedMap should work fine unless you are doing something strange.
I am not sure if you have done this, but you can create your own comparator and use that.
As a side note, depending on your applications setup you may want to be careful with how you use SimpleDateFormat, there are some issues with it.
java.time
I am providing the modern answer: use java.time, the modern Java date and time API, for your date and time work. First of all because it is so much nicer to work with than the old date and time classes like Date and (oh, horrors) SimpleDateFormat, which are poorly designed. We’re fortunate that they are long outdated. An added advantage is: Your date-time string is in ISO 8601 format, and the classes of java.time parse this format as their default, that is, without any explicit formatter.
String stringFromCloud = "2014-06-14T08:55:56.789Z";
Instant timestamp = Instant.parse(stringFromCloud);
System.out.println("Parsed timestamp: " + timestamp);
Output:
Parsed timestamp: 2014-06-14T08:55:56.789Z
Now it’s clear to see that the string has been parsed with full millisecond precision (Instant can parse with nanosecond precision, up to 9 decimals on the seconds). Instant objects will work fine as keys for your SortedMap.
Corner case: if the fraction of seconds i 0, it is not printed.
String stringFromCloud = "2014-06-14T08:56:59.000Z";
Parsed timestamp: 2014-06-14T08:56:59Z
You will need to trust that when no fraction is printed, it is because it is 0. The Instant will still work nicely for your purpose, being sorted before instants with fraction .001, .002, etc.
What went wrong in your parsing?
First, you’ve got a problem that is much worse than missing milliseconds: You are parsing into the wrong time zone. The trailing Z in your incoming string is a UTC offset of 0 and needs to be parsed as such. What happened in your code was that SimpleDateFormat used the time zone setting of your JVM instead of UTC, giving rise to an error of up to 14 hours. In most cases your sorting would still be correct. Around transition from summer time (DST) in your local time zone the time would be ambiguous and parsing may therefore be incorrect leading to wrong sort order.
As the Mattias Isegran Bergander says in his answer, parsing of milliseconds should work in your code. The reason why you didn’t think so is probably because just a minor one of the many design problems with the old Date class: even though internally it has millisecond precision, its toString method only prints seconds, it leaves out the milliseconds.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601

Timezones in SQL DATE vs java.sql.Date

I'm getting a bit confused by the behaviour of the SQL DATE data type vs. that of java.sql.Date. Take the following statement, for example:
select cast(? as date) -- in most databases
select cast(? as date) from dual -- in Oracle
Let's prepare and execute the statement with Java
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setDate(1, new java.sql.Date(0)); // GMT 1970-01-01 00:00:00
ResultSet rs = stmt.executeQuery();
rs.next();
// I live in Zurich, which is CET, not GMT. So the following prints -3600000,
// which is CET 1970-01-01 00:00:00
// ... or GMT 1969-12-31 23:00:00
System.out.println(rs.getDate(1).getTime());
In other words, the GMT timestamp I bind to the statement becomes the CET timestamp I get back. At what step is the timezone added and why?
Note:
I have observed this to be true for any of these databases:
DB2, Derby, H2, HSQLDB, Ingres, MySQL, Oracle, Postgres, SQL Server, Sybase ASE, Sybase SQL Anywhere
I have observed this to be false for SQLite (which doesn't really have true DATE data types)
All of this is irrelevant when using java.sql.Timestamp instead of java.sql.Date
This is a similar question, which doesn't answer this question, however: java.util.Date vs java.sql.Date
The JDBC specification does not define any details with regards to time zone. Nonetheless, most of us know the pains of having to deal with JDBC time zone discrepencies; just look at all the StackOverflow questions!
Ultimately, the handling of time zone for date/time database types boils down to the database server, the JDBC driver and everything in between. You're even at the mercy of JDBC driver bugs; PostgreSQL fixed a bug in version 8.3 where
Statement.getTime, .getDate, and .getTimestamp methods which are passed a Calendar object were rotating the timezone in the wrong direction.
When you create a new date using new Date(0) (let's assert you are using Oracle JavaSE java.sql.Date, your date is created
using the given milliseconds time value. If the given milliseconds value contains time information, the driver will set the time components to the time in the default time zone (the time zone of the Java virtual machine running the application) that corresponds to zero GMT.
So, new Date(0) should be using GMT.
When you call ResultSet.getDate(int), you're executing a JDBC implementation. The JDBC specification does not dictate how a JDBC implementation should handle time zone details; so you're at the mercy of the implementation. Looking at the Oracle 11g oracle.sql.DATE JavaDoc, it doesn't seem Oracle DB stores time zone information, so it performs its own conversions to get the date into a java.sql.Date. I have no experience with Oracle DB, but I would guess the JDBC implementation is using the server's and your local JVM's time zone settings to do the conversion from oracle.sql.DATE to java.sql.Date.
You mention that multiple RDBMS implementations handle time zone correctly, with the exception of SQLite. Let's look at how H2 and SQLite work when you send date values to the JDBC driver and when you get date values from the JDBC driver.
The H2 JDBC driver PrepStmt.setDate(int, Date) uses ValueDate.get(Date), which calls DateTimeUtils.dateValueFromDate(long) which does a time zone conversion.
Using this SQLite JDBC driver, PrepStmt.setDate(int, Date) calls PrepStmt.setObject(int, Object) and does not do any time zone conversion.
The H2 JDBC driver JdbcResultSet.getDate(int) returns get(columnIndex).getDate(). get(int) returns an H2 Value for the specified column. Since the column type is DATE, H2 uses ValueDate. ValueDate.getDate() calls DateTimeUtils.convertDateValueToDate(long), which ultimately creates a java.sql.Date after a time zone conversion.
Using this SQLite JDBC driver, the RS.getDate(int) code is much simpler; it just returns a java.sql.Date using the long date value stored in the database.
So we see that the H2 JDBC driver is being smart about handling time zone conversions with dates while the SQLite JDBC driver is not (not to say this decision isn't smart, it might suit SQLite design decisions well). If you chase down the source for the other RDBMS JDBC drivers you mention, you will probably find that most are approaching date and time zone in a similar fashion as how H2 does.
Though the JDBC specifications do not detail time zone handling, it makes good sense that RDBMS and JDBC implementation designers took time zone into consideration and will handle it properly; especially if they want their products to be marketable in the global arena. These designers are pretty darn smart and I am not surprised that most of them get this right, even in the absence of a concrete specification.
I found this Microsoft SQL Server blog, Using time zone data in SQL Server 2008, which explains how time zone complicates things:
timezones are a complex area and each application will need to address how you are going to handle time zone data to make programs more user friendly.
Unfortunately, there is no current international standard authority for timezone names and values. Each system needs to use a system of their own choosing, and until there is an international standard, it is not feasible to try to have SQL Server provide one, and would ultimately cause more problems than it would solve.
It is the jdbc driver that does the conversion. It needs to convert the Date object to a format acceptable by the db/wire format and when that format doesn't include a time zone, the tendency is to default to the local machine's time zone setting when interpreting the date. So, most likely scenario, given the list of drivers you specify, is that you set the date to GMT 1970-1-1 00:00:00 but the interpreted date when you set it to the statement was CET 1970-1-1 1:00:00. Since the date is only the date portion, you get 1970-1-1 (without a time zone) sent to the server and echoed back to you. When the driver gets the date back, and you access it as a date, it sees 1970-1-1 and interprets that again with the local time zone, i.e. CET 1970-1-1 00:00:00 or GMT 1969-12-31 23:00:00. Hence, you have "lost" an hour compared to the original date.
Both java.util.Date and the Oracle/MySQL Date objects are simply representations of a point in time regardless of location. This means that it is very likely to be internally stored as the number of milli/nano seconds since the "epoch", GMT 1970-01-01 00:00:00.
When you read from the resultset, your call to "rs.getDate()" tells the resultset to take the internal data containing the point in time, and convert it to a java Date object. This date object is created on your local machine, so Java will choose your local timezone, which is CET for Zurich.
The difference you are seeing is a difference in representation, not a difference in time.
The class java.sql.Date corresponds to SQL DATE, which does not store time or timezone information. The way this is accomplished is by 'normalizing' the date, like the javadoc puts it:
To conform with the definition of SQL DATE, the millisecond values wrapped by a java.sql.Date instance must be 'normalized' by setting the hours, minutes, seconds, and milliseconds to zero in the particular time zone with which the instance is associated.
This means that when you work in UTC+1 and ask the database for a DATE a compliant implementation does exactly what you've observed: return a java.sql.Date with a milliseconds value that corresponds to the date in question at 00:00:00 UTC+1 independently of how the data got to the database in the first place.
Database drivers may allow changing this behaviour through options if it's not what you want.
On the other hand, when you pass a java.sql.Date to the database, the driver will use the default time zone to separate the date and time components from the millisecond value. If you use 0 and you're in UTC+X, the date will be 1970-01-01 for X>=0 and 1969-12-31 for X<0.
Sidenote: It's odd to see that the documentation for the Date(long) constructor differs from the implementation. The javadoc says this:
If the given milliseconds value contains time information, the driver will set the time components to the time in the default time zone (the time zone of the Java virtual machine running the application) that corresponds to zero GMT.
However what is actually implemented in OpenJDK is this:
public Date(long date) {
// If the millisecond date value contains time info, mask it out.
super(date);
}
Apparently this "masking out" is not implemented. Just as well because the specified behaviour is not well specified, e.g. should 1970-01-01 00:00:00 GMT-6 = 1970-01-01 06:00:00 GMT be mapped to 1970-01-01 00:00:00 GMT = 1969-12-31 18:00:00 GMT-6, or to 1970-01-01 18:00:00 GMT-6?
JDBC 4.2 and java.time
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setObject(1, LocalDate.EPOCH); // The epoch year LocalDate, '1970-01-01'.
ResultSet rs = stmt.executeQuery();
rs.next();
// It doesnt matter where you live or your JVM’s time zone setting
// since a LocalDate doesn’t have or use time zone
System.out.println(rs.getObject(1, LocalDate.class));
I didn’t test, but unless there’s a bug in your JDBC driver, the output in all time zones is:
1970-01-01
LocalDate is a date without time of day and without time zone. So there is no millisecond value to get and no time of day to be confused about. LocalDate is part of java.time, the modern Java date and time API. JDBC 4.2 specifies that you can transfer java.time objects including LocalDate to and from your SQL database through the methods setObject and getObject I use in the snippet (so not setDate nor getDate).
I think that virtually all of us are using JDBC 4.2 drivers by now.
What went wrong in your code?
java.sql.Date is a hack trying (not very successfully) to disguise a java.util.Date as a date without time of day. I recommend that you don’t use that class.
From the documentation:
To conform with the definition of SQL DATE, the millisecond values
wrapped by a java.sql.Date instance must be 'normalized' by setting
the hours, minutes, seconds, and milliseconds to zero in the
particular time zone with which the instance is associated.
“Associated” may be vague. What this means, I believe, is that the millisecond value must denote the start of day in your JVM’s default time zone (Europe/Zurich in your case, I suppose). So when you create new java.sql.Date(0) equal to GMT 1970-01-01 00:00:00, it’s really you creating an incorrect Date object, since this time is 01:00:00 in your time zone. JDBC is ignoring the time of day (we might have expected an error message, but don’t get any). So SQL is getting and returning a date of 1970-01-01. JDBC correctly translates this back to a Date containing CET 1970-01-01 00:00:00. Or a millisecond value of -3 600 000. Yes, it’s confusing. As I said, don’t use that class.
If you had been at a negative GMT offset (for example most time zones in the Americas), new java.sql.Date(0) would have been interpreted as 1969-12-31, so this would have been the date you had passed to and got back from SQL.
To make matters worse, think what happens when the JVM’s time zone setting is changed. Any part of your program and any other program running in the same JVM may do this at any time. This typically causes all java.sql.Date objects created before the change to become invalid and may sometimes shift their date by one day (in rare cases by two days).
Links
Oracle tutorial: Date Time explaining how to use java.time.
JSR-000221 JDBC™ API Specification 4.2 Maintenance Release 2
Documentation of java.sql.Date
There is an overload of getDate that accepts a Calendar, could using this solve your problem? Or you could use a Calendar object to convert the information.
That is, assuming that retrieval is the problem.
Calendar gmt = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setDate(1, new Date(0)); // I assume this is always GMT
ResultSet rs = stmt.executeQuery();
rs.next();
//This will output 0 as expected
System.out.println(rs.getDate(1, gmt).getTime());
Alternatively, assuming that storage is the problem.
Calendar gmt = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
PreparedStatement stmt = connection.prepareStatement(sql);
gmt.setTimeInMillis(0) ;
stmt.setDate(1, gmt.getTime()); //getTime returns a Date object
ResultSet rs = stmt.executeQuery();
rs.next();
//This will output 0 as expected
System.out.println(rs.getDate(1).getTime());
I don't have a test environment for this, so you will have to find out which is the correct assumption. Also I believe that getTime returns the current locale, otherwise you will have to look up how to do a quick timezone conversion.

Categories