Comparing two date objects of different TimeZones and get the exact time difference in seconds

Hemanth Annavarapu picture Hemanth Annavarapu · Jun 23, 2017 · Viewed 7.2k times · Source

I am trying to compare received date of an email(date object) with a date(String) in a JSONObject. The two dates are going to be of different Timezones with a difference of 1 hour.

My scenario is that I'll have to get the exact difference between them in seconds and if the difference is +/- 10 seconds, I would have found a match. For this purpose, I decided to convert the both into 'UTC' and then do a difference. Here is my code:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

String MsgDtStr = sdf.format(messagesToBeProcessed.getReceivedDate()); 

Date MsgDt = sdf.parse(MsgDtStr);

Date ObjDt = sdf.parse((JsonObj.get("date").toString()));

int DiffInSecs = (int) (ObjDt.getTime() - MsgDt.getTime()) / 1000 % 60;

if (DiffInSecs >= -10 && DiffInSecs <= 10) {
                System.out.println("Found a match!");
                MatchedList.add(i);
}

This seems to give me the correct result. The mails that are supposed to match with the json objects match. i.e the difference is +/- 10 seconds. However, when I try to print the dates and DiffInSecs after the above logic, I get values like this:

date in object: 2017-06-22 19:47:25
date in message: 2017-06-22 23:47:30
DiffInSecs: -5

        ........

date in object: 2017-06-22 17:50:38
date in message: 2017-06-22 21:50:39
DiffInSecs: -1

I see that there is a difference of 4 hours between the times. Now I am confused how my logic is even working. Any explanations on this? and is my approach to the problem even correct?

These are my sample date values of a mail and a jsonobject that I should work with and these should match: (How can I achieve this in any way, not just the aproach I posted)

messagesToBeProcessed[i].getReceivedDate(): Thu Jun 22 01:03:13 CDT 2017 messagesToBeProcessed[i].getReceivedDate().getTime(): 1498111393000

Obj.get('date'): 2017-06-22 02:03:03 ObjDt.getTime(): 1498096983000

EDIT:

As pointed out by JBNizet in his comment, I'll need to remove the %60 to get the exact difference in seconds.

Okay, now I feel sticking to the old API doesn't make sense. Now my date in the message object is not being converted to UTC anymore like I posted in the above result. I am not sure why it worked earlier and doesn't anymore (Any explanations/comments on this are welcome). As pointed out by Hugo in his answer, using new Java.Time API is better.

Hope this thread helps someone find way in the future.

Answer

user7605325 picture user7605325 · Jun 23, 2017

I'm not sure which java version you're using, but anyway: the old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by new Date/Time APIs.

If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.

If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.


Based on your output for getReceivedDate(), your default timezone is CDT. But these short timezone names like EST and CDT are ambiguous and not standard.

The new API uses long names in the format Continent/City (used by IANA database. So, CDT can be in lots of different locations, like America/Chicago or America/Havana. You must first check which one fits best in your context - you can get a list of all available names with ZoneId.getAvailableZoneIds().

For the example below, I've picked one of them by random (America/Chicago) just to show how it works. I've used a DateTimeFormatter to parse the input String and a ZoneId to convert it to a timezone.

// parse jsonInput
String jsonInput = "2017-06-22 02:03:03";
// formatter
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// parse the input to a LocalDateTime
LocalDateTime dt = LocalDateTime.parse(jsonInput, fmt); // 2017-06-22T02:03:03
// convert it to the EDT timezone (using America/Chicago as it's currently in CDT)
ZonedDateTime z = dt.atZone(ZoneId.of("America/Chicago")); // 2017-06-22T02:03:03-05:00[America/Chicago]

// convert the java.util.Date object to the new API
Date date = new Date(1498111393000L); // using your timestamp = 2017-06-22T06:03:13 UTC
// for Java >= 8, use toInstant method
Instant instant = date.toInstant();
// for Java <= 7, use org.threeten.bp.DateTimeUtils
Instant instant = DateTimeUtils.toInstant(date);

// get the difference between them
long diffSecs = ChronoUnit.SECONDS.between(instant, z);

The difference will be 3590 seconds - that's because 2017-06-22 02:03:03 in CDT (in my example, Chicago) is equals to UTC 2017-06-22T07:03:03Z. And your date (1498111393000 millis) is equals to UTC 2017-06-22T06:03:13Z.

In your code, you're using sdf to parse the input 2017-06-22 02:03:03, but sdf is set to UTC (not to CDT), leading to incorrect results (assuming that the JSON input is in CDT as well).


If you want to convert the JSON input to UTC, you can use ZoneOffset.UTC instead of the ZoneId.

Actually, you must check if the input is really in UTC or not and change the code accordingly (the code above assumes that the JSON input is in CDT).


Of course you can do the same with SimpleDateFormat:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Chicago"));

Date jsonDate = sdf.parse("2017-06-22 02:03:03"); // 2017-06-22 02:03:03 in Chicago, or 2017-06-22T07:03:03 in UTC

And then calculate the difference the same way you're doing ((jsonDate.getTime() - date.getTime()) / 1000). The result will be the same as above: 3590 seconds.