1

Here is my simple code:

    String defaultSimpleDateFormatPattern = "MMM dd, yyyy HH:mm:ss";
    TimeZone tzNY = TimeZone.getTimeZone("America/New_York");
    TimeZone tzLos = TimeZone.getTimeZone("America/Los_Angeles");
    String dateToTest = "Jan 03, 2015 23:59:59";
    SimpleDateFormat df = new SimpleDateFormat(defaultSimpleDateFormatPattern);
    Calendar c = Calendar.getInstance();
    c.setTime(df.parse(dateToTest)); 
    c.setTimeZone(tzLos);

    System.out.println(c.getTimeZone());
    System.out.println(c.getTime());        
    System.out.println(df.format(c.getTime()));


    Calendar c1 = Calendar.getInstance();
    c1.setTime(df.parse(dateToTest));        
    c1.setTimeZone(tzNY);

    System.out.println(c1.getTimeZone());
    System.out.println(c1.getTime());
    System.out.println(df.format(c1.getTime()));

    System.out.println(c.after(c1)? "after" : (c.before(c1)? "before" : "equal"));

The printout is "equal". How is that? any explanation on this result?

curiousman
  • 57
  • 1
  • 1
  • 7

2 Answers2

0

There are two problems here:

  • You're using an invalid time zone ID (you want America/New_York)
  • You're parsing using a formatter that hasn't got a time zone set (so it'll use the default time zone) and then setting the time zone in the Calendar afterwards... that doesn't change the instant in time being represented

So basically you're parsing to the same Date twice, doing things which don't affect the Date being represented, and then comparing the two equal Date values.

If at all possible, you should use Joda Time or java.time instead of java.util.Calendar, but if you really need to use it, just create two different formatters, one with each time zone. (You'll need to set the time zone in the Calendar as well, if you actually need the Calendar...)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • "America/New York" or "America/New_York" make no difference here(I tried). I knew I'd better use joda datetime or DateFormatter with time zone to get around this problem. what I don't understand is that why setting timezone on Calendar make no difference on the time being represented. I assume it should automatically trigger Date changes. – curiousman Jul 24 '15 at 17:57
  • @curiousman: ""America/New York" or "America/New_York" make no difference here" - it should do. Look at the time zones returned... "what I don't understand is that why setting timezone on Calendar make no difference on the time being represented. I assume it should automatically trigger Date changes." Why would you assume that? It just sets the time zone - it doesn't change the instant in time being represented. It will change the values returned by `Calendar.get(...)` but that's a different matter. Basically, if you want to parse a value in a certain time zone, set it on the formatter. – Jon Skeet Jul 24 '15 at 18:11
  • you are right. my system time zone is same as New York, that's why I didn't see the difference on the result. you mentioned " it doesn't change the instant in time being represented. It will change the values returned by Calendar.get(...) but that's a different matter". --this puzzles me. calling Calendar.get(...) will trigger the change. but calling Calendar.getTime() won't. – curiousman Jul 24 '15 at 19:37
  • @curiousman: It's not a matter of *triggering* the change - it's a matter of *seeing* it. A `Calendar` value represents a point in time. If you change the time zone, you don't change the point in time - but you change how that's represented when the time zone is applied, that's all. The point of time "now" is 20:39 for me, but 15:39 for you - so I could call `calendar.get(Calendar.HOUR)` and 20 would be returned, change the time zone to New York and make the same call and get 15... without changing the point of time being represented. – Jon Skeet Jul 24 '15 at 19:39
0

java.time

The java.util Date-Time API 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*.

Also, quoted below is a notice from the home page of Joda-Time:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

Solution using java.time, the modern Date-Time API: Your Date-Time string does not have timezone information and therefore it can be described as a local Date-Time. So, parse it to LocalDateTime and apply the timezone to it to get the ZonedDateTime.

Demo:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String defaultSimpleDateFormatPattern = "MMM dd, uuuu HH:mm:ss";
        ZoneId tzNY = ZoneId.of("America/New_York");
        ZoneId tzLos = ZoneId.of("America/Los_Angeles");
        String dateToTest = "Jan 03, 2015 23:59:59";

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(defaultSimpleDateFormatPattern, Locale.ENGLISH);
        LocalDateTime ldt = LocalDateTime.parse(dateToTest, dtf);

        ZonedDateTime zdtNY = ldt.atZone(tzNY);
        ZonedDateTime zdtLos = ldt.atZone(tzLos);

        System.out.println(zdtNY.isAfter(zdtLos) ? "after" : zdtNY.isBefore(zdtLos) ? "before" : "equal");
    }
}

Output:

before

ONLINE DEMO

Alternatively, Create separate DateTimeFormatter specific to each timezone i.e. ask Java to parse the local Date-Time string applying the given timezone.

Demo:

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String defaultSimpleDateFormatPattern = "MMM dd, uuuu HH:mm:ss";
        ZoneId tzNY = ZoneId.of("America/New_York");
        ZoneId tzLos = ZoneId.of("America/Los_Angeles");
        String dateToTest = "Jan 03, 2015 23:59:59";

        DateTimeFormatter dtfNY = DateTimeFormatter.ofPattern(defaultSimpleDateFormatPattern, Locale.ENGLISH)
                                    .withZone(tzNY);
        DateTimeFormatter dtfLos = DateTimeFormatter.ofPattern(defaultSimpleDateFormatPattern, Locale.ENGLISH)
                                    .withZone(tzLos);

        ZonedDateTime zdtNY = ZonedDateTime.parse(dateToTest, dtfNY);
        ZonedDateTime zdtLos = ZonedDateTime.parse(dateToTest, dtfLos);

        System.out.println(zdtNY.isAfter(zdtLos) ? "after" : zdtNY.isBefore(zdtLos) ? "before" : "equal");
    }
}

Output:

before

ONLINE DEMO

Learn more about the modern Date-Time API from Trail: Date Time.

What is wrong with your code?

  1. You have not set a timezone to your SimpleDateFormat: Unlike the modern Date-Time API with which you have multiple ways to create a Date-Time object specific to a timezone, you have only this way with the legacy API to deal with such a situation (because java.util.Date does not hold timezone information). It is similar to the alternative example shown above.
  2. You have not set a Locale to your SimpleDateFormat: Never use SimpleDateFormat or DateTimeFormatter without a Locale. Luckily, your program did not crash because your JVM's timezone must be an English locale.

Demo:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        String defaultSimpleDateFormatPattern = "MMM dd, yyyy HH:mm:ss";
        TimeZone tzNY = TimeZone.getTimeZone("America/New_York");
        TimeZone tzLos = TimeZone.getTimeZone("America/Los_Angeles");
        String dateToTest = "Jan 03, 2015 23:59:59";
        SimpleDateFormat df = new SimpleDateFormat(defaultSimpleDateFormatPattern, Locale.ENGLISH);

        df.setTimeZone(tzNY);
        Calendar c = Calendar.getInstance();
        c.setTime(df.parse(dateToTest));

        df.setTimeZone(tzLos);
        Calendar c1 = Calendar.getInstance(tzNY);
        c1.setTime(df.parse(dateToTest));

        System.out.println(c.after(c1) ? "after" : (c.before(c1) ? "before" : "equal"));
    }
}

Output:

before

ONLINE DEMO


* 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.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110