anonymous
anonymous

Reputation: 503

Java 8 localdatetime vs Date

I have a string 2017-07-31T01:01:00-07:00 and I am trying to parse it to date and in CST Timezone. I am getting different results when i parse this string using Date and Java 8 ZonedDateTime. I am not getting why this is happening and what I am doing wrong.

    String dateStr = "2017-07-31T01:01:00-07:00";
    LocalDateTime time = null;
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss-hh");

    String[] dateArray = dateStr.split("-");
    String[] timeZones = TimeZone
            .getAvailableIDs(TimeZone.getTimeZone("GMT-" + dateArray[dateArray.length - 1]).getRawOffset());
    format.setTimeZone(TimeZone.getTimeZone(timeZones[0]));
    Date dateObj = null;
    try {
        dateObj = format.parse(dateStr);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    time = dateObj.toInstant().atZone(TimeZone.getTimeZone("CST").toZoneId()).toLocalDateTime();
    ZonedDateTime time2 = ZonedDateTime.parse(dateStr).toInstant().atZone(TimeZone.getTimeZone("CST").toZoneId());
    System.out.println(time);
    System.out.println(time2.toLocalDateTime());

Upvotes: 3

Views: 2086

Answers (2)

Anonymous
Anonymous

Reputation: 86379

I am not sure about the exact behaviour of TimeZone.getAvailableIDs (it’s not perfectly well documented). When I run your code on my Java 9.0.4, the first ID it returns is America/Boise. Boise, Idaho, USA, is on Mountain Standard Time in winter (UTC-07:00) and on Mountain Daylight Time now (UTC-06:00). You set your format to use this for parsing. Your format parses 01 as hour of day (uppercase HH, 00 through 23) and 07 as hour within AM or PM (lowercase hh, 01 through 12). So there’s a conflict here. Apparently the former wins, I don’t know why (don’t always insist on understanding SimpleDateFormat). Since July 31 is in the summer time (DST) time of year, you get a time of 01:01-06:00, equal to 02:01 North American Central Daylight Time (-05:00). Which is incorrect.

ZonedDateTime.parse parses your string correctly. TimeZone.getTimeZone("CST").toZoneId() interprets CST as America/Chicago (which strictly speaking is incorrect since Chicago uses CST only for the minor part of the year). So in time2 you get 2017-07-31T03:01-05:00[America/Chicago], which is correct.

For what I think you were trying to obtain I recommend:

    ZonedDateTime dateTime = OffsetDateTime.parse(dateStr)
            .atZoneSameInstant(ZoneId.of("America/Chicago"));
    System.out.println(dateTime.toLocalDateTime());

Output is the same as your second output:

2017-07-31T03:01

If you find it more appropriate for your situation, instead of America/Chicago you may consider for example America/Bahia_Banderas, America/Indiana/Knox, America/Indiana/Tell_City, America/Matamoros, America/Menominee or America/Winnipeg. Don’t rely on a three or four letter time zone abbreviation. CST is not a true time zone since it is only used for some of the year, and it is ambiguous, it may refer to Australian Central Standard Time, North and Central American Central Standard Time, China Standard Time or Cuba Standard Time. And there’s no guarantee which one Java gives you.

Upvotes: 1

Andreas
Andreas

Reputation: 159205

You should not parse the timezone offset yourself. Just use the X pattern:

new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX")

You should not use timezone CST, since that is ambiguous (Central Standard Time, China Standard Time, Cuba Standard Time). Use America/Chicago (I assume that's what you meant).

So, to parse the date string with old and new APIs:

String dateStr = "2017-07-31T01:01:00-07:00";

// Using Date
SimpleDateFormat parseFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
Date date = parseFormat.parse(dateStr);

SimpleDateFormat printFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
printFormat.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
System.out.println(printFormat.format(date));

// Using ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateStr);
zonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("America/Chicago"));
System.out.println(zonedDateTime.toLocalDateTime());

Output

2017-07-31T03:01
2017-07-31T03:01

If you want to see the time zone, you can do this:

String dateStr = "2017-07-31T01:01:00-07:00";

// Using Date
SimpleDateFormat parseFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
Date date = parseFormat.parse(dateStr);

SimpleDateFormat printFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm XXX z");
printFormat.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
System.out.println(printFormat.format(date));

// Using ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateStr);

DateTimeFormatter printFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm XXX z")
                                                    .withZone(ZoneId.of("America/Chicago"));
System.out.println(zonedDateTime.format(printFormatter));

Output

2017-07-31T03:01 -05:00 CDT
2017-07-31T03:01 -05:00 CDT

Note how the first example changes the ZonedDateTime timezone, converts it to a LocalDateTime, then prints that without a formatter, while in the second example the DateTimeFormatter is setup to format the value in a specific timezone, similar to how the SimpleDateFormat is doing it. Just different ways of accomplishing the same result.

Upvotes: 5

Related Questions