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.
Related
An old Stack Overflow posting suggests that the way to get the UTC timestamp in Java is the following:
Instant.now() // Capture the current moment in UTC.
Unfortunately this does not work for me. I have a very simple program (reproduced below) which demonstrates different behavior.
On Windows: the time is the local time and it is labeled with the offset with GMT
On Linux: the time is again the local time, and it is labeled correctly for the local timezone
Question: How do we display the UTC timestamp in a Java program?
My sample source code is as follows:
import java.time.Instant;
import java.util.Date;
public class UTCTimeDisplayer {
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
Date currentUtcTime = Date.from(Instant.now());
System.out.println("Current UTC time is " + currentUtcTime);
}
}
Windows Output:
C:\tmp>java UTCTimeDisplayer
Windows 10
Current UTC time is Fri Jan 22 14:28:59 GMT-06:00 2021
Linux Output:
/tmp> java UTCTimeDisplayer
Linux
Current UTC time is Fri Jan 22 14:31:10 MST 2021
Your code:
Date.from(Instant.now())
You are mixing the terrible legacy classes with their replacement, the modern java.time classes.
Don’t.
Never use Date. Certainly no need to mix with java.time.Instant.
To explain your particular example, understand that among the Date class’ many poor design choices is the anti-feature of its Date#toString method implicitly applying the JVM’s current default time zone while generating its text.
You ran your code on two different JVMs that had different current default time zones. So you got different outputs.
Sun, Oracle, and the JCP gave up on the legacy date-time classes. So should we all. I recommend you not spend time trying understand Date, Calendar, SimpleDateFormat, and such.
You asked:
Question: How do we display the UTC timestamp in a Java program?
Instant.now().toString()
See that code run live at IdeOne.com.
2021-01-22T21:50:18.887335Z
You said:
On Windows: …
On Linux: …
You’ll get the same consistent results from Instant.now().toString() across Windows, Linux, BSD, macOS, iOS, Android, AIX, and so on.
Here is a table I made to guide you in transitioning from the legacy classes.
The java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT (or UTC). When you print an object of java.util.Date, its toString method returns the date-time in the JVM's timezone, calculated from this milliseconds value. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFormat and obtain the formatted string from it.
I would suggest you simply use Instant.now() which you can convert to other java.time type.
The date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.
For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7.
If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
However, if you still want to use java.util.Date, use SimpleDateFormat as mentioned above.
Demo:
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) {
Date currentUtcTime = Date.from(Instant.now());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
System.out.println("Current UTC time is " + sdf.format(currentUtcTime));
}
}
Output:
Current UTC time is 2021-01-22 21:53:07 UTC
suggests that the way to get the UTC timestamp in Java is the following:
Instant.now() // Capture the current moment in UTC.
This, and most answers in this thread, are misleading.
Instant represents an instant in time. It's 'solarflares' time: Absolutely not one iota about it represents anything that is invented by human brains, and UTC is a timezone: A human invention. The cosmos, the sun, astronomy - they have no idea what UTC is, and don't care - and that's what Instant is all about. Instants are devoid of such human concepts as 'hours' or 'days' or 'timezones'. It makes no sense to ask an instant what day it happened on. It cannot tell you; some event occurred: If I ask a russian from the 19th century when that happened, they'll likely give a completely different answer vs. if I ask someone living a mere 100 miles west, for example. Instant doesn't know which localization to apply and thus doesn't let you ask it this question - that's a good thing, objects should not expose methods to which any answer it gives is either gobbledygook or at least requires knowing about all sorts of surprising caveats.
Crucially, if you tell me '... in UTC', you surely can tell with exacting detail which month, which day, etcetera. And Instant does not do this, which is why it is misleading to say that a java.time.Instant represents a moment of time in UTC. It doesn't. It represents a moment in time (not in any particular timezone).
Yeah, internally Instant, just like Date, is just a light wrapper around what System.currentTimeMillis() returns: "millis since epoch", but the crucial thing to understand about it, is that 'UTC' is not part of what it means, and therefore, when you give an Instant instance to some other method (such as System.out.println, to a database via JDBC, etc), that method is under absolutely no obligation to assume that UTC is semantically relevant.
When you want to mix human notions of time keeping (years, days, months, hours, minutes, milliseconds, and, yeah, timezones) with the notion of a more or less absolute* time, the right answer is java.time.ZonedDateTime. Note that any representation of time in something that isn't java.time.* based is by definition broken, as it is in most programming languages - turns out time is a lot more complex than most takes on a library to represent it realize. The fact that java.time is in effect the 4th attempt at writing a time library should be ample indication that it's hard to get it right.
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
THAT is what you want - that isn't just implementation-detail-wise what you want, but it is code that exactly describes what you mean: Right now, at the UTC time zone, stored in an object that semantically doesn't just store the right time but also stores, and tightly entangles into its very identity, that it is specifically in UTC and is not to be re-interpreted, moved to the local zone, or any other such shenanigans - at least, not unless you explicitly ask it to do so.
Date currentUtcTime = Date.from(Instant.now());
Note that Date is the old API and therefore necessarily broken. In this case, Date is a lying liar who lies - it doesn't represent a date, it represents an instant; it is badly named. (The second API is Calendar, also broken. For example, that is also a lying liar who lies: It doesn't represent a Calendar whatsoever. It represents some bizarre amalgamation of a zoned datetime and an instant and is fit to represent neither as a consequence). Any time you go to the Date API weirdness ensues, and something as simple as 'I just want the concept of the time, at some specific moment, in UTC' isn't possible in these APIs. You are now dependent on barely defined behaviour of all the various libraries up and down the chain - you're effectively stuck praying that they do the right thing, or delving into exotic settings to try to cajole these libraries into doing what you want.
TL;DR: Use java.time.
*) Note that ZonedDateTime is not absolute. For example, if you have the time January 20th, 2023, 8 in the morning, at Europe/Amsterdam, in the form of a ZonedDateTime object, then the amount of seconds that will pass between now and that moment sure seems like it does not change and will not change when e.g. amsterdam goes through an hour change due daylight savings. However, if the dutch government decrees that henceforth The Netherlands will no longer move the clocks at all and will stay in summer time forever (which is likely - EU directive is already in place, it's now just a matter of when), then at the moment the gavel lands, your appointment shifts by 1 hour exactly.
That hopefully provides crucial insight in the difference: Instant, representing events (hence why I like to call it 'solarflares time', to disentangle it from human time keeping concepts as much as possible), doesn't even understand the very concept of such a decision having an effect on things. ZonedDateTime on the other hand is inherently bound up in it - hence the Zone in ZonedDateTime.
If you want to store barber appointments and use Instant to do it, you WILL be an hour late or early sooner rather than later.
An Instant object and also a Date object by themselves
only contain a point in time, but no timezone information.
Furthermore, the toString() method of the Date class
implicitly chooses the timezone provided by the system environment,
which is not what you want.
Therefore you need to chose the timezone (in your case UTC) explicitly.
For example like this:
Instant instant = Instant.now();
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC);
System.out.println("Current UTC time is " + offsetDateTime);
This will (independently from the operation system) print
Current UTC time is 2021-01-22T22:37:21.950354100Z
where the trailing Z denotes the zero timezone offset (i.e. UTC).
A simple method that could work!
My requirement was date time with milliseconds
2021-11-25 19:55:00.743
private String getUTCTimestamp() {
ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC);
return utc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}
Instant.now() is essentially the period of time since the epoch, (midnight 1 January 1970 in UTC), but you are using a Date to present that instant. Date is a reflection of the instant with millisecond precision, but as explained in the documentation at https://docs.oracle.com/javase/8/docs/api/java/util/Date.html, presenting a date should be done using a Calendar, as the presentation of a Date depends on the host. Essentially Date wraps the instant but is displayed according to other factors.
The simplest approach now if you want to output the instant is to use OffsetDateTime so that you can elect to present the instant in your desired timezone - UTC in your case. Use either OffsetDateTime.now() or OffsetDateTime.ofInstant() but if you are using the instant within your application logic then just stick with Instant.
Sometimes your program has to work with older java versions, so here is an example for 1.5:
java.text.SimpleDateFormat tfGMT = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Calendar cUTC = java.util.Calendar.getInstance (java.util.TimeZone.getTimeZone ("GMT+0"));
tfGMT.setCalendar (cUTC);
java.util.Date d= new java.util.Date ();
String s= tfGMT.format (d);
System.out.printf ("now=%s [unix ts=%d.%03d]\n", s, d.getTime()/1000, d.getTime()%1000);
Mind you, the first three lines don't have to be repeat at every call, but keep in mind that SimpleDateFormat is not thread-safe. (Simple solution: create one for each thread.)
Example usage (it shows that setting TZ doesn't affect UTC-timestamp):
$ TZ=GMT+3 java5 now_utc; TZ=GMT-3 java5 now_utc
now=2021-01-24 12:56:14 [unix ts=1611492974.264]
now=2021-01-24 12:56:14 [unix ts=1611492974.726]
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!
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).
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.
We have one of our application hosted on a application server installed on machine working in CST timings. Now in the application footer we are showing the Last Login time and date using java.util.date().toString();
Now from reference of this post I can say java.util.Date().toString() is using TimeZone.getDefault() method which, in turn, will default to the time zone of the operating system it is running on (i.e. the host).
So the application Should show the timings according to CST timzone but it is not happening only for this server, it is always showing GMT timzone.
Here is a attached screenshot showing time configuration and the results from ‘time’ command on the serve, as well as the application Admin UI, showing time in GMT which is visible in footer.
So I am looking for the possible causes as why java.util.date.toString() is showing this unexpected behavior and what is the solution for that?
As you have already recognized, the behaviour of java.util.Date.toString() is based on TimeZone.getDefault().
Use the user.timezone property value as the default time zone ID if it's available.
Detect the platform time zone ID. The source of the platform time zone and ID mapping may vary with implementation.
Use GMT as the last resort if the given or detected time zone ID is unknown.
The JVM might not be able to interprete the timezone of the underlying operating system, for example if the OS uses Windows timezone names which cannot be resolved to identifiers common in iana-tzdb.
Solution:
I suggest you to start the JVM with following system property:
-Duser.timezone=America/Chicago
Better use Joda-Time, a date-time object (DateTime) truly does know its assigned time zone. That means an offset from UTC and the rules and history of that time zone’s Daylight Saving Time (DST) and other such anomalies.
The standard date and time classes prior to Java SE 8 are poor. By tackling this problem head-on, Joda-Time became the de facto standard date and time library for Java prior to Java SE 8.
DateTimeZone dateTimeZone = DateTimeZone.forID("America/Chicago");
DateTime dateTime = DateTime.now(dateTimeZone);
System.out.println("Current time is: " + dateTime);
References:
Joda Time:
http://googleweblight.com/?lite_url=http://www.joda.org/joda-time/&ei=fiQH2-Y-&lc=en-IN&s=1&m=717&host=www.google.co.in&ts=1456818933&sig=ALL1Aj74vOgr-H7yxBKCAWq5KKQ28MTvvw
Time zones:
http://joda-time.sourceforge.net/timezones.html
A java.util.Date object represents a timestamp. A timestamp is a specific moment in time and has no concept of a timezone. When displaying a java.util.Date you need to format the time with respect to a given timezone.
The usual way to do this is using java.text.DateFormat:
Date d = new Date()
DateFormat dateForamt = DateFormat.getDateTimeInstance()
// The default timezone is the timezone that is default for the user, but it
// can be changed like so:
dateFormat.setTimeZone(someTimezone)
System.out.println(dateFormat.format(d))