简体   繁体   中英

Converting ISO 8601-compliant String to ZonedDateTime

I'm parsing logs from different sources, I'm extracting datetime string from log. Now I want to convert it into java ZonedDateTime.

Problem here is, I don't know exact datetime format, I just know that, string will be ISO-8601 compliant. So I want to write function like:

    /**
     * @param _dateTime : any ISO 8601 format , e.g 1. %Y-%m-%dT%H:%M:%s%z => 2014-05-25T08:20:03.123456Z , 
     *                    e.g 2. %Y-%m-%dT%H:%M:%s => 2014-05-25T08:20:03.123456, e.g 3. %Y-%m-%d %H:%M:%s%z => 2014-11-08 15:55:55.123456Z, 
     *                    e.g 4. %Y-%m-%d %H:%M:%s => 2014-11-08 15:55:55
     * @return Instance of ZonedDateTime if conversion successful
     * @throws DateTimeParseException
     */
    private ZonedDateTime parse (String _dateTime) throws DateTimeParseException  {
        // Magic here.
    }

What is best way to do it in java?

That's non-trivial alright. Most of the magic is in the following formatter:

private static final DateTimeFormatter formatter
        = new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE)
                .appendPattern("['T'][' ']")
                .append(DateTimeFormatter.ISO_LOCAL_TIME)
                .appendPattern("[XX]")
                .toFormatter();

This formatter accepts

  • A date like 2014-05-25 .
  • Either a T or a space (or both or neither, but I count on exactly one of them being in your string). The square brackets denote optional parts of the format. The single quotes denote literal parts and cause the T not to be interpreted as a pattern letter.
  • A time of day. ISO_LOCAL_TIME accepts seconds with and without a decimal fraction.
  • An optional UTC offset like Z (for zero) or -0600 .

We also need a time zone for the cases where we haven't got an offset in the string. For example:

private static final ZoneId defaultZone = ZoneId.of("America/Curacao");

What your method still needs to do is distinguish between the cases with and without UTC offset. The form with offset can be parsed directly into a ZonedDateTime . The one without cannot, so for it we need to parse into a LocalDateTime and convert.

/**
 * @param dateTime : any ISO 8601 format , e.g 1. %Y-%m-%dT%H:%M:%s%z => 2014-05-25T08:20:03.123456Z , 
 *                    e.g 2. %Y-%m-%dT%H:%M:%s => 2014-05-25T08:20:03.123456, 
 *                    e.g 3. %Y-%m-%d %H:%M:%s%z => 2014-11-08 15:55:55.123456Z, 
 *                    e.g 4. %Y-%m-%d %H:%M:%s => 2014-11-08 15:55:55
 * @return Instance of ZonedDateTime if conversion successful
 * @throws DateTimeParseException
 */
private static ZonedDateTime parse(String dateTime) {
    // Little magic here.
    TemporalAccessor parsed = formatter.parseBest(dateTime,
            ZonedDateTime::from, LocalDateTime::from);
    if (parsed instanceof ZonedDateTime) {
        return (ZonedDateTime) parsed;
    } else {
        return ((LocalDateTime) parsed).atZone(defaultZone);
    }
}

Let's try it out. I have used your four examples plus one that I added between the 1st and the 2nd.

    System.out.println(parse("2014-05-25T08:20:03.123456Z"));
    System.out.println(parse("2014-05-25T10:20:03.123456+0200"));
    System.out.println(parse("2014-05-25T08:20:03.123456"));
    System.out.println(parse("2014-11-08 15:55:55.123456Z"));
    System.out.println(parse("2014-11-08 15:55:55"));

Output:

 2014-05-25T08:20:03.123456Z 2014-05-25T10:20:03.123456+02:00 2014-05-25T08:20:03.123456-04:00[America/Curacao] 2014-11-08T15:55:55.123456Z 2014-11-08T15:55:55-04:00[America/Curacao]

As an aside prefer OffsetDateTime over ZonedDateTime for your strings with a UTC offset. The code will be the same except you will need a further conversion .toOffsetDateTime() after atZone(defaultZone) in the case where you don't get an OffsetDateTime directly from parsing. A ZonedDateTime is for a date and time with a time zone like Europe/Prague. An OffsetDateTime is for a date and time with a UTC offset. As I mentioned, Z is an offset of 0 from UTC.

Edit: Arvind Kumar Avinash' notes in a comment are useful and important enough to deserve to be in the answer proper: Some notes for the future visitors:

  1. Replacing [XX] with [XX][XXX] will also cater to the offset like +02:00.
  2. Date-time parsing/formatting types are Locale-sensitive and therefore, it's always advisable to use it with the applicable locale eg .toFormatter(Locale.ENGLISH) instead of .toFormatter() .
  3. You can use ZoneId.systemDefault() if you want the ZoneId of the JVM to be picked up automatically.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM