How to format LocalDate to ISO 8601 with T and Z?

Jeff picture Jeff · Jan 6, 2021 · Viewed 8.3k times · Source

I'm trying to generate a random date and time, and convert it to the "yyyy-MM-dd'T'HH:mm:ss'Z'" format.

Here is what I have tried:

  public static String generateRandomDateAndTimeInString() {
    LocalDate date = LocalDate.now().minus(Period.ofDays((new Random().nextInt(365 * 70))));
    System.out.println("date and time :: " + date.toString());
    return formatDate(date) ;
  }

  public static String formatDate(LocalDate date){
    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    return dateFormat.format(date);
  }

But in the line dateFormat.format(date), it complains with:

java.lang.IllegalArgumentException: Cannot format given Object as a Date

The second problem is that, the output of print does not contain the time:

date :: 1998-12-24 

I don't know how to get it to work.

Answer

Arvind Kumar Avinash picture Arvind Kumar Avinash · Jan 6, 2021

Never format the java.time types using SimpleDateFormat

Using the SimpleDateFormat, you are supposed to format only legacy date-time types e.g. java.util.Date. In order to format the java.time date-time types, you need to use DateTimeFormatter.

Never enclose Z within single quotes

It's a blunder to enclose Z within single quotes in a format. The symbol Z stands for zulu and specifies UTC+00:00. If you enclose it within single quotes, it will simply mean character literal, Z and won't function as UTC+00:00 on parsing.

You do not need to use a formatter explicitly

For this requirement, you do not need to use a formatter explicitly because the OffsetDateTime#toString already returns the string in the format that you need. However, if the number of seconds in an OffsetDateTime object is zero, the same and the subsequent smaller units are truncated by OffsetDateTime#toString. If you need the full format irrespective of the value of seconds, then, of course, you will have to use DateTimeFormatter.

import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        System.out.println(generateRandomDateAndTimeInString());
    }

    public static String generateRandomDateAndTimeInString() {
        LocalDate date = LocalDate.now().minus(Period.ofDays((new Random().nextInt(365 * 70))));
        System.out.println("date and time :: " + date.toString());
        return formatDate(date);
    }

    public static String formatDate(LocalDate date) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssX");
        // return date.atStartOfDay().atOffset(ZoneOffset.UTC).toString();
        return date.atStartOfDay().atOffset(ZoneOffset.UTC).format(dtf);
    }
}

A sample run:

date and time :: 1996-09-05
1996-09-05T00:00:00Z

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 from Trail: Date Time.

If you still need to use SimpleDateFormat for whatsoever reason:

Convert LocalDate to ZonedDateTime with ZoneOffset.UTC and at the start of the day ➡️ Convert ZonedDateTime to Instant ➡️ Obtain java.util.Date object from Instant.

public static String formatDate(LocalDate date) {
    Date utilDate = Date.from(date.atStartOfDay(ZoneOffset.UTC).toInstant());
    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
    return dateFormat.format(utilDate);
}