diff --git a/src/main/java/org/jabref/model/entry/Date.java b/src/main/java/org/jabref/model/entry/Date.java index 219a7b294e5..8c127f6bf2f 100644 --- a/src/main/java/org/jabref/model/entry/Date.java +++ b/src/main/java/org/jabref/model/entry/Date.java @@ -48,18 +48,16 @@ public class Date { "uuuu.MM.d", // covers 2015.10.15 "d MMMM u/d MMMM u", // covers 20 January 2015/20 February 2015 "d MMMM u", // covers 20 January 2015 - "d MMMM u / d MMMM u" + "d MMMM u / d MMMM u", + "u'-'", // covers 2015- + "u'?'", // covers 2023? + "u G", // covers 1 BC and 1 AD + "uuuu G", // covers 0030 BC and 0005 AD + "u G/u G", // covers 30 BC/5 AD + "uuuu G/uuuu G", // covers 0030 BC/0005 AD + "uuuu-MM G/uuuu-MM G" // covers 0030-01 BC/0005-02 AD ); - /* TODO: The following date formats do not yet work and need to be created with tests - * "u G", // covers 1 BC - * "u G / u G", // covers 30 BC / 5 AD - * "uuuu G / uuuu G", // covers 0030 BC / 0005 AD - * "uuuu-MM G / uuuu-MM G", // covers 0030-01 BC / 0005-02 AD - * "u'-'", // covers 2015- - * "u'?'", // covers 2023? - */ - SIMPLE_DATE_FORMATS = formatStrings.stream() .map(DateTimeFormatter::ofPattern) .reduce(new DateTimeFormatterBuilder(), @@ -167,7 +165,71 @@ public static Optional parse(String dateString) { LOGGER.debug("Invalid Date format range", e); return Optional.empty(); } + } else if (dateString.matches( + "\\d{1,4} BC/\\d{1,4} AD|" + // 30 BC/5 AD and 0030 BC/0005 AD + "\\d{1,4} BC/\\d{1,4} BC|" + // 30 BC/10 BC and 0030 BC/0010 BC + "\\d{1,4} AD/\\d{1,4} AD|" + // 5 AD/10 AD and 0005 AD/0010 AD + "\\d{1,4}-\\d{1,2} BC/\\d{1,4}-\\d{1,2} AD|" + // 5 AD/10 AD and 0005 AD/0010 AD + "\\d{1,4}-\\d{1,2} BC/\\d{1,4}-\\d{1,2} BC|" + // 5 AD/10 AD and 0005 AD/0010 AD + "\\d{1,4}-\\d{1,2} AD/\\d{1,4}-\\d{1,2} AD" // 5 AD/10 AD and 0005 AD/0010 AD + )) { + try { + String[] strDates = dateString.split("/"); + TemporalAccessor parsedDate = parseDateWithEraIndicator(strDates[0]); + TemporalAccessor parsedEndDate = parseDateWithEraIndicator(strDates[1]); + return Optional.of(new Date(parsedDate, parsedEndDate)); + } catch (DateTimeParseException e) { + LOGGER.debug("Invalid Date format range", e); + return Optional.empty(); + } + } else if (dateString.matches( + "\\d{1,4} BC / \\d{1,4} AD|" + // 30 BC / 5 AD and 0030 BC / 0005 AD + "\\d{1,4} BC / \\d{1,4} BC|" + // 30 BC / 10 BC and 0030 BC / 0010 BC + "\\d{1,4} AD / \\d{1,4} AD|" + // 5 AD / 10 AD and 0005 AD / 0010 AD + "\\d{1,4}-\\d{1,2} BC / \\d{1,4}-\\d{1,2} AD|" + // 5 AD/10 AD and 0005 AD/0010 AD + "\\d{1,4}-\\d{1,2} BC / \\d{1,4}-\\d{1,2} BC|" + // 5 AD/10 AD and 0005 AD/0010 AD + "\\d{1,4}-\\d{1,2} AD / \\d{1,4}-\\d{1,2} AD" // 5 AD/10 AD and 0005 AD/0010 AD + )) { + try { + String[] strDates = dateString.split(" / "); + TemporalAccessor parsedDate = parseDateWithEraIndicator(strDates[0]); + TemporalAccessor parsedEndDate = parseDateWithEraIndicator(strDates[1]); + return Optional.of(new Date(parsedDate, parsedEndDate)); + } catch (DateTimeParseException e) { + LOGGER.debug("Invalid Date format range", e); + return Optional.empty(); + } + } + + // if dateString is single year + if (dateString.matches("\\d{4}-|" + "\\d{4}\\?")) { + try { + String year = dateString.substring(0, dateString.length() - 1); + TemporalAccessor parsedDate = SIMPLE_DATE_FORMATS.parse(year); + return Optional.of(new Date(parsedDate)); + } catch (DateTimeParseException e) { + LOGGER.debug("Invalid Date format", e); + return Optional.empty(); + } + } + + // handle the new date formats with era indicators + if (dateString.matches( + "\\d{1,4} BC|" + // covers 1 BC + "\\d{1,4} AD|" + // covers 1 BC + "\\d{1,4}-\\d{1,2} BC|" + // covers 0030-01 BC + "\\d{1,4}-\\d{1,2} AD" // covers 0005-01 AD + )) { + try { + // Parse the date with era indicator + TemporalAccessor date = parseDateWithEraIndicator(dateString); + return Optional.of(new Date(date)); + } catch (DateTimeParseException e) { + LOGGER.debug("Invalid Date format with era indicator", e); + return Optional.empty(); + } } + try { TemporalAccessor parsedDate = SIMPLE_DATE_FORMATS.parse(dateString); return Optional.of(new Date(parsedDate)); @@ -210,6 +272,28 @@ private static Optional convertToInt(String value) { } } + /** + * Create a date with a string with era indicator. + * + * @param dateString the string which contain era indicator to extract the date information + * @return the date information with TemporalAccessor type + */ + private static TemporalAccessor parseDateWithEraIndicator(String dateString) { + String yearString = dateString.strip().substring(0, dateString.length() - 2); + + String[] parts = yearString.split("-"); + int year = Integer.parseInt(parts[0].strip()); + + if (dateString.endsWith("BC")) { + year = 1 - year; + } + if (parts.length > 1) { + int month = Integer.parseInt(parts[1].strip()); + return YearMonth.of(year, month); + } + return Year.of(year); + } + public String getNormalized() { return NORMALIZED_DATE_FORMATTER.format(date); } diff --git a/src/test/java/org/jabref/model/entry/DateTest.java b/src/test/java/org/jabref/model/entry/DateTest.java index e67cad410d4..f25fa62136f 100644 --- a/src/test/java/org/jabref/model/entry/DateTest.java +++ b/src/test/java/org/jabref/model/entry/DateTest.java @@ -46,8 +46,16 @@ private static Stream validDates() { Arguments.of(LocalDate.of(2015, Month.OCTOBER, 15), "2015.10.15"), Arguments.of(LocalDate.of(-10000, Month.OCTOBER, 15), "-10000-10-15"), Arguments.of(YearMonth.of(2015, Month.NOVEMBER), "2015/11"), - Arguments.of(LocalDate.of(2015, Month.JANUARY, 15), "15 January 2015") - ); + Arguments.of(LocalDate.of(2015, Month.JANUARY, 15), "15 January 2015"), + Arguments.of(Year.of(2015), "2015-"), + Arguments.of(Year.of(2015), "2015?"), + Arguments.of(Year.of(-29), "30 BC"), + Arguments.of(Year.of(-29), "0030 BC"), + Arguments.of(Year.of(2), "2 AD"), + Arguments.of(Year.of(2), "0002 AD"), + Arguments.of(YearMonth.of(-29, Month.JANUARY), "0030-01 BC"), + Arguments.of(YearMonth.of(5, Month.FEBRUARY), "0005-02 AD") + ); } @ParameterizedTest @@ -63,8 +71,17 @@ private static Stream validDateRanges() { Arguments.of(LocalDate.of(2015, Month.JANUARY, 15), LocalDate.of(2015, Month.FEBRUARY, 25), "2015-01-15/2015-02-25"), Arguments.of(LocalDate.of(2015, Month.JANUARY, 15), LocalDate.of(2015, Month.FEBRUARY, 25), "2015-01-15 / 2015-02-25"), Arguments.of(LocalDate.of(2015, Month.JANUARY, 15), LocalDate.of(2015, Month.FEBRUARY, 25), "15 January 2015/25 February 2015"), - Arguments.of(LocalDate.of(2015, Month.JANUARY, 15), LocalDate.of(2015, Month.FEBRUARY, 25), "15 January 2015 / 25 February 2015") - ); + Arguments.of(LocalDate.of(2015, Month.JANUARY, 15), LocalDate.of(2015, Month.FEBRUARY, 25), "15 January 2015 / 25 February 2015"), + Arguments.of(Year.of(-29), Year.of(5), "30 BC/5 AD"), + Arguments.of(Year.of(-29), Year.of(5), "30 BC / 5 AD"), + Arguments.of(Year.of(-29), Year.of(5), "0030 BC/0005 AD"), + Arguments.of(Year.of(-29), Year.of(-9), "0030 BC/0010 BC"), + Arguments.of(Year.of(5), Year.of(10), "0005 AD/0010 AD"), + Arguments.of(YearMonth.of(-29, Month.JANUARY), YearMonth.of(5, Month.FEBRUARY), "0030-01 BC/0005-02 AD"), + Arguments.of(YearMonth.of(-29, Month.JANUARY), YearMonth.of(5, Month.FEBRUARY), "0030-01 BC / 0005-02 AD"), + Arguments.of(YearMonth.of(-29, Month.JANUARY), YearMonth.of(-9, Month.FEBRUARY), "0030-01 BC / 0010-02 BC"), + Arguments.of(YearMonth.of(5, Month.JANUARY), YearMonth.of(20, Month.FEBRUARY), "0005-01 AD / 0020-02 AD") + ); } @ParameterizedTest