Search code examples
javadatetimetimezonejodatime

Convert ISO 8601 string in UTC to local time - JodaTime is adding the opposite of local time zone


Say I have a String equal to 2019-02-23T07:58:21. This is a time retrieved from a system clock in UTC.

I want to convert the time to what it would be in the system's local time.

I'm using JodaTime because it seems like it can easily do what I want.


What I've tried:

DateTimeFormatter dateParser = ISODateTimeFormat.dateTimeNoMillis().withZoneUTC();

DateTime dateTime;
LocalDateTime localDateTime;

dateTime = dateParser.parseDateTime("2019-02-22T01:03:23Z");
localDateTime = dateTime.toLocalDateTime();

System.out.println("UTC: " + dateTime.toDate());
System.out.println("Local: " + localDateTime.toDate());

What it's outputting:

UTC: Thu Feb 21 18:03:23 MST 2019
Local: Fri Feb 22 01:03:23 MST 2019

Questions:

  • The default time zone set to my system is MST, which is UTC-7:00. It looks like it's actually adding 7 hours to the UTC time - why is that?

  • Additionally, it thinks both times are in MST time. I had thought that the .withZoneUTC() would solve this. Why is it thinking this?

  • Is this method the "best" for doing this (assuming these problems can be fixed)?


Solution

  • I take it step by step:

    1. dateParser.parseDateTime("2019-02-22T01:03:23Z") gives you Feb 22 at 01:03 UTC as expected (same point in time as Feb 21 at 18:03 at offset -07:00).
    2. dateTime.toLocalDateTime() throws the UTC offset information away, so you get Feb 22 at 01:03 with no time zone information. A LocalDateTime is a date and a time of day without time zone information, so it does not correspond to any point in time.
    3. dateTime.toDate() gives you the same point in time as Feb 22 at 01:03 UTC without offset or time zone information. A Date despite its name is somehow opposite a LocalDateTime: It’s a point in time but does not correspond to any specific hour of the day.
    4. "UTC: " + dateTime.toDate() appends the Date to a string. To do this, Date.toString is implicitly called to obtain a string to append to the first string. Date.toString uses your local time zone for rendering the string (this behavior confuses many). So the point in time you got is printed as Thu Feb 21 18:03:23 MST 2019, which is the correct point in time, only converted to Mountain Standard Time.
    5. localDateTime.toDate() is problematic. You’re saying that you want to convert a date and time to a point in time. LocalDateTime solves this by using your local time zone, so you get a point in time equal to Feb 22 at 01:03 MST (same point as 08:03 UTC).
    6. "Local: " + localDateTime.toDate() again calls toString and uses MST to render the string.

    It looks like it's actually adding 7 hours to the UTC time - why is that?

    Because you are throwing away the information that the date and time were in UTC and then interpreting them in MST.

    Additionally, it thinks both times are in MST time. … Why is it thinking this?

    It’s not. It’s just Date.toString acting confusingly.

    Is this method the "best" for doing this (assuming these problems can be fixed)?

    If you’re writing new code, it’s better to use java.time, the modern Java date and time API. If you were already using Joda-Time, a good solution with Joda-Time can be developed, and it’s an open question whether and when you will want to migrate to java.time.

    I’m not experienced with Joda-Time, but here’s a snippet using java.time in case you want that:

    String systemUtcString = "2019-02-22T01:03:23Z";
    Instant pointInTime = Instant.parse(systemUtcString);
    ZonedDateTime denverDateTime = pointInTime.atZone(ZoneId.of("America/Denver"));
    System.out.println(denverDateTime);
    

    2019-02-21T18:03:23-07:00[America/Denver]

    In any case, avoid java.util.Date if you can. Only if you indispensably need a Date for a legacy API that you cannot change or don’t want to change just now, convert before calling that API. In this case, remember that a Date hasn’t got a time zone, so there is no need to convert to MST before converting to Date.

    Date oldfashionedDateObject = Date.from(pointInTime);
    System.out.println(oldfashionedDateObject);
    

    Thu Feb 21 18:03:23 MST 2019

    Quote

    Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).

    (from the Joda-Time home page)

    Links