001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.search.impl;
007
008import java.time.Instant;
009import java.time.ZoneOffset;
010import java.time.format.DateTimeFormatter;
011import java.time.format.DateTimeFormatterBuilder;
012import java.time.temporal.ChronoField;
013import java.util.ArrayList;
014import java.util.List;
015
016import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
017import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
018import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
019
020/**
021 * A utility class for parsing a variety of different date/time formats into an Instant.
022 *
023 * @author dbernstein
024 */
025public class InstantParser {
026
027    private static final List<DateTimeFormatter> VALID_DATE_FORMATS = new ArrayList<>();
028
029    static {
030        VALID_DATE_FORMATS.add(ISO_DATE_TIME);
031        VALID_DATE_FORMATS.add(ISO_OFFSET_DATE_TIME);
032        VALID_DATE_FORMATS.add(RFC_1123_DATE_TIME);
033        final var zoneId = ZoneOffset.UTC;
034        VALID_DATE_FORMATS.add(new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd")
035                .parseDefaulting(ChronoField.NANO_OF_DAY, 0).toFormatter().withZone(zoneId));
036        VALID_DATE_FORMATS.add(new DateTimeFormatterBuilder().appendPattern("yyyyMMdd")
037                .parseDefaulting(ChronoField.NANO_OF_DAY, 0).toFormatter().withZone(zoneId));
038        VALID_DATE_FORMATS.add(new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd HH:mm:ss")
039                .parseDefaulting(ChronoField.NANO_OF_DAY, 0).toFormatter().withZone(zoneId));
040        VALID_DATE_FORMATS.add(new DateTimeFormatterBuilder().appendPattern("yyyyMMdd HH:mm:ss")
041                .parseDefaulting(ChronoField.NANO_OF_DAY, 0).toFormatter().withZone(zoneId));
042    }
043
044    private InstantParser() { }
045
046    /**
047     * Parse a datestring into an instant.  If timezone or time information is missing, UTC is assumed.
048     *
049     * @param dateString The date string
050     * @return an instant
051     */
052    public static Instant parse(final String dateString) {
053        for (final DateTimeFormatter formatter : VALID_DATE_FORMATS) {
054            try {
055                final var temporalAccessor = formatter.parse(dateString);
056                return Instant.from(temporalAccessor);
057            } catch (final Exception e) {
058                //ignore failures - if no date string is parsable, an error is thrown below
059            }
060        }
061        throw new IllegalArgumentException("Invalid date format: \"" + dateString + "\"");
062    }
063
064}