Search code examples
javatimezonedatetime-formatdatetime-parsing

SimpleDateFormat parse method works first time, is ignored second time


I have a date time that I am first converting to local time, followed by a conversion to another time zone. The first conversion works with no issue however the second conversion is ignored. What is the issue?

String input = "2020-05-20 01:10:05";
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
localFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));

try {
    Date date = localFormat.parse(input);
    System.out.println(date); //Wed May 20 01:10:05 PDT 2020 <--- Logs this (Expected)

    SimpleDateFormat estFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    estFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
    
    String newDate = estFormat.format(date);
    System.out.println(newDate); //2020-05-20 04:10:05 <--- Logs this (Expected)

    Date dateEst = estFormat.parse(newDate);
    System.out.println(dateEst); //Wed May 20 01:10:05 PDT 2020 <--- Logs this (Unexpected) Should be Wed May 20 04:10:05 EST 2020

}catch (Exception e){
    e.printStackTrace();
}

It seems like the second estFormat.parse() is ignored when trying to convert to America/New_York time zone.


Solution

  • java.time

    I warmly recommend that you use java.time, the modern Java date and time API, for your date and time work. Once you get used to the slightly different mindset, you will likely also find the resulting code clearer and more natural to read. Let’s first define the constant stuff as constants:

    private static final DateTimeFormatter FORMATTER
            = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
    private static final ZoneId FROM_ZONE = ZoneId.of("America/Los_Angeles");
    private static final ZoneId TO_ZONE = ZoneId.of("America/New_York");
    

    With these we can do our work:

        String input = "2020-05-20 01:10:05";
        
        ZonedDateTime fromDateTime
                = LocalDateTime.parse(input, FORMATTER).atZone(FROM_ZONE);
        System.out.println("From:      " + fromDateTime);
        
        ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant(TO_ZONE);
        System.out.println("To:        " + toDateTime);
        
        String formatted = toDateTime.format(FORMATTER);
        System.out.println("Formatted: " + formatted);
    

    Output is:

    From:      2020-05-20T01:10:05-07:00[America/Los_Angeles]
    To:        2020-05-20T04:10:05-04:00[America/New_York]
    Formatted: 2020-05-20 04:10:05
    

    Edit:

    How would I get it to have EST 2020 at the end?

    A good option for most purposes is to use a localized formatter:

    private static final DateTimeFormatter TARGET_FORMATTER
            = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
                    .withLocale(Locale.US);
    

    Like this:

        String formatted = toDateTime.format(TARGET_FORMATTER);
    
    Formatted: May 20, 2020 at 4:10:05 AM EDT
    

    A detail, we didn’t get EST at the end because New York and most of the East coast of Northern America uses summer time (DST) and hence is on Eastern Daylight time, EDT, in May.

    What went wrong?

    It seems that you were expecting your Date object to carry the time zone of the formatter that parsed it, America/New_York. An old-fashioned Date object cannot do that. It’s just a dumb point in time without any time zone or other additional information. What confuses many is that its toString method uses the default time zone of the JVM to render the string returned, thus giving the false impression of a time zone being present. In contrast the modern ZonedDateTime, as the name says, does hold a time zone.

    Link

    Oracle tutorial: Date Time explaining how to use java.time.