CST/CDT time zone change issue - java

We are storing time in like '22-NOV-17 05.33.51.937000000 PM' format with server default timezone CST. We have half an our time comparison in many places. So CST to CDT and CDT to CST are facing issues because on retrieval time for database we can not identify the time zone. So it is breaking our time comparison on CST to CDT and CDT to CST time changes.
We can not change our storing logic like store with timezone and store in UTC timezone because it will breaking our existing logic in many places.
So is there any way to identity date timezone like CST or CDT, stored in database with '22-NOV-17 05.33.51.937000000 PM' format.

We are storing time in like '22-NOV-17 05.33.51.937000000 PM' format
does not make sense with your comment
We are storing as a timestamp in database
In Oracle databases, a TIMESTAMP does not have a format - it is stored in the database as 11 bytes representing year (2 bytes), month, day, hours, minutes, seconds (1 byte each) and fractional seconds (4 bytes). It is only when whatever interface you are using (SQL/Plus, SQL Developer, Toad, Java, PHP, etc.) to talk to the database decides to show it to you, the user, that that interface will format it as a string (but the database will just keep it as bytes without any format).
Assuming you are using SQL/Plus or SQL Developer then you can find the default format using:
SELECT value FROM NLS_SESSION_PARAMETERS WHERE parameter = 'NLS_TIMESTAMP_FORMAT';
And change the default format using:
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF9';
Or for TIMESTAMP WITH TIME ZONE
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF9 TZR';
So is there any way to identity date timezone like CST or CDT, stored in database with '22-NOV-17 05.33.51.937000000 PM' format.
No, without any other meta-data that could identify the source of the timestamp and indicate which location it came from (i.e. is there another column that links to the user who entered the data that could be mapped to a physical location and so a time zone) then it is impossible to determine which time zone it is from.
You will either need to:
change your database column to TIMESTAMP WITH TIME ZONE and store the time zone; or
convert all the values to the same time zone when you are storing them.

I am assuming by CST and CDT you mean North American Central Standard Time and Central Daylight Time such as observed in Rainy River, Chicago and Mexico (the city) among other places. More on this ambiguity later.
For 99.977 % of all times it is fairly easy to know whether they are standard time or daylight saving time. Only times from the two hours around the transition from DST to standard time are ambiguous, and as said in the comments, there is no way to know from the time stamp which is the right way to resolve this ambiguity.
java.time
This answer will take you as far into the future as possible without taking you away from Java 7. You can still use java.time, the modern Java date and time API also known as JSR-310. It has been backported to Java 6 and 7 in the ThreeTen Backport, so it’s a matter of getting this and adding it to your project (just until one day you upgrade to Java 8 or later).
I am taking your word for your date-time string format. What we can do with it:
DateTimeFormatter storedFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("d-MMM-uu hh.mm.ss.SSSSSSSSS a")
.toFormatter(Locale.US);
ZoneId zone = ZoneId.of("America/Mexico_City");
String storedTime = "22-NOV-17 05.33.51.937000000 PM";
LocalDateTime dateTime = LocalDateTime.parse(storedTime, storedFormatter);
// First shot -- will usually be correct
ZonedDateTime firstShot = ZonedDateTime.of(dateTime, zone);
System.out.println(firstShot);
This prints:
2017-11-22T17:33:51.937-06:00[America/Mexico_City]
You can see that it picked an offset of -06:00, which means that the time is in standard time (CDT is -05:00).
Since your month abbreviation is in all uppercase, I needed to tell the formatter to parse case insensitively. If America/Mexico_City time zone is not appropriate for you, pick a better one, for example America/Rainy_River or America/Chicago.
Ambiguous times in fall
I once had to parse a log file containing date-times without indication of standard time and summer time (DST). Since we assumed time would always move forward, we failed at the transition to standard time, and one hour of the log file was lost. In this case we might have solved it using the information that times were in summer time until the leap backward by an hour, from there they were in standard time. You may want to think about whether something similar will be possible for you.
Other options include just taking DST time every time — this is what the above code will do — or taking an average and living with the error thus introduced.
We can at least detect the ambiguous times:
ZoneOffset standardOffset = ZoneOffset.ofHours(-6);
ZoneOffset dstOffset = ZoneOffset.ofHours(-5);
// check if in fall overlap
ZonedDateTime standardDateTime
= ZonedDateTime.ofLocal(dateTime, zone, standardOffset);
ZonedDateTime dstDateTime
= ZonedDateTime.ofLocal(dateTime, zone, dstOffset);
if (! standardDateTime.equals(dstDateTime)) {
System.out.println("Ambiguous, this could be in CST or CDT: " + dateTime);
}
Now if the string was 29-OCT-17 01.30.00.000000000 AM, I get the message
Ambiguous, this could be in CST or CDT: 2017-10-29T01:30
ZonedDateTime.ofLocal() will use the provided offset for resolving the ambiguity if it is a valid offset for the date-time and zone.
Non-existing times in the spring
Similarly we can detect if your date-time falls in the gap where the clock is moved forward in the transition to DST:
// Check if in spring gap
if (! firstShot.toLocalDateTime().equals(dateTime)) {
System.out.println("Not a valid date-time, in spring gap: " + dateTime);
}
This can give a message like
Not a valid date-time, in spring gap: 2018-04-01T02:01
I suggest you can safely reject such values. They cannot be correct.
Avoid the three letter time zone abbreviations
CST may refer to Central Standard Time (in North and Central America), Australian Central Standard Time, Cuba Standard Time and China Standard Time. CDT may mean Central Daylight Time or Cuba Daylight Time. The three and four letter abbreviations are not standardized and are very often ambiguous. Prefer time zone IDs in the region/city format, for example America/Winnipeg.

Related

Why is the following date conversion in Java 8 not appropriate?

I have seen a lot of debates on the following date conversion:
timeStamp.toLocalDateTime().toLocalDate();
Some people say that it is not appropriate because the timezone has to be specified for proper conversion, otherwise the result may be unexpected. My requirement is that I have an object that contains Timestamp fields and another object that contains LocalDate fields. I have to take the date difference between both so I think that the best common type to use is LocalDate. I don't see why the timezone has to be specified as either timestamp or LocalDate just represent dates. The timezone is already implied. Can someone give an example when this conversion fails?.
It’s more complicated than that. While it’s true that a Timestamp is a point in time, it also tends to have a dual nature where it sometimes pretends to be a date and time of day instead.
BTW, you probably already know, the Timestamp class is poorly designed and long outdated. Best if you can avoid it completely. If you are getting a Timestamp from a legacy API, you are doing the right thing: immediately converting it to a type from java.time, the modern Java date and time API.
Timestamp is a point in time
To convert a point in time (however represented) to a date you need to decide on a time zone. It is never the same date in all time zones. So the choice of time zone will always make a difference. So one correct conversion would be:
ZoneId zone = ZoneId.of("Africa/Cairo");
LocalDate date = timestamp.toInstant().atZone(zone).toLocalDate();
The Timestamp class was designed for use with your SQL database. If your datatype in SQL is timestamp with time zone, then it unambiguously denotes a point in time, and you need to see it as a point in time as just described. Even when to most database engines timestamp with time zone really just means “timestamp in UTC”, it’s still a point in time.
And then again: sometimes to be thought of as date and time of day
From the documentation of Timestamp:
A Timestamp also provides formatting and parsing operations to support
the JDBC escape syntax for timestamp values.
The JDBC escape syntax is defined as
yyyy-mm-dd hh:mm:ss.fffffffff, where fffffffff indicates
nanoseconds.
This doesn’t define any point in time. It’s a mere date and time of day. What the documentation doesn’t even tell you is that the date and time of day is understood in the default time zone of the JVM.
I suppose that the reason for seeing a Timestamp in this way comes from the SQL Timestamp datatype. In most database engines this is a date and time without time zone. It’s not a timestamp, despite the name! It doesn’t define a point in time, which is the purpose of and is in the definition of timestamp.
I have seen a number of cases where the Timestamp prints the same date and time as in the database, but doesn’t represent the point in time implied in the database. For example, there may be a decision that “timestamps” in the database are in UTC, while the JVM uses the time zone of the place where it’s running. It’s a bad practice, but it is not one that will go away within a few years.
This must also have been the reason why Timestamp was fitted with the toLocalDateTime method that you used in the question. It gives you that date and time that were in the database, right? So in this case your conversion in the question ought to be correct, or…?
Where this can fail miserably without us having a chance to notice is, as others have mentioned already, when the default time zone of the JVM is changed. The JVM’s default time zone can be changed at any time from any place in your program or any other program running in the same JVM. When this happens, your Timestamp objects don’t change their point in time, but they do tacitly change their time of day, sometimes also their date. I’ve read horror stories — in Stack Overflow questions and elsewhere — about the wrong results and the confusion coming out of this.
Solution: don’t use Timestamp
Since JDBC 4.2 you can retrieve java.time types out of your SQL database. If your SQL datatype is timestamp with time zone (recommended for timestamps), fetch an OffsetDateTime. Some JDBC drivers also let you fetch an Instant, that’s fine too. In both cases no time zone change will play any trick on you. If the SQL type is timestamp without time zone (discouraged and all too common), fetch a LocalDateTime. Again you can be sure that your object doesn’t change its date and time no matter if the JVM time zone setting changes. Only your LocalDateTime never defined a point in time. Conversion to LocalDate is trivial, as you have already demonstrated in the question.
Links
java.sql.Timestamp documentation
Wikipedia article: Timestamp
Question: Getting the date from a ResultSet for use with java.time classes
Question: Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
As you can see here(taken from https://stackoverflow.com/a/32443004/1398418):
Timestamp represents a moment in UTC and is the equivalent of the modern Instant.
When you do:
timeStamp.toLocalDateTime().toLocalDate();
the timeStamp is converted from UTC to the system timezone. It's the same as doing:
timeStamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
For example:
Timestamp stamp = new Timestamp(TimeUnit.HOURS.toMillis(-1)); // UTC 1969-12-31
System.setProperty("user.timezone", "EET"); // Set system time zone to Eastern European EET - UTC+2
stamp.toLocalDateTime().toLocalDate(); // represents EET 1970-01-01
stamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // represents EET 1970-01-01
That result (getting the date in the system time zone) is expected and if that's what you want, doing timeStamp.toLocalDateTime().toLocalDate() is appropriate and correct.
You're saying that you have a LocalDate field in some object and you want to get a period between it and a Timestamp, well that's just not possible without aditional information. LocalDate just represents a date, it has no time zone information, you need to know how it was created and what time zone was used.
If it represent a date in the system time zone then getting the period by using timeStamp.toLocalDateTime().toLocalDate() would be correct, if it represents a date in UTC or any other time zone then you might get a wrong result.
For example if the LocalDate field represents a date in UTC you will need to use:
timeStamp.toInstant().atZone(ZoneId.of("UTC")).toLocalDate();
Example: the 23rd of January becomes the 24th
You asked:
Can someone give an example when this conversion fails?.
Yes, I can.
Start with the 23rd of January.
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.of( 23 , 0 );
ZoneId zMontreal = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , zMontreal );
Instant instant = zdt.toInstant();
zdt.toString() = 2020-01-23T23:00-05:00[America/Montreal]
instant.toString() = 2020-01-24T04:00:00Z
The Instant class represents a moment as seen in UTC. Let's convert to the terribly legacy class java.sql.Timestamp using the new conversion method added to that old class.
// Convert from modern class to troubled legacy class `Timestamp`.
java.sql.Timestamp ts = Timestamp.from( instant );
ts.toString() = 2020-01-23 20:00:00.0
Unfortunately, the Timestamp::toString method dynamically applies the JVM’s current default time zone while generating text.
ZoneOffset defaultOffset = ZoneId.systemDefault().getRules().getOffset( ts.toInstant() );
System.out.println( "JVM’s current default time zone: " + ZoneId.systemDefault() + " had an offset then of: " + defaultOffset );
JVM’s current default time zone: America/Los_Angeles had an offset then of: -08:00
So Timestamp::toString misreports the object’s UTC value after adjusting back eight hours from 4 AM to 8 PM. This anti-feature is one of several severe problems with this poorly designed class. For more discussion of the screwy behavior of Timestamp, see the correct Answer by Ole V.V.
Let's run your code. Imagine at runtime the JVM’s current default time zone is Asia/Tokyo.
TimeZone.setDefault( TimeZone.getTimeZone( "Asia/Tokyo" ) );
LocalDate localDate = ts.toLocalDateTime().toLocalDate();
Test for equality. Oops! We ended up with the 24th rather than the 23rd.
boolean sameDate = ld.isEqual( localDate );
System.out.println( "sameDate = " + sameDate + " | ld: " + ld + " localDate: " + localDate );
sameDate = false | ld: 2020-01-23 localDate: 2020-01-24
See this code run live at IdeOne.com.
So what is wrong with your code?
Never use java.sql.Timestamp. It is one of several terrible date-time classes shipped with the earliest versions of Java. Never use these legacy classes. They have been supplanted entirely by the modern java.time classes defined in JSR 310.
You called toLocalDateTime which strips away vital information. Any time zone or offset-from-UTC is removed, leaving only a date and a time-of-day. So this class cannot be used to represent a moment, is not a point on the timeline. Ex: 2020-12-25 at noon — is that noon in Delhi, noon in Düsseldorf, or noon in Detroit, three different moments several hours apart? A LocalDateTime is inherently ambiguous.
You ignored the crucial issue of time zone in determining a date. For any given moment, the date varies around the globe. At one moment it may be “tomorrow” in Australia while simultaneously “yesterday” in Mexico.
The problem lies in what is being represented by these objects. Your question forgets a crucial aspect, which is: What is the type of timeStamp?
I'm guessing it's a java.sql.Timestamp object.
Timestamp, just like java.util.Date, is old API equivalent to Instant.
It represents an instant in time, in the sense that it is milliseconds since jan 1st 1970 UTC. The system has no idea which timezone that was supposed to be in. You're supposed to know; the error, if an error is going to occur here, already occurred before you get to this code. Here's a trivial explanation of how it COULD go wrong:
you start off with a user entering a date in a date field on a webform; it's 2020-04-01.
Your server, running in Amsterdam, saves it to a DB column that is internally represented as UTC, no zone. This is a mistake (you're not saving an instant in time, you're saving a date, these two are not the same thing). What is actually stored in the DB is the exact moment in time that it is midnight, 2020-04-01 in amsterdam (in UTC, that'd be 22:00 the previous day!).
Later, you query this moment in time back into a java.sql.Timestamp object, and you're doing this when the server's tz is elsewhere (say, London time). You then convert this to a localdatetime, and from there to a localdate, and.... you get 2020-03-31 out.
Whoops.
Dates should remain dates. Never convert LocalX (be it Time, Date, or DateTime) to Instant (or anything that effectively is an instant, including j.s.Timestamp, or j.u.Date - yes, j.u.Date does NOT represent a date, it is very badly named), or vice versa, or pain will ensue. If you must because of backward APIs take extreme care; it's hard to test that 'moving the server's timezone around' breaks stuff!

How do I tell Joda Time that the time I am giving it is for a specific time zone offset, even though the offset isn't given in the String?

I am working with an API that provides me with a ModifyDate field that is being given in CST (-06:00), but when passing the string in to Joda time and setting the time zone to America/Phoenix, Joda time thinks that the date/time I gave it is in UTC time zone because there is no offset information being given by the API (the time being returned is in CST, confirmed with the developers).
Side note: I am in Arizona where we do not recognize daylight savings time, so I can't just apply a static offset of -1 hour.
Here's an example of what I'm dealing with:
Field returned by the API:
"modifyDate": "2020-02-11T12:23:39.817Z"
Trying to format the date with Joda time:
DateTime time1 = new DateTime("2020-02-11T12:23:39.817Z", DateTimeZone.forID("CST6CDT"));
System.out.println(time1);
DateTime time2 = new DateTime(time1, DateTimeZone.forID("America/Phoenix"));
System.out.println(time2);
System.out.println("----------------------------");
DateTime time3 = DateTime.parse("2020-02-11T12:23:39.817Z");
System.out.println(time3);
System.out.println(time3.toInstant());
System.out.println(time3.withZone(DateTimeZone.forID("America/Phoenix")));
System.out.println(time3.toDateTimeISO());
System.out.println(time3.toDate());
System.out.println("--------------------------------");
Output:
2020-02-11T06:23:39.817-06:00
2020-02-11T05:23:39.817-07:00
----------------------------
2020-02-11T12:23:39.817Z
2020-02-11T12:23:39.817Z
2020-02-11T05:23:39.817-07:00
2020-02-11T12:23:39.817Z
Tue Feb 11 05:23:39 MST 2020
--------------------------------
As you can see in the first two outputs, by trying to apply the time zone for CST, the time provided is offset by -6 (to be expected if the time provided was in UTC). By setting the time zone to America/Phoenix, the offset is -7 (also to be expected). However, as I mentioned, the time that I am passing into DateTime is not UTC, it is CST.
How can I tell DateTime (or even some other library, for that matter) that the time being provided is in CST? Again, keeping in mind that when daylight savings time changes, the offset needs to be managed properly.
In this case, the time being provided by the API was incorrectly being provided as UTC, even though the time is CST, as pointed out by OleV.V. The cleanest solution to this problem was to use DateTime.withZoneRetainFields(), as mentioned by shmosel.
For whatever reason, however, if I created the DateTime object by using the constructor, I couldn't adjust the time zone with withZoneRetainFields(), instead, I had to use DateTime.parse().
I adjusted for the time zone being off by using the following logic:
DateTime time4 = DateTime.parse("2020-02-11T12:23:39.817Z").withZoneRetainFields(DateTimeZone.forID("CST6CDT"));
System.out.println(time4);
System.out.println(time4.withZone(DateTimeZone.forID("America/Phoenix")));
Output (correct)
2020-02-11T12:23:39.817-06:00
2020-02-11T11:23:39.817-07:00
Hopefully, this will help someone else if they come across the same problem.

Convert a timestamp before year 1900 in java

My Android app communicate with an API which give me the following timestamp : -2209161600. Converted to a date time, it's supposed to be 12-30-1899 00:00:00
The problem is, I tried to convert this timestamp using both the default library, threetenbp, and then jodatime, but I always get the same wrong result, using Europe/Paris timezone : 12-30-1899 00:09:21
Why does that happen ?
EDIT: For example with jodatime
DateTime dt = new DateTime(-2209161600000L, DateTimeZone.forID("Europe/Paris")); // dt: "1899-12-30T00:09:21.000+00:09:21"
I think I found the answer on the FAQ as part of Why is the offset for a time-zone different to the JDK?:
... affects date-times before the modern time-zone system was introduced. The time-zone data is obtained from the time-zone database. The database contains information on "Local Mean Time" (LMT) which is the local time that would have been observed at the location following the Sun's movements.
Joda-Time uses the LMT information for all times prior to the first time-zone offset being chosen in a location. ...
In other words, the database does not have entries for that time so it is uses the Local Mean Time (e.g. 0:09:21 for Paris, or -0:14:44 for Madrid 1).
System.out.println(new DateTime(-2209161600000L, DateTimeZone.forID("Europe/Paris")));
System.out.println(new DateTime(-2209161600000L, DateTimeZone.forID("Europe/Madrid")));
will print
1899-12-30T00:09:21.000+00:09:21
1899-12-29T23:45:16.000-00:14:44
Solution: depends what tis time is needed for, if UTC is sufficient, use
new DateTime(-2209161600000L, DateTimeZone.forID("UTC")) // 1899-12-30T00:00:00.000Z
or just the standard java.time classes like
Instant.ofEpochSecond(-2209161600L)
Instant.ofEpochMilli(-2209161600000L)
1 - http://home.kpn.nl/vanadovv/time/TZworld.html#eur
Carlos Heuberger may have said it already. As far as I can see, it’s a matter of using UTC instead of Europe/Paris time zone.
long unixTimestamp = -2_209_161_600L;
Instant inst = Instant.ofEpochSecond(unixTimestamp);
System.out.println("As Instant: " + inst);
Output is:
As Instant: 1899-12-30T00:00:00Z
If you need date and time:
OffsetDateTime dateTime = inst.atOffset(ZoneOffset.UTC);
System.out.println("As OffsetDateTime: " + dateTime);
As OffsetDateTime: 1899-12-30T00:00Z
Am I missing something?
Explanation
Why does it matter? Because in 1899 Paris used the local mean time in Paris, which is at offset +00:09:21 from UTC. Therefore the correct and expected result in Europe/Paris time zone is the one you got, 12-30-1899 00:09:21. To check this offset: Go to Time Zone in Paris, Île-de-France, France. In the Time zone changes for dropdown choose 1850 – 1899. You will see that the offset of +00:09:21 was in effect during this entire interval if years (both before and after the change of time zone abbreviation in 1891).

Java Date confusion

I am really very confused about dates.
I have a web application which has a backend on Java. I have a date field which has no time information. I write this info to DB using hibernate. Its definition is
#Column
#Temporal(TemporalType.DATE)
private Date startDate;
Actually these are basic infos. Here is my problem.
I run java on UTC(GMT+0) timezone but client timezone is GMT+3.
A user picks a day (let's say 10.12.2016 (dd.MM.yyyy)). On client side this day represented as 10.12.2016 00:00 GMT+3. After this user wants to save this date.
This date comes to my backend as 09.12.2016 21:00 GMT+0. I want to save this date without timeinfo. So java code saves data as 09.12.2016. (which corresponds to 09.12.2016 00:00 GMT+0)
Now user wants to see what s/he saved. Java reads date as 09.12.2016 00:00 GMT+0 and client gets this date as 09.12.2016 03:00 GMT+3. At the end, the user sees date as 09.12.2016 but s/he saved this date as 10.12.2016.
So the user sees the day, one day before s/he actually saved.
I want to keep data as TemporalType.DATE and want to show user the correct date.
How can I solve this problem? I keep digging for 2 days on Google but I could not find a reasonable explanation.
NOTE:** My users can be different timezones not only on GMT+3. Please consider this situation.
I suppose that, if your application is aware of client's GMT you are receiving the GMT position from request so I won't go deep into this point because it would be needed further information about what your application does.
Once you have request's GMT info, you only have to do this:
Date databaseDate = database's date (you said you were working in GMT and it was also stored in GMT 0 so no further transformation is required)
//As you said you are working in GMT 0 this will be the GMT of your calendar.
Calendar calendar = new GregorianCalendar();
//As you know your GMT you only have to set it through this method
TimeZone clientTimezone = TimeZone.getTimeZone("GMT-1:00");
Date originalDate = inputCalendar.getTime();
calendar.setTimeZone(clientTimezone);
//This will return you the offset -positive or negative- of calendars time zone respect to UTC 0 - GMT 0 - in milliseconds
int offset = inputCalendar.getTimeZone().getOffset(originalDate.getTime());
//You get the database's date in milliseconds and add your offset to it. It can be positive or negative and the final result will the date transformed to your request's GMT
Date clientDate= (new Date(originalDate.getTime() + offset));
And it would be enough for your requirements.
Several options available for you.
If you are working with Java 8 (or can switch to java 8) then new
package java.time provides much more flexible date model and class
LocalDate will give you precisely what you need.
If you work with java 7 or less I would sudjest to store your Date
in DB as a String field. I.e. format your Date to String and save it
that way to DB into varchar field. When you read it just parse it
back to Date. This way your "10.12.2016" will remain such in any
time zone without any additional trickery.
Finally, if you insist on keeping it as Date you might want to
inforce that all your Dates are always stored in GMT+0 regardless of
actiual location. This way user in the same location will always see
the same date. But two users in different locations still might see
the same field as a different dates
I would sugjest the 1st option if possible and if not then 2d. Third is would become more and more complex if your app is used by users in dfferent time zones.
Also you might find this article interesting: It is about converting a String of unknown format to Date: Parsing any string to Date
Some general info about your date-time issues, not Hibernate specific…
What time zone is the current default on your server should be irrelevant. Always specify your desired/expected time zone in your code by always passing the optional time zone argument. Discussed many times on Stack Overflow, so search for more info.
LocalDate todayMontreal = LocalDate.now( ZoneId.of( "America/Montreal" ) ) ;
A date-only value without time zone is indeed imprecise. For any given moment, the date varies around the world by zone. A few minutes after midnight in Paris France is a new day while still ‘yesterday’ in Montréal Canada. Discussed many times on Stack Overflow, so search for more info.
If you want precision use a date-time with time zone. Discussed many times on Stack Overflow, so search for more info.
ZonedDateTime todayMontrealStart = todayMontreal.atStartOfDay( ZoneId.of( "America/Montreal" ) ) ;
…or…
ZonedDateTime todayMontrealStart = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
For storing data, make that time zone UTC. The Instant class is always in UTC by default.
Instant instant = todayMontrealStart.toInstant();
Your JDBC driver and/or database may do this for you. With JDBC 4.2 and later, pass java.time objects via getObject and setObject, otherwise fall back to java.sql types for exchanging data with database. Discussed many times on Stack Overflow, so search for more info.
Avoid the troublesome old date-time classes such as java.util.Date and .Calendar. Now legacy, supplanted by the java.time classes. Discussed many times on Stack Overflow, so search for more info.
Specifically, you should search Stack Overflow for the classes LocalDate, Instant, OffsetDateTime, and ZonedDateTime.
Bonus tip: when exchanging date-time values as text such as between your client and server, use ISO 8601 standard formats. Discussed many times on Stack Overflow, so search for more info.

Hibernate not providing date in UTC timezone

This seems like a stupid question, but I am not able to understand this creepy behavior. I am completely aware of the fact that java Date class does not store any TimeZone information in it. It just stores the number of milliseconds since January 1, 1970, 00:00:00 GMT
Thing is that, I am using MySql which is residing on a server with UTC timezone and I am also storing DateTime in UTC only. If I make a select query then I get this date 2014-01-17 16:15:49
By using http://www.epochconverter.com/ I get this:
Epoch timestamp: 1389975349
Timestamp in milliseconds: 1389975349000
Human time (GMT): Fri, 17 Jan 2014 16:15:49 GMT
Human time (your time zone): Friday, January 17, 2014 9:45:49 PM
Now comes the part of Hibernate. I am running my Java web app on a machine having IST as system timezone. I made a simple object fetch using Id and fetched createdDate property which is a Date object. I have wrote a simple code to understand its output, here is the code:
Date dt = c.getCreatedDate();
System.out.println(dt.getTime());
System.out.println(dt);
DateFormat df = new SimpleDateFormat("dd/MM/yyyy hh:mm a z");
df.setTimeZone(TimeZone.getTimeZone("IST"));
System.out.println(df.format(dt));
df.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(df.format(dt));
And following is the output for this:
1389955549000
2014-01-17 16:15:49.0
17/01/2014 04:15 PM IST
17/01/2014 10:45 AM UTC
If you put this 1389955549000 in http://www.epochconverter.com/ then you get following output:
GMT: Fri, 17 Jan 2014 10:45:49 GMT
Your time zone: Friday, January 17, 2014 4:15:49 PM GMT+5.5
This is not the expected output, right. It is giving me time in millis which is -5:30 from the UTC time, so If I try to get time in IST timezone then it actually gives me time which is in UTC
Does anyone got idea where I am doing wrong?
--------------------------How I fixed it----------------------------
Based on suggestions from - Ako and Basil Bourque
Hibernate takes system timezone into consideration while fetching date/time fields from database. If you have stored DateTime in UTC in database but your system time or for least your java app timezone is in other timezone(e.g, IST - Indian Standard Time) then hibernate thinks that DateTime stored in database is also in IST and this is what causes whole problem.
For this as Basil suggested, use same timezones accross different servers. UTC should be preferred. Fix that I applied is that I added following code:
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
in ServletContextListener which made my app timezone in UTC and now hibernate is fetching and storing dates as expected.
Confusing Question
Your question could use some rewriting.
If I make a select query – You should explain this and give the exact query statement.
Red Herring
Since dealing with two separate servers (database server, web app server), each with a different time zone setting, you should separate the question more cleanly. Indeed, the MySQL server seems to be just a red herring, a distraction.
Instead of talking about the irrelevant MySQL server, you should have tested and reported the actual time. Easy to do… Just google "current time in utc".
Thus the old sailors' adage: Use one compass or three, but never two.
Time Zones
Three-letter time zone codes are outmoded, being neither standardized nor unique. "IST" means "India Standard Time" and "Irish Standard Time", for example.
Use time zone names, as seen in this slightly outdated list. In your case, +05:30, you could use "Asia/Kolkata", also known as "Asia/Calcutta" in the older tables. That is 5.5 hours ahead of UTC/GMT.
Joda-Time
The java.util.Date & Calendar classes are notoriously bad and confusing, as you've come to see. Avoid them. Use either the open-source third-party Joda-Time or, in Java 8, the new java.time.* classes (inspired by Joda-Time).
The code below uses Joda-Time 2.3 and Java 8 on a Mac with US west coast time zone.
Baseline
Let's establish that 1389975349000L ≈ 16:15 UTC ≈ 21:45 India.
This agrees with EpochConverter.com, as the question stated.
// Specify a time zone rather than depend on defaults.
DateTimeZone timeZoneKolkata = DateTimeZone.forID( "Asia/Kolkata" );
long millis = 1389975349000L;
DateTime dateTimeUtc = new DateTime( millis, DateTimeZone.UTC );
DateTime dateTimeKolkata = dateTimeUtc.toDateTime( timeZoneKolkata );
Dump to console…
System.out.println( "millis: " + millis );
System.out.println( "dateTimeUtc: " + dateTimeUtc );
System.out.println( "dateTimeKolkata: " + dateTimeKolkata );
When run…
millis: 1389975349000
dateTimeUtc: 2014-01-17T16:15:49.000Z
dateTimeKolkata: 2014-01-17T21:45:49.000+05:30
Mystery Number
The question mentions a second number: 1389955549000L.
That number turns out to be the same date as the first number, with different time.
Let's establish that 1389955549000L ≈ 10:45 UTC ≈ 16:15 India.
long mysteryMillis = 1389955549000L;
DateTime mysteryUtc = new DateTime( mysteryMillis, DateTimeZone.UTC );
DateTime mysteryKolkata = mysteryUtc.toDateTime( timeZoneKolkata );
Dump to console…
System.out.println( "mysteryMillis: " + mysteryMillis );
System.out.println( "mysteryUtc: " + mysteryUtc );
System.out.println( "mysteryKolkata: " + mysteryKolkata );
When run…
mysteryMillis: 1389955549000
mysteryUtc: 2014-01-17T10:45:49.000Z
mysteryKolkata: 2014-01-17T16:15:49.000+05:30
Conclusion
I'm not 100% sure, but…
→ Your web app server machine seems to have its clock set improperly, set to UTC time rather than India time.
The web app server is apparently let to 16:15 time in the India time zone, but apparently at that moment the true time in India was 21:45. In other words, the time did not match the time zone.
Mixing UTC time with non-UTC time zone = WRONG.
If you set an Indian time zone, then set an Indian time to match.
Details
Note that we have "16:15" in common in both sets of numbers.
The java.util.Date class has a very bad design where it has no time zone information itself BUT applies the Java Virtual Machine's default time zone in its implementation of toString. This is one of many reasons to avoid this class, but may be key to your problem.
Lessons Learned
Avoid java.util.Date, java.util.Calendar, and java.text.SimpleDateFormat classes. Use only as required for exchanging values as needed with other classes.
Use Joda-Time or the new java.time.* classes (JSR 310).
Specify a time zone, never rely on default time zone.
Match the time to time zone on each server. Verify by googling "current time in utc".
Set host server’s operating system’ time zone to UTC or GMT where possible. No such choice on some OSes, so choose "Atlantic/Reykjavik" as a workaround, because Iceland stays on UTC/GMT year-round without any Daylight Savings Time nonsense.
Do not set the JVM’s default time zone with a call to TimeZone.setDefault (except as a last resort in the worst situations). Setting the default is rude, as it immediately affects all the code in all the threads in all the apps running in that JVM. And it is unreliable as any of that other code can change it on your app, during runtime. Instead, specify a time zone in all your date-time code. Never rely implicitly on the JVM’s current default. Both Joda-Time and java.time have methods that take an argument of time zone. So, while it is a good practice to set all your server computers’ host OS’ time zone to UTC, you should never depend on that in your Java code.
There is another option of solving the time zone shift issue; in this case you don't have to put your entire JVM in the UTC time zone (it is not a good idea anyway; for example, you might want to share JVM across multiple Java applications and such a hacky solution can cause problems in the future).
So, there is a small open source project DbAssist, which provides a clean and elegant solution to this problem. Internally, it maps the java.util.Date fields in your entities to a custom UtcDateType. The custom type forces JDBC (and later Hibernate) to treat the dates in the database as UTC. There are different versions of this fix for different versions of Hibernate (its API was changed a couple of times between versions), so you have to pick a correct one according to the table here.
On the same page, you can also find the detailed instructions how to install and apply the fix. Generally, if you are using for example Hibernate 5.2.2, just add the following lines to your POM file:
<dependency>
<groupId>com.montrosesoftware</groupId>
<artifactId>DbAssist-5.2.2</artifactId>
<version>1.0-RELEASE</version>
</dependency>
Then, the application of the fix differs between JPA Annotations and HBM files setup, so I will present only example for one possible setup; for JPA Annotations case (without Spring Boot), just add this line to the persistence.xml file between <persistence-unit> tag:
<class>com.montrosesoftware.dbassist.types</class>
Now the dates in your entities are treated as UTC, when read/saved to the DB. If you want to learn more about the very essence of the date and timezone problem in Java/Hibernate, you can read this article explaining it with more details.

Categories