Optional iso dates (#94)

* Optionally use ISO formatting for dates

Prior to this commit we always used the date pattern as
assigned in the Excel workbook for the cell. Which could
lead to surprising results.

We now allow the dates to be formatted as an ISO date time with
offset, so dates/times are consistently formatted in the result.

Closes: #88
This commit is contained in:
Marten Deinum
2022-04-12 10:58:18 +02:00
committed by GitHub
parent c8b48a05d5
commit 7820d8ea6a
3 changed files with 52 additions and 3 deletions

View File

@@ -91,6 +91,7 @@ public RowMapper rowMapper() {
| `rowSetFactory` | no | `DefaultRowSetFactory` | For reading rows a `RowSet` abstraction is used. To construct a `RowSet` for the current `Sheet` a `RowSetFactory` is needed. The `DefaultRowSetFactory` constructs a `DefaultRowSet` and `DefaultRowSetMetaData`. For construction of the latter a `ColumnNameExtractor` is needed. At the moment there are 2 implementations
| `skippedRowsCallback` | no | `null` | When rows are skipped an optional `RowCallbackHandler` is called with the skipped row. This comes in handy when one needs to write the skipped rows to another file or create some logging.
| `strict` | no | `true` | This controls wether or not an exception is thrown if the file doesn't exists or isn't readable, by default an exception will be thrown.
| `datesAsIso` | no | `false` | Controls if dates need to be parsed as ISO or to use the format as specified in the excel sheet. *NOTE:* Only for the `PoiItemReader` **not** the `StreamingXlsxReader`!
|===
- `StaticColumnNameExtractor` uses a preset list of column names.

View File

@@ -44,9 +44,11 @@ public class PoiItemReader<T> extends AbstractExcelItemReader<T> {
private InputStream inputStream;
private boolean datesAsIso = false;
@Override
protected Sheet getSheet(final int sheet) {
return new PoiSheet(this.workbook.getSheetAt(sheet));
return new PoiSheet(this.workbook.getSheetAt(sheet), this.datesAsIso);
}
@Override
@@ -89,4 +91,13 @@ public class PoiItemReader<T> extends AbstractExcelItemReader<T> {
}
this.workbook.setMissingCellPolicy(Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
}
/**
* Instead of using the format defined in the Excel sheet, read the date/time fields as an ISO formatted
* string instead. This is by default {@code false} to leave the original behavior.
* @param datesAsIso default {@code false}
*/
public void setDatesAsIso(boolean datesAsIso) {
this.datesAsIso = datesAsIso;
}
}

View File

@@ -16,13 +16,17 @@
package org.springframework.batch.extensions.excel.poi;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Row;
@@ -37,9 +41,10 @@ import org.springframework.lang.Nullable;
*/
class PoiSheet implements Sheet {
private final DataFormatter dataFormatter = new DataFormatter();
private final DataFormatter dataFormatter;
private final org.apache.poi.ss.usermodel.Sheet delegate;
private final boolean datesAsIso;
private final int numberOfRows;
@@ -50,12 +55,15 @@ class PoiSheet implements Sheet {
/**
* Constructor which takes the delegate sheet.
* @param delegate the apache POI sheet
* @param datesAsIso should we format the dates as ISO or use the Excel formatting instead
*/
PoiSheet(final org.apache.poi.ss.usermodel.Sheet delegate) {
PoiSheet(final org.apache.poi.ss.usermodel.Sheet delegate, boolean datesAsIso) {
super();
this.delegate = delegate;
this.datesAsIso = datesAsIso;
this.numberOfRows = this.delegate.getLastRowNum() + 1;
this.name = this.delegate.getSheetName();
this.dataFormatter = this.datesAsIso ? new IsoFormattingDateDataFormatter() : new DataFormatter();
}
/**
@@ -134,4 +142,33 @@ class PoiSheet implements Sheet {
};
}
/**
* Specialized subclass for additionally formatting the date into an ISO date/time.
*
* @author Marten Deinum
* @see DateTimeFormatter#ISO_OFFSET_DATE_TIME
*/
private static class IsoFormattingDateDataFormatter extends DataFormatter {
@Override
public String formatCellValue(Cell cell, FormulaEvaluator evaluator, ConditionalFormattingEvaluator cfEvaluator) {
if (cell == null) {
return "";
}
CellType cellType = cell.getCellType();
if (cellType == CellType.FORMULA) {
if (evaluator == null) {
return cell.getCellFormula();
}
cellType = evaluator.evaluateFormulaCell(cell);
}
if (cellType == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell, cfEvaluator)) {
LocalDateTime value = cell.getLocalDateTimeCellValue();
return (value != null) ? value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) : "";
}
return super.formatCellValue(cell, evaluator, cfEvaluator);
}
}
}