5

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.

Hemanth Annavarapu
  • 823
  • 3
  • 19
  • 37
  • Your local timezone is -4 hours (four hours behind UTC). So first account for that offset, and then the differences are as shown. – Elliott Frisch Jun 23 '17 at 13:07
  • Thanks for the comment. Do I have to account for the offset for the date in the Json Object? because the date on the mail seems to be getting converted. And how can I do that? @ElliottFrisch – Hemanth Annavarapu Jun 23 '17 at 13:10
  • 2
    Why do you use `% 60` in your diff? If you have an actual difference of 4 hours and 1 second, that will change the difference to 1 second. Apparently, that's not what you want. So don't do it. Also, formatting and then parsing a date with the same SimpleDateFormat doesn't really make sense. Why are you doing that? Just use the Date you already have. – JB Nizet Jun 23 '17 at 13:19
  • 1
    Thanks for this! Makes sense. I'll have to remove the `%60` and the extra formatting step. Now my real problem is getting them converted to UTC and it still remains. @JBNizet – Hemanth Annavarapu Jun 23 '17 at 13:23
  • 2
    A Date doesn't have any timezone. It's just a number of milliseconds, representing a moment on the universal timeline. There's nothing to convert. You have a Date, that you don't need to touch. And you have a String, that apparently represents some time in UTC, so you parse it to a Date using a SimpleDateFormat whose timezone is set to UTC. If the timezone of the json date isn't UTC, but something else, then parse it with the appropriate timezone instead of UTC. – JB Nizet Jun 23 '17 at 13:27
  • Does the input String from JSON comes in the format `2017-06-22 23:47:30`? If it comes in this format, there's no indication of the timezone, can we assume it's UTC? –  Jun 23 '17 at 13:33
  • No, the input String in Json is not UTC. it's EDT. So, can I just parse it by setting the timezone as 'EDT' as pointed out by JBNizet and then do a compare with the date in the message? @Hugo – Hemanth Annavarapu Jun 23 '17 at 13:36
  • But what's the format? It's `2017-06-22 23:47:30`? Or `2017-06-22 23:47:30 EDT`? –  Jun 23 '17 at 13:37
  • There's no Timezone in the String. it's just `2017-06-22 23:47:30` @Hugo – Hemanth Annavarapu Jun 23 '17 at 13:38
  • So can I just parse my string from the jsonobject with the respective timezone to get a date and then just do the diffsecs? @JBNizet – Hemanth Annavarapu Jun 23 '17 at 13:49
  • Yes, that's what I told in my previous comment. – JB Nizet Jun 23 '17 at 13:50
  • I tried that in these lines of code: `SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf2.setTimeZone(TimeZone.getTimeZone("EDT")); ObjDt = sdf2.parse(obj.get("date").toString());` This reduces my time in the json object by 3 hours and the differnce in seconds is going to around 14000 secs. The time in json is already in EDT. Am I going wrong somewhere? @JBNizet – Hemanth Annavarapu Jun 23 '17 at 13:58
  • @HemanthAnnavarapu If you could provide the value of `messagesToBeProcessed.getReceivedDate().getTime()` and a JSON input `String`, so we can make a more reliable test... –  Jun 23 '17 at 14:03
  • @Hugo Posted that in the question. thanks. – Hemanth Annavarapu Jun 23 '17 at 14:22
  • @HemanthAnnavarapu Well, I've updated my answer. –  Jun 23 '17 at 14:39

2 Answers2

5

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.

  • Thanks for the answer! I am completely new to this API. Now, what will be the values of `z` and `dt` and `date` here? and based on the current state of my code, isn't it easier for me to just convert the json date string to UTC and then do a time difference like I am already doing? @Hugo – Hemanth Annavarapu Jun 23 '17 at 14:45
  • 1
    dt is a date and time without timezone and z is the date/time with a timezone. I suggested this API because IMO it's better, less error prone and more clear about what it does (and it becomes easier after you learn it). But of course you can do it the way you suggested. –  Jun 23 '17 at 14:53
  • @HemanthAnnavarapu I've updated the answer putting the values of the variables. Anyway, `dt` is `2017-06-22T02:03:03` (without any timezone information) and `z` is `2017-06-22T02:03:03` in `America/Chicago` timezone (one of the zones that are in CDT). The `date` will be `2017-06-22T06:03:13` in UTC (or `2017-06-22T01:03:13` in CDT). –  Jun 23 '17 at 15:37
  • Help! I am not able to parse the dates to 'UTC'! I'm doing: `sdf.setTimeZone(TimeZone.getTimeZone("UTC"));MsgDt = sdf.parse(sdf.format(messagesToBeProcessed[i].getReceivedDate()));` but when print `MsgDt`, I still get the local time! It worked earlier when it increased the time by 4 hours to UTC. @Hugo – Hemanth Annavarapu Jun 23 '17 at 15:52
  • You don't need to parse and format, because a `Date` [doesn't have a timezone](https://codeblog.jonskeet.uk/2017/04/23/all-about-java-util-date/) - just use the `Date` the way it is. You need to parse the JSON string only. –  Jun 23 '17 at 15:56
  • That's another reason why I prefer the new API, it doesn't lead me to make these kind of obscure errors... –  Jun 23 '17 at 15:57
  • I should've listened! But, I'll give it one last try. Why should the difference be 3590 seconds? the original timings will have a hour difference between them(CDT for mail and EDT for json). Now that I parse json as EDT, when I do the differnce of `.getTime()`, shouldn't it be 10 seconds? because the two are deviating only 10 secs from the same instant in time, right? @Hugo – Hemanth Annavarapu Jun 23 '17 at 16:08
  • In your question you told that: `messagesToBeProcessed[i].getReceivedDate().getTime(): 1498111393000` - And `1498111393000` is equivalent to `2017-06-22T06:03:13` in **UTC**. The JSON String is `2017-06-22 02:03:03` in CDT (Chicago in my example), which is `2017-06-22T07:03:03` in UTC. So, the difference between them is almost 1 hour –  Jun 23 '17 at 16:18
  • Okay I don't think what I am trying is working anymore. `sdf.setTimeZone(TimeZone.getTimeZone("UTC"));MsgDt = sdf.parse(sdf.format(messagesToBeProcessed[i].getReceivedDat‌​e()));` if I try to remove the sdf.format and just parse the `getReceivedDate.toString();` it throws me an error saying `java.text.ParseException: Unparseable date: "Thu Jun 22 02:03:13 EDT 2017"`. I don't understand why it worked earlier and why it isn't anymore. @Hugo – Hemanth Annavarapu Jun 23 '17 at 16:28
  • 1
    The parse expects the format set in `sdf` (`yyyy-MM-dd HH:mm:ss`) but the `toString()` method returns in a different format (`Thu Jun 22 etc`) - anyway, you don't need to parse(format(date)), just use the `Date` object. You need to parse only the JSON string –  Jun 23 '17 at 16:51
2

Using this code

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

You are making the module 60 of the difference in seconds between the two dates.

This is always a value between 0 and 59, so it doesn't consider differences of minutes, days, hours, months and years.

Change it as follow:

int diffInSecs = (int) (ObjDt.getTime() - MsgDt.getTime()) / 1000;

This is the real difference in seconds.

Note: don't capitalize variable names. Use capitalized names only for classes and interfaces.

Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56