diff --git a/spring-batch-excel/README.adoc b/spring-batch-excel/README.adoc index 428ca27..8f366ed 100644 --- a/spring-batch-excel/README.adoc +++ b/spring-batch-excel/README.adoc @@ -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. diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiItemReader.java b/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiItemReader.java index 541c710..cf1c200 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiItemReader.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiItemReader.java @@ -44,9 +44,11 @@ public class PoiItemReader extends AbstractExcelItemReader { 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 extends AbstractExcelItemReader { } 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; + } } diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiSheet.java b/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiSheet.java index d39eb7e..02e0cb1 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiSheet.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/extensions/excel/poi/PoiSheet.java @@ -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); + } + } }