Michiel
Michiel

Reputation: 768

Using DateTimeFormatter over SimpleDateFormat

I'm refactoring some code and I'm trying to distinguish what problem is being solved. The function of formatDateRfc1123 is obvious, but what's the added value of the added code?

In the first commit this was the function, which could crash for reasons unknown to me:

public static String formatDateRfc1123(final Date timestamp) {
    SimpleDateFormat formatRf1123 = new SimpleDateFormat(RFC_1123);
    formatRf1123.setTimeZone(GMT_ZONE);
    return formatRfc1123.format(timestamp);
} 

Later, that got refactored to solve the crash:

public static String formatDateRfc1123(final Date timestamp, final int buildVersion) {
    if (buildVersion >= Build.VERSION_CODE_O) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(RFC_1123, US);
        Instant instant = Instant.ofEpochSecond(TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime());
        ZoneId zoneId = ZoneId.of(GMT_ZONE.getID());
        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId);
        return dateTimeFormatter.format(zonedDateTime);
    } else {
        SimpleDateFormat formatRf1123 = new SimpleDateFormat(RFC_1123);
        formatRf1123.setTimeZone(GMT_ZONE);
        return formatRfc1123.format(timestamp);
    }
} 

There's an old user story linked to the added code, but that only links to a removed Fabric crash log. So, I only know it can crash, not what exception was thrown for what value.

For what value and SDK version is it likely to crash?

Upvotes: 1

Views: 151

Answers (1)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 78965

The first three lines of your code using java.time has been overly complicated making the calculation error-prone. I recommend you use the out-of-the-box constants to avoid any kind of ambiguity and error. Given below is the recommended way to do it:

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(formatDateRfc1123(new Date()));
    }

    public static String formatDateRfc1123(final Date timestamp) {
        // Use the out-of-the-box formatter
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.RFC_1123_DATE_TIME.withLocale(Locale.US);

        // Use Date#toInstant to convert it into Instant
        Instant instant = timestamp.toInstant();

        // Use the out-of-the-box constant for UTC (or GMT)
        ZoneId zoneId = ZoneOffset.UTC;

        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId);
        // ZonedDateTime zonedDateTime = instant.atZone(zoneId); //This is simpler

        return dateTimeFormatter.format(zonedDateTime);
    }
}

Note that the three-letter string representing a time zone is error-prone. You should always use the full name of the time zone i.e. Continent/City. Given below is an example to demonstrate it.

import java.time.ZoneId;

public class Main {
    public static void main(String[] args) {
        ZoneId zoneId = ZoneId.of("EDT");
        // ...
    }
}

Output:

Exception in thread "main" java.time.zone.ZoneRulesException: Unknown time-zone ID: EDT
    at java.base/java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:279)
    at java.base/java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:234)
    at java.base/java.time.ZoneRegion.ofId(ZoneRegion.java:120)
    at java.base/java.time.ZoneId.of(ZoneId.java:408)
    at java.base/java.time.ZoneId.of(ZoneId.java:356)
    at Main.main(Main.java:5)

The SimpleDateFormat won't even throw an exception and silently format the date-time at UTC (or GMT). Such dangerous it is!

Incorrect:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        sdf.setTimeZone(TimeZone.getTimeZone("EDT"));
        System.out.println(sdf.format(new Date()));
    }
}

Output:

2020-12-05T01:36:48Z

Correct:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(sdf.format(new Date()));
    }
}

Output:

2020-12-04T20:36:05-05:00

Note that the date-time API of java.util 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. Learn more about the modern date-time API at Trail: Date Time.

Note: 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.

Upvotes: 2

Related Questions