How to format java.util.Date with DateTimeFormatter portable?

TL;DR: You’re right to be concerned about the use of the system local time zone, but you should have been concerned earlier in the process, when you used the system local time zone to construct a Date in the first place.

If you just want the formatted string to have the same components that Date.getDate(), Date.getMonth(), Date.getYear() etc return then your original code is appropriate:

LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

You say you’re “afraid that usage of ZoneId.systemDefault() can introduce some changes” – but that’s precisely what Date.getDate() etc use.

Date doesn’t have any kind of “dual contract” that lets you view it as a time-zone-less representation. It is just an instant in time. Almost every single method that lets you construct or deconstruct it into components is clearly documented to use the system default time zone, just like your use of ZoneId.systemDefault(). (One notable exception is the UTC method.)

Implicitly using the system default time zone is not the same as Date being a valid time-zone-less representation, and it’s easy to demonstrate why: it can lose data, very easily. Consider the time-zone-free date and time of “March 26th 2017, 1:30am”. You may well want to be able to take a text representation of that, parse it, and then later reformat it. If you do that in the Europe/London time zone, you’ll have problems, as demonstrated below:

import java.util.*;
import java.time.*;
import java.time.format.*;

public class Test {

    public static void main(String[] args) {
        TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
        Date date = new Date(2017 - 1900, 3 - 1, 26, 1, 30);

        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime ldt = LocalDateTime.ofInstant(instant, zone);
        System.out.println(ldt); // Use ISO-8601 by default
    }
}

The output is 2017-03-26T02:30. It’s not that there’s an off-by-one error in the code – if you change it to display 9:30am, that will work just fine.

The problem is that 2017-03-26T01:30 didn’t exist in the Europe/London time zone due to DST – at 1am, the clock skipped forward to 2am.

So if you’re happy with that sort of brokenness, then sure, use Date and the system local time zone. Otherwise, don’t try to use Date for this purpose.

If you absolutely have to use Date in this broken way, using methods that have been deprecated for about 20 years because they’re misleading, but you’re able to change the system time zone, then change it to something that doesn’t have – and never has had – DST. UTC is the obvious choice here. At that point, you can convert between a local date/time and Date without losing data. It’s still a bad use of Date, which is just an instant in time like Instant, but at least you won’t lose data.

Or you could make sure that whenever you construct a Date from a local date/time, you use UTC to do the conversion, of course, instead of the system local time zone… whether that’s via the Date.UTC method, or by parsing text using a SimpleDateFormat that’s in UTC, or whatever it is. Unfortunately you haven’t told us anything about where your Date value is coming from to start with…

Browse More Popular Posts

Leave a Comment