diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvCell.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvCell.java index 740f1f263..f8e3b9f68 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvCell.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvCell.java @@ -164,7 +164,13 @@ protected void setCellValueImpl(Date value) { if (value == null) { return; } - this.dateValue = LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()); + if (value instanceof java.sql.Date) { + this.dateValue = ((java.sql.Date) value).toLocalDate().atStartOfDay(); + } else if (value instanceof java.sql.Time) { + this.dateValue = ((java.sql.Time) value).toLocalTime().atDate(java.time.LocalDate.of(1970, 1, 1)); + } else { + this.dateValue = LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()); + } this.cellType = CellType.NUMERIC; this.numericCellType = NumericCellTypeEnum.DATE; } diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvRowTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvRowTest.java index 2af4a1b7c..7d65df234 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvRowTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvRowTest.java @@ -20,10 +20,15 @@ package org.apache.fesod.sheet.format; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.List; import org.apache.fesod.sheet.FastExcel; +import org.apache.fesod.sheet.metadata.csv.CsvCell; import org.apache.fesod.sheet.metadata.csv.CsvRow; import org.apache.fesod.sheet.metadata.csv.CsvSheet; import org.apache.fesod.sheet.metadata.csv.CsvWorkbook; @@ -120,6 +125,88 @@ void testCsvWriteWithModelShouldSuccess() { .doWrite(modelData()); } + /** + * Verifies that {@link CsvCell} handles {@link java.sql.Date} the same way as + * {@link org.apache.fesod.sheet.metadata.data.WriteCellData}: the date is extracted + * via {@code toLocalDate().atStartOfDay()}, stripping any time component that may + * exist in the underlying milliseconds (common when JDBC drivers create + * {@code java.sql.Date} from a {@code java.util.Date} with time info). + */ + @Test + void testCsvCellSqlDateConversion() { + // Create a java.sql.Date from a java.util.Date that has a time component + Calendar cal = Calendar.getInstance(); + cal.set(2023, Calendar.JUNE, 15, 23, 30, 0); + cal.set(Calendar.MILLISECOND, 0); + java.sql.Date sqlDate = new java.sql.Date(cal.getTimeInMillis()); + + Cell cell = csvRow.createCell(0, CellType.NUMERIC); + cell.setCellValue(sqlDate); + + LocalDateTime dateValue = ((CsvCell) cell).getLocalDateTimeCellValue(); + // java.sql.Date is date-only: time component must be stripped to midnight + Assertions.assertEquals(LocalDateTime.of(2023, 6, 15, 0, 0, 0), dateValue); + } + + /** + * Verifies that {@link CsvCell} handles {@link java.sql.Time} the same way as + * {@link org.apache.fesod.sheet.metadata.data.WriteCellData}: the time is extracted + * via {@code toLocalTime().atDate(LocalDate.of(1970, 1, 1))}, stripping any date + * component that may exist in the underlying milliseconds. + */ + @Test + void testCsvCellSqlTimeConversion() { + // Create a java.sql.Time from a java.util.Date that has a date component + Calendar cal = Calendar.getInstance(); + cal.set(2023, Calendar.JUNE, 15, 12, 30, 45); + cal.set(Calendar.MILLISECOND, 0); + java.sql.Time sqlTime = new java.sql.Time(cal.getTimeInMillis()); + + Cell cell = csvRow.createCell(0, CellType.NUMERIC); + cell.setCellValue(sqlTime); + + LocalDateTime dateValue = ((CsvCell) cell).getLocalDateTimeCellValue(); + // java.sql.Time is time-only: date component must be normalized to 1970-01-01 + Assertions.assertEquals(LocalDateTime.of(1970, 1, 1, 12, 30, 45), dateValue); + } + + /** + * Real-file integration test: writes a physical CSV file containing + * {@code java.sql.Date} and {@code java.sql.Time} values via the + * {@link CsvCell} API, then reads the file back to verify the output. + *

+ * Without the fix, {@code CsvCell.setCellValueImpl(Date)} calls + * {@code value.toInstant()} which throws {@code UnsupportedOperationException} + * on Java 9+ for {@code java.sql.Date}/{@code java.sql.Time}. + */ + @Test + void csvWrite_withSqlDateAndTime_producesCorrectFile() throws Exception { + File csvFile = new File(tempDir, "sql-date-test.csv"); + + try (java.io.Writer writer = Files.newBufferedWriter(csvFile.toPath(), StandardCharsets.UTF_8)) { + CsvWorkbook workbook = new CsvWorkbook(writer, null, false, false, StandardCharsets.UTF_8, false); + CsvSheet sheet = (CsvSheet) workbook.createSheet(); + CsvRow row = (CsvRow) sheet.createRow(0); + + // java.sql.Date — without fix: UnsupportedOperationException + Cell dateCell = row.createCell(0, CellType.NUMERIC); + dateCell.setCellValue(java.sql.Date.valueOf("2024-01-15")); + + // java.sql.Time — without fix: UnsupportedOperationException + Cell timeCell = row.createCell(1, CellType.NUMERIC); + timeCell.setCellValue(java.sql.Time.valueOf("12:30:45")); + + sheet.close(); + } + + // Read file back and verify date/time strings + List lines = Files.readAllLines(csvFile.toPath(), StandardCharsets.UTF_8); + Assertions.assertEquals(1, lines.size()); + String line = lines.get(0); + Assertions.assertTrue(line.contains("2024-01-15"), "CSV should contain date 2024-01-15, got: " + line); + Assertions.assertTrue(line.contains("12:30:45"), "CSV should contain time 12:30:45, got: " + line); + } + private static List modelData() { List data = new ArrayList<>(); data.add(new SimpleCsvData("1", "Jackson", "20"));