package org.jabref.model.entry; import java.time.LocalDate; import java.time.Year; import java.time.YearMonth; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; public class Date { private final TemporalAccessor date; public Date(TemporalAccessor date) { this.date = date; } /** * Try to parse the following formats * - "M/y" (covers 9/15, 9/2015, and 09/2015) * - "MMMM (dd), yyyy" (covers September 1, 2015 and September, 2015) * - "yyyy-MM-dd" (covers 2009-1-15) * - "dd-MM-yyyy" (covers 15-1-2009) * - "d.M.uuuu" (covers 15.1.2015) * - "uuuu.M.d" (covers 2015.1.15) * The code is essentially taken from http://stackoverflow.com/questions/4024544/how-to-parse-dates-in-multiple-formats-using-simpledateformat. */ public static Optional<Date> parse(String dateString) { List<String> formatStrings = Arrays.asList("uuuu-M-d", "uuuu-M", "d-M-uuuu", "M/uu", "M/uuuu", "MMMM d, uuuu", "MMMM, uuuu", "d.M.uuuu", "uuuu.M.d", "uuuu"); for (String formatString : formatStrings) { try { TemporalAccessor parsedDate = DateTimeFormatter.ofPattern(formatString).parse(dateString); return Optional.of(new Date(parsedDate)); } catch (DateTimeParseException ignored) { // Ignored } } return Optional.empty(); } public static Optional<Date> parse(Optional<String> yearValue, Optional<String> monthValue, Optional<String> dayValue) { Optional<Year> year = yearValue.flatMap(Date::convertToInt).map(Year::of); Optional<Month> month = monthValue.flatMap(Month::parse); Optional<Integer> day = dayValue.flatMap(Date::convertToInt); if (year.isPresent()) { TemporalAccessor date; if (month.isPresent()) { if (day.isPresent()) { date = LocalDate.of(year.get().getValue(), month.get().getNumber(), day.get()); } else { date = YearMonth.of(year.get().getValue(), month.get().getNumber()); } } else { date = year.get(); } return Optional.of(new Date(date)); } return Optional.empty(); } private static Optional<Integer> convertToInt(String value) { try { return Optional.of(Integer.valueOf(value)); } catch (NumberFormatException ex) { return Optional.empty(); } } public String getNormalized() { DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("uuuu[-MM][-dd]"); return dateFormatter.format(date); } public Optional<Integer> getYear() { return get(ChronoField.YEAR); } public Optional<Integer> get(ChronoField field) { if (date.isSupported(field)) { return Optional.of(date.get(field)); } else { return Optional.empty(); } } public Optional<Month> getMonth() { return get(ChronoField.MONTH_OF_YEAR).flatMap(Month::getMonthByNumber); } public Optional<Integer> getDay() { return get(ChronoField.DAY_OF_MONTH); } public TemporalAccessor toTemporalAccessor() { return date; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Date date1 = (Date) o; return Objects.equals(getYear(), date1.getYear()) && Objects.equals(getMonth(), date1.getMonth()) && Objects.equals(getDay(), date1.getDay()); } @Override public String toString() { return "Date{" + "date=" + date + '}'; } @Override public int hashCode() { return Objects.hash(date); } }