How to format LocalDate to ISO 8601 with T and Z?
Asked Answered
B

3

10

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.

Branchia answered 6/1, 2021 at 13:31 Comment(2)
Does this answer your question? Changing one date to another date format using LocalDateKommunarsk
A LocalDate only has a day, a month and a year. If you want the time of day, too, think about a LocalDateTime and if you want an offset or a time zone, check out OffsetDateTime and ZonedDateTime. All of them may be parsed and formatted from/to String by means of a suitable DateTImeFormatter. No need for a SimpleDateFormat anymore...Parthenon
L
20

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);
}
Lupita answered 6/1, 2021 at 13:51 Comment(6)
Thanks for the response, could you please write an answer using the java.util.Calendar which is newer?Branchia
java.util.Calendar is another hack on top of java.util.Date. I suggest you do not use it as you are already using the modern date-time API. Do not mix the error-prone java.util date-time API with it. If you insist, I can provide you with the same in a couple of minutes.Lupita
@Branchia - I've added a section at the end of my answer just because you have requested for it but as I have mentioned repeatedly, do not use java.util date-time API for any production code. They are error-prone and should never be mixed with the modern, java.time API. Feel free to comment in case of any doubt.Lupita
Newer? java.util.Calendar is a few years newer than Date alright but doesn’t really help us anything here since it cannot be formatted. Calendar is 24 years old and was supplanted nearly 7 years ago (March 2014) through java.time.Sidestroke
How can i do it, n a way that it generates random time as well?Branchia
@Branchia - Replace return date.atStartOfDay().atOffset(ZoneOffset.UTC).format(dtf); with return date.atStartOfDay().plus(Duration.ofNanos(ThreadLocalRandom.current().nextLong(TimeUnit.DAYS.toNanos(1)))).atOffset(ZoneOffset.UTC).format(dtf);Lupita
T
3

If you want to ignore the time part then you can use ZonedDateTime like this:

DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
return ZonedDateTime.of(
        date, 
        LocalTime.MIN, 
        ZoneId.of("Europe/Paris")
).format(dateFormat);

Output example

2013-10-19T00:00:00+0200

Or much better, you can use just toString to get a formatted date as a String with the default format of ZonedDateTime:

return ZonedDateTime.of(
        date,
        LocalTime.MIN,
        ZoneId.of("Europe/Paris")
).toString();

Output

2013-10-19T00:00+02:00[Europe/Paris]

Note

This date are always with 00:00:00 for time part, because we are using LocalTime.MIN

Also, you can change the ZoneId to the expected Zone, this was just an example.

Important

DateFormat and SimpleDateFormat are legacy library, so please don't mix them with the java.time library, in the top you are using LocalDate which mean you are using this java.time library so keep going with it in all your code.

Truehearted answered 6/1, 2021 at 13:40 Comment(2)
It's a blunder to enclose Z within single quotes in a format. Please correct it.Lupita
Thank you @LiveandLetLive I change it ;)Truehearted
S
3
    ZoneOffset utc = ZoneOffset.UTC;
    LocalDate today = LocalDate.now(utc);
    LocalDate seventyYearsAgo = today.minusYears(70);
    int totalDays = Math.toIntExact(ChronoUnit.DAYS.between(seventyYearsAgo, today));
    LocalDate date = today.minusDays(new Random().nextInt(totalDays));
    String dateString = date.atStartOfDay(utc).toString();
    System.out.println("date and time :: " + dateString);

Example output:

date and time :: 1983-08-24T00:00Z

Points to note:

  • Let java.time convert from years to days. It gives more readable and more correct code (a year is not always 365 days).
  • To have time of day and UTC offset in the string, convert a ZonedDateTime or an OffsetDateTime since such objects hold time of day and offset. A LocalDate does not. It’s a date without time of day and without offset from UTC. The Z you asked for denotes an offset of 0 from UTC.

If you want hours, minutes and seconds in the output too, you can have that by counting seconds rather than days. In this case use OffsetDateTime for the entire operation (or ZonedDateTime if in a time zone different from UTC).

    ZoneOffset utc = ZoneOffset.UTC;
    OffsetDateTime today = OffsetDateTime.now(utc).truncatedTo(ChronoUnit.SECONDS);
    OffsetDateTime seventyYearsAgo = today.minusYears(70);
    long totalSeconds = ChronoUnit.SECONDS.between(seventyYearsAgo, today);
    OffsetDateTime date = today.minusSeconds(ThreadLocalRandom.current().nextLong(0, totalSeconds));
    String dateString = date.toString();
    System.out.println("date and time :: " + dateString);

date and time :: 1996-09-21T06:49:56Z

I am using ThreadLocalRandom because it can generate a random long value in a specified interval. Funnily ThreadLocalRandom has a lot of convenient methods that Random hasn’t got.

Sidestroke answered 7/1, 2021 at 7:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.