Amit Jagtap
Amit Jagtap

Reputation: 119

Compare java.util.Calendar dates of different timezones

I have two dates - Ship Date and Delivery Date. Business case is - Ship Date cannot be after Delivery Date.

My one input date is made up of three separate String components - date (e.g.05.07.2021), time (e.g.00:00:00), timezone (e.g.CST).

I tried converting this input date to Calendar instance and then used Calender.after(). But it looks like it doesn't consider timezone while comparing.

Input:

ShipDate - 05.07.2021, 01:00:00, EST

DelDate - 05.07.2021, 00:00:00, CST

Expected:

Though the time is different, considering timezone conversion, both ship and delivery dates should be same, passing business case.

Please help achieve the expected.

Code:

public static void main(String[] args) {

    String delDate = "05.07.2021";
    String delTime = "00:00:00";
    String delTz = "CST";
    String shipDate = "05.07.2021";
    String shipTime = "01:00:00";
    String shipTz = "EST";
        
    SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
    Calendar delCal = Calendar.getInstance(TimeZone.getTimeZone(delTz));
    Calendar shipCal = Calendar.getInstance(TimeZone.getTimeZone(shipTz));
    try {
        delCal.setTime(sdf.parse(delDate + " " + delTime));
        shipCal.setTime(sdf.parse(shipDate + " " + shipTime));
    } catch (ParseException e) {
        e.printStackTrace();
    }
    
    if (shipCal.after(delCal)) {
        System.out.println("ERROR: Ship datetime is after Del datetime");
    } else {
        System.out.println("SUCCESS: Ship datetime and Del datetime are correct");
    }
}

Upvotes: 0

Views: 669

Answers (2)

deHaar
deHaar

Reputation: 18588

You could use the datetime API that was introduced in Java 8, that is java.time.

There is a ZonedDateTime which you can use in order to achieve the expected, here's a small example:

public static void main(String[] args) {
    // your example input
    String delDate = "05.07.2021";
    String delTime = "00:00:00";
    String delTz = "CST";
    String shipDate = "05.07.2021";
    String shipTime = "01:00:00";
    String shipTz = "EST";
    // concatenate the input, just comma separated
    String delDateTimeZone = String.join(",", delDate, delTime, delTz);
    String shipDateTimeZone = String.join(",", shipDate, shipTime, shipTz);
    // define a formatter that parses Strings like the concatenated ones
    DateTimeFormatter dateTimeZoneDtf = DateTimeFormatter.ofPattern(
                                                "dd.MM.uuuu,HH:mm:ss,z");
    // parse them to objects that consider time zones
    ZonedDateTime delZdt = ZonedDateTime.parse(delDateTimeZone, dateTimeZoneDtf);
    ZonedDateTime shipZdt = ZonedDateTime.parse(shipDateTimeZone, dateTimeZoneDtf);
    // find out if their instants are equal
    String e = delZdt.toInstant().equals(shipZdt.toInstant()) ?
                                    "the same moment" : "different moments";
    // and print them along with a statement about their equality
    System.out.println(delZdt + " and " + shipZdt + " are " + e + " in time");
}

The output of this example is

2021-07-05T00:00-05:00[America/Chicago] and 2021-07-05T01:00-04:00[America/New_York] are the same moment in time

Please note that you can parse or create the parts a ZonedDateTime consist of by handling every part of your input separately.

But if you do...

... it will not produce the same results in every case due to ZoneId.of(delTz, ZoneId.SHORT_IDS) seems to be interpreting "EST" as -05:00 hours from UTC all year while the DateTimeFormatter appears to use a real zone that considers daylight saving times.

Thanks to Ole V. V. for pointing out this difference to the example above in a comment below this answer.

Here's the example that resolves the three-letter time zone abbreviation to a fixed offset:

public static void main(String[] args) {
    // your example input
    String delDate = "05.07.2021";
    String delTime = "00:00:00";
    String delTz = "CST";
    String shipDate = "05.07.2021";
    String shipTime = "01:00:00";
    String shipTz = "EST";
    // define a formatter that parses the date String
    DateTimeFormatter dateDtf = DateTimeFormatter.ofPattern("dd.MM.uuuu");
    // parse the dates using the date formatter 
    LocalDate delLocalDate = LocalDate.parse(delDate, dateDtf);
    LocalDate shipLocalDate = LocalDate.parse(shipDate, dateDtf);
    // and parse the times of day without (they are in standard ISO format already)
    LocalTime delLocalTime = LocalTime.parse(delTime);
    LocalTime shipLocalTime = LocalTime.parse(shipTime);
    // then create zone objects from the zone short ids
    ZoneId delZone = ZoneId.of(delTz, ZoneId.SHORT_IDS);
    ZoneId shipZone = ZoneId.of(shipTz, ZoneId.SHORT_IDS);
    // create objects that consider time zones using the 
    ZonedDateTime delZdt = ZonedDateTime.of(delLocalDate, delLocalTime, delZone);
    ZonedDateTime shipZdt = ZonedDateTime.of(shipLocalDate, shipLocalTime, shipZone);
    // find out if their instants are equal
    String e = delZdt.toInstant().equals(shipZdt.toInstant()) ?
                                    "the same moment" : "different moments";
    // and print them along with a statement about their equality
    System.out.println(delZdt + " and " + shipZdt + " are " + e + " in time");
}

Upvotes: 3

Anonymous
Anonymous

Reputation: 86379

What went wrong in your code?

This is the answer for the curious. deHaar has long posted the good answer showing how to solve your problem. We have no real need to know what went wrong when you tried to use the poorly designed and long outdated SimpleDateFormat, Calendar and TimeZone classes since we should not be using those classes at all anyway. However, in this case curiosity won’t completely kill the cat, so let’s see.

Comparison between your two Calendar objects works the way I think you had expected: it is comparing the points in time to see whether one is after the other. In other words, it is not comparing the wall-clock times from different time zones.

But before you get around to doing the comparison, two other things go wrong.

The first thing is that your two three letter time zone abbreviations, CST and EST, give you the same UTC offset, namely -05:00. What?! How?! This is because inconsistently CST is taken to mean America/Chicago time zone, that is, with summer time (DST) taken into account, whereas EST is taken literally to mean (North American) Eastern Standard Time all year, that is, with no summer time. So while Chicago is at offset -06:00 during standard time, your date, 5 July, falls in the summer time part of the year, so offset -05:00 is applied. Eastern Standard Time is at offset -05:00 all year, so the same offset is applied here.

Lesson to learn: Do not use three letter time zone abbreviations. Use real time zone IDs like America/Winnipeg, America/Chicago, America/Toronto or America/New_York, always region/city.

The second thing goes badly wrong here:

        delCal.setTime(sdf.parse(delDate + " " + delTime));

You had expected this to set the Calendar that was already in Chicago time zone to 5 July 00:00 in Chicago time zone. This (probably) did not happen. sdf.parse() returns a Date object. Despite the the name a Date neither holds a date nor an hour of day in it. It holds a point in time. And your SimpleDateFormat not knowing that you had Central Time in mind, parses your concatenated string into a point in time that corresponds to 5 July 00:00 in the default time zone of the JVM. In my time zone (Europe/Copenhagen) I got 4 July 17:00 in Chicago. You may be able to guess by now that the following line then parses into a point in time corresponding to 01:00 in the night in the same JVM default time zone, so 1 hour later (unless something very special happens to time in your time zone in this night).

Lesson to learn: Don’t use SimpleDateFormat, Calendar and TimeZone. With those old classes it’s too easy to make errors that are too hard to look through and track down.

Upvotes: 2

Related Questions