@@ -12,6 +12,8 @@ import org.apache.poi.ss.usermodel.Sheet
12
12
import org.apache.poi.ss.usermodel.Workbook
13
13
import org.apache.poi.ss.usermodel.WorkbookFactory
14
14
import org.apache.poi.ss.util.CellReference
15
+ import org.apache.poi.util.LocaleUtil
16
+ import org.apache.poi.util.LocaleUtil.getUserTimeZone
15
17
import org.apache.poi.xssf.usermodel.XSSFWorkbook
16
18
import org.jetbrains.kotlinx.dataframe.AnyFrame
17
19
import org.jetbrains.kotlinx.dataframe.AnyRow
@@ -29,7 +31,8 @@ import java.io.OutputStream
29
31
import java.net.URL
30
32
import java.time.LocalDate
31
33
import java.time.LocalDateTime
32
- import java.util.*
34
+ import java.util.Calendar
35
+ import java.util.Date
33
36
34
37
public class Excel : SupportedFormat {
35
38
override fun readDataFrame (stream : InputStream , header : List <String >): AnyFrame = DataFrame .readExcel(stream)
@@ -82,7 +85,7 @@ public fun DataFrame.Companion.readExcel(
82
85
return wb.use { readExcel(it, sheetName, columns, rowsCount) }
83
86
}
84
87
85
- internal fun DataFrame.Companion.readExcel (
88
+ public fun DataFrame.Companion.readExcel (
86
89
wb : Workbook ,
87
90
sheetName : String? = null,
88
91
columns : String? = null,
@@ -91,7 +94,14 @@ internal fun DataFrame.Companion.readExcel(
91
94
val sheet: Sheet = sheetName
92
95
?.let { wb.getSheet(it) ? : error(" Sheet with name $sheetName not found" ) }
93
96
? : wb.getSheetAt(0 )
97
+ return readExcel(sheet, columns, rowsCount)
98
+ }
94
99
100
+ public fun DataFrame.Companion.readExcel (
101
+ sheet : Sheet ,
102
+ columns : String? = null,
103
+ rowsCount : Int? = null
104
+ ): AnyFrame {
95
105
val columnIndexes = if (columns != null ) {
96
106
columns.split(" ," ).flatMap {
97
107
if (it.contains(" :" )) {
@@ -108,7 +118,12 @@ internal fun DataFrame.Companion.readExcel(
108
118
val headerRow = sheet.getRow(0 )
109
119
val valueRows = sheet.drop(1 ).let { if (rowsCount != null ) it.take(rowsCount) else it }
110
120
val columns = columnIndexes.map { index ->
111
- val name = headerRow.getCell(index)?.stringCellValue ? : CellReference .convertNumToColString(index)
121
+ val headerCell = headerRow.getCell(index)
122
+ val name = if (headerCell?.cellType == CellType .NUMERIC ) {
123
+ headerCell.numericCellValue.toString() // Support numeric-named columns
124
+ } else {
125
+ headerCell?.stringCellValue ? : CellReference .convertNumToColString(index) // Use Excel column names if no data
126
+ }
112
127
val values = valueRows.map {
113
128
val cell: Cell ? = it.getCell(index)
114
129
when (cell?.cellType) {
@@ -171,6 +186,17 @@ public fun <T> DataFrame<T>.writeExcel(
171
186
factory : () -> Workbook
172
187
) {
173
188
val wb: Workbook = factory()
189
+ writeExcel(wb, columnsSelector, sheetName, writeHeader)
190
+ wb.write(outputStream)
191
+ wb.close()
192
+ }
193
+
194
+ public fun <T > DataFrame<T>.writeExcel (
195
+ wb : Workbook ,
196
+ columnsSelector : ColumnsSelector <T , * > = { all() },
197
+ sheetName : String? = null,
198
+ writeHeader : Boolean = true
199
+ ): Sheet {
174
200
val sheet = if (sheetName != null ) {
175
201
wb.createSheet(sheetName)
176
202
} else {
@@ -189,6 +215,14 @@ public fun <T> DataFrame<T>.writeExcel(
189
215
i++
190
216
}
191
217
218
+ val createHelper = wb.creationHelper
219
+ val cellStyleDate = wb.createCellStyle()
220
+ val cellStyleDateTime = wb.createCellStyle()
221
+ val cellStyleTime = wb.createCellStyle()
222
+ cellStyleDate.dataFormat = createHelper.createDataFormat().getFormat(" dd.mm.yyyy" )
223
+ cellStyleDateTime.dataFormat = createHelper.createDataFormat().getFormat(" dd.mm.yyyy hh:mm:ss" )
224
+ cellStyleTime.dataFormat = createHelper.createDataFormat().getFormat(" hh:mm:ss" )
225
+
192
226
columns.forEach {
193
227
val row = sheet.createRow(i)
194
228
it.values().forEachIndexed { index, any ->
@@ -198,12 +232,35 @@ public fun <T> DataFrame<T>.writeExcel(
198
232
if (any != null ) {
199
233
val cell = row.createCell(index)
200
234
cell.setCellValueByGuessedType(any)
235
+
236
+ when (any) {
237
+ is LocalDate , is kotlinx.datetime.LocalDate -> {
238
+ cell.cellStyle = cellStyleDate
239
+ }
240
+ is Calendar , is Date -> {
241
+ cell.cellStyle = cellStyleDateTime
242
+ }
243
+ is LocalDateTime -> {
244
+ if (any.year < 1900 ) {
245
+ cell.cellStyle = cellStyleTime
246
+ } else {
247
+ cell.cellStyle = cellStyleDateTime
248
+ }
249
+ }
250
+ is kotlinx.datetime.LocalDateTime -> {
251
+ if (any.year < 1900 ) {
252
+ cell.cellStyle = cellStyleTime
253
+ } else {
254
+ cell.cellStyle = cellStyleDateTime
255
+ }
256
+ }
257
+ else -> {}
258
+ }
201
259
}
202
260
}
203
261
i++
204
262
}
205
- wb.write(outputStream)
206
- wb.close()
263
+ return sheet
207
264
}
208
265
209
266
private fun Cell.setCellValueByGuessedType (any : Any ) {
@@ -221,16 +278,16 @@ private fun Cell.setCellValueByGuessedType(any: Any) {
221
278
this .setCellValue(any)
222
279
}
223
280
is LocalDateTime -> {
224
- this .setCellValue (any)
281
+ this .setTime (any)
225
282
}
226
283
is Boolean -> {
227
284
this .setCellValue(any)
228
285
}
229
286
is Calendar -> {
230
- this .setCellValue (any.time)
287
+ this .setDate (any.time)
231
288
}
232
289
is Date -> {
233
- this .setCellValue (any)
290
+ this .setDate (any)
234
291
}
235
292
is RichTextString -> {
236
293
this .setCellValue(any)
@@ -242,7 +299,7 @@ private fun Cell.setCellValueByGuessedType(any: Any) {
242
299
this .setCellValue(any.toJavaLocalDate())
243
300
}
244
301
is kotlinx.datetime.LocalDateTime -> {
245
- this .setCellValue (any.toJavaLocalDateTime())
302
+ this .setTime (any.toJavaLocalDateTime())
246
303
}
247
304
// Another option would be to serialize everything else to string,
248
305
// but people can convert columns to string with any serialization framework they want
@@ -252,3 +309,25 @@ private fun Cell.setCellValueByGuessedType(any: Any) {
252
309
}
253
310
}
254
311
}
312
+
313
+ /* *
314
+ * Set LocalDateTime value correctly also if date have zero value in Excel.
315
+ * Zero date is usually used fore storing time component only,
316
+ * is displayed as 00.01.1900 in Excel and as 30.12.1899 in LibreOffice Calc and also in POI.
317
+ * POI can not set 1899 year directly.
318
+ */
319
+ private fun Cell.setTime (localDateTime : LocalDateTime ) {
320
+ this .setCellValue(DateUtil .getExcelDate(localDateTime.plusDays(1 )) - 1.0 )
321
+ }
322
+
323
+ /* *
324
+ * Set Date value correctly also if date have zero value in Excel.
325
+ * Zero date is usually used fore storing time component only,
326
+ * is displayed as 00.01.1900 in Excel and as 30.12.1899 in LibreOffice Calc and also in POI.
327
+ * POI can not set 1899 year directly.
328
+ */
329
+ private fun Cell.setDate (date : Date ) {
330
+ val calStart = LocaleUtil .getLocaleCalendar()
331
+ calStart.time = date
332
+ this .setTime(calStart.toInstant().atZone(getUserTimeZone().toZoneId()).toLocalDateTime())
333
+ }
0 commit comments