Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 2573 support biblatex date formats #10511

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 94 additions & 10 deletions src/main/java/org/jabref/model/entry/Date.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -167,7 +165,71 @@ public static Optional<Date> 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));
Expand Down Expand Up @@ -210,6 +272,28 @@ private static Optional<Integer> 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);
}
Expand Down
25 changes: 21 additions & 4 deletions src/test/java/org/jabref/model/entry/DateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,16 @@ private static Stream<Arguments> 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
Expand All @@ -63,8 +71,17 @@ private static Stream<Arguments> 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
Expand Down