Home > database >  Single date formatter to parse dates with different syntax
Single date formatter to parse dates with different syntax

Time:09-05

I am having a java program . In my java program the input will be date strings of different formats like the following

a) 2021-10-09T08:00:01.111Z ( with millisecond)
b) 2021-10-09T08:00:01Z ( without milliseconds)
c) 2021-10-09T08:00Z ( no seconds )
d) 2021-10-09T08Z ( no minutes ) 

Currently I am using a built in date formatter DateTimeFormatter.ISO_OFFSET_DATE_TIME . However when i run my sample snippet program it fails

the following is my sample program

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class Tester2 {

    public static void main(String[] args) {
        
        OffsetDateTime t1 = OffsetDateTime.parse("2021-10-09T08:00:01.111Z");;
        System.out.println(t1.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        
        OffsetDateTime t2 = OffsetDateTime.parse("2021-10-09T08:00:01Z");;
        System.out.println(t2.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        
        OffsetDateTime t3 = OffsetDateTime.parse("2021-10-09T08:00Z");;
        System.out.println(t3.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
   
        OffsetDateTime t4 = OffsetDateTime.parse("2021-10-09T08Z");;
        System.out.println(t4.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
    }

}

However when I run my program it fails with the following output.

2021-10-09T08:00:01.111Z
2021-10-09T08:00:01Z
2021-10-09T08:00:00Z
Exception in thread "main" java.time.format.DateTimeParseException: Text '2021-10-09T08Z' could not be parsed at index 13
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2052)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1954)
    at java.base/java.time.OffsetDateTime.parse(OffsetDateTime.java:404)
    at java.base/java.time.OffsetDateTime.parse(OffsetDateTime.java:389)
    at test.Tester2.main(Tester2.java:20)

The following is the output I want

2021-10-09T08:00:01.111Z
2021-10-09T08:00:01Z
2021-10-09T08:00:00Z
2021-10-09T08:00:00Z

Is there any java built in data formatter that will help me to achieve the desired output. If there is no such dateformatter could you help me to write a new date formatter ? Another solutions are welcome.

CodePudding user response:

DataTimeFormatter supports optional parsing patterns with [ and ] characters.

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH[:mm[:ss[.SSS]]]X");

    OffsetDateTime t1 = OffsetDateTime.parse("2021-10-09T08:00:01.111Z", formatter);
    System.out.println(t1.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));

    OffsetDateTime t2 = OffsetDateTime.parse("2021-10-09T08:00:01Z", formatter);
    System.out.println(t2.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));

    OffsetDateTime t3 = OffsetDateTime.parse("2021-10-09T08:00Z", formatter);
    System.out.println(t3.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));

    OffsetDateTime t4 = OffsetDateTime.parse("2021-10-09T08Z", formatter);
    System.out.println(t4.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));

Output is what you asked for:

2021-10-09T08:00:01.111Z
2021-10-09T08:00:01Z
2021-10-09T08:00:00Z
2021-10-09T08:00:00Z

CodePudding user response:

One the one hand that might look weird that Java lacks functionality of parsing partial date formats (I'm facing such issues in every heterogeneous project), however, on the other hand when we parsing partial date formats we need somehow compensate missing fields, and the problem is such compensations/heuristics depend on either particular project or, moreover, particular interaction between systems, please check the PoC below, hope it will help:

import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;

public class DateTimeFormats {

    public static final DateTimeFormatter ISO_LOCAL_TIME = new DateTimeFormatterBuilder()
            .appendValue(HOUR_OF_DAY, 2)
            .optionalStart()
            .appendLiteral(':')
            .appendValue(MINUTE_OF_HOUR, 2)
            .optionalStart()
            .appendLiteral(':')
            .appendValue(SECOND_OF_MINUTE, 2)
            .optionalStart()
            .appendFraction(NANO_OF_SECOND, 0, 9, true)
            .toFormatter();

    public static final DateTimeFormatter ISO_OPTIONAL_TIME = new DateTimeFormatterBuilder()
            .parseCaseInsensitive()
            .appendLiteral('T')
            .append(ISO_LOCAL_TIME)
            .parseStrict()
            .toFormatter();

    public static final DateTimeFormatter ISO_OPTIONAL_OFFSET = new DateTimeFormatterBuilder()
            .appendOffsetId()
            .toFormatter();

    public static final DateTimeFormatter ISO_LOCAL_DATE_OPTIONAL_TIME = new DateTimeFormatterBuilder()
            .append(ISO_LOCAL_DATE)
            .appendOptional(ISO_OPTIONAL_TIME)
            .toFormatter();

    public static final DateTimeFormatter ISO_DATE_OPTIONAL_TIME = new DateTimeFormatterBuilder()
            .append(ISO_LOCAL_DATE_OPTIONAL_TIME)
            .appendOptional(ISO_OPTIONAL_OFFSET)
            .optionalStart()
            .appendLiteral('[')
            .parseCaseSensitive()
            .appendZoneRegionId()
            .appendLiteral(']')
            .toFormatter()
            // compensation
            .withZone(ZoneId.systemDefault())
            .withChronology(IsoChronology.INSTANCE);

    public static OffsetDateTime offsetDateTime(CharSequence text) {
        return offsetDateTime(text, ISO_DATE_OPTIONAL_TIME);
    }

    public static OffsetDateTime offsetDateTime(CharSequence text, DateTimeFormatter formatter) {
        Objects.requireNonNull(formatter, "formatter");
        return formatter.parse(text, DateTimeFormats::offsetDateTime);
    }

    public static OffsetDateTime offsetDateTime(TemporalAccessor temporal) {
        try {
            LocalDate date = LocalDate.from(temporal);
            Optional<LocalTime> time = Optional.ofNullable(temporal.query(TemporalQueries.localTime()));
            Optional<ZoneOffset> offset = Optional.ofNullable(temporal.query(TemporalQueries.offset()));
            Optional<ZoneId> zone = Optional.ofNullable(temporal.query(TemporalQueries.zoneId()));
            // compensation
            LocalDateTime result = LocalDateTime.of(date, time.orElse(LocalTime.MIN));
            if (offset.isPresent()) {
                return OffsetDateTime.of(result, offset.get());
            } else if (zone.isPresent()) {
                return ZonedDateTime.of(result, zone.get())
                        .toOffsetDateTime();
            }
            return result
                    // compensation
                    .atZone(ZoneId.systemDefault())
                    .toOffsetDateTime();
        } catch (DateTimeException ex) {
            throw new DateTimeException("Unable to obtain OffsetDateTime from TemporalAccessor: "  
                    temporal   " of type "   temporal.getClass().getName(), ex);
        }

    }

}

System.out.println(offsetDateTime("2021-10-09T08:00:01.1111111Z"));
System.out.println(offsetDateTime("2021-10-09T08:00:01.111Z"));
System.out.println(offsetDateTime("2021-10-09T08:00:01Z"));
System.out.println(offsetDateTime("2021-10-09T08:00Z"));
System.out.println(offsetDateTime("2021-10-09T08Z"));
System.out.println(offsetDateTime("2021-10-09Z"));
System.out.println(offsetDateTime("2021-10-09[Australia/Melbourne]"));
System.out.println(offsetDateTime("2021-10-09T08:00:01.111[Australia/Melbourne]"));
System.out.println(offsetDateTime("2021-10-09T08:00:01[Australia/Melbourne]"));
System.out.println(offsetDateTime("2021-10-09T08:00[Australia/Melbourne]"));
System.out.println(offsetDateTime("2021-10-09T08[Australia/Melbourne]"));
System.out.println(offsetDateTime("2021-10-09[Australia/Melbourne]"));
2021-10-09T08:00:01.111111100Z
2021-10-09T08:00:01.111Z
2021-10-09T08:00:01Z
2021-10-09T08:00Z
2021-10-09T08:00Z
2021-10-09T00:00Z
2021-10-09T00:00 11:00
2021-10-09T08:00:01.111 11:00
2021-10-09T08:00:01 11:00
2021-10-09T08:00 11:00
2021-10-09T08:00 11:00
2021-10-09T00:00 11:00

CodePudding user response:

I once had to deal with parsing dates from Strings that could come in ANY format not known in advance. I resolved it in the following way. I prepared a config file that contained on each line a date format that I wanted to support. When String came I tried to parse it with each format from the file until I succeeded or went through all available formats. Note that in this case the order of the formats is important as if you want European formats take precedence over US one you would place European formats first or vise-versa. In any case, I wrote a small article where I explained this idea in detail: Java 8 java.time package: parsing any string to date

CodePudding user response:

There's probably a better solution, but SimpleDateFormat will at a bare minimum allow you to customize the format per object. For example:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.parse("2021-10-09T08:00:01Z");

Would parse into the desired date object. If you want to do the opposite and go from date to string:

sdf.format(new Date(1633780801000));
  • Related