diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java index 88b90d955..50dcec396 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java @@ -75,6 +75,9 @@ import org.apache.fesod.sheet.converters.string.StringNumberConverter; import org.apache.fesod.sheet.converters.string.StringStringConverter; import org.apache.fesod.sheet.converters.url.UrlImageConverter; +import org.apache.fesod.sheet.converters.zoneddatetime.ZonedDateTimeDateConverter; +import org.apache.fesod.sheet.converters.zoneddatetime.ZonedDateTimeNumberConverter; +import org.apache.fesod.sheet.converters.zoneddatetime.ZonedDateTimeStringConverter; /** * Load default handler @@ -117,6 +120,9 @@ private static void initAllConverter() { putAllConverter(new LocalDateTimeNumberConverter()); putAllConverter(new LocalDateTimeStringConverter()); + putAllConverter(new ZonedDateTimeNumberConverter()); + putAllConverter(new ZonedDateTimeStringConverter()); + putAllConverter(new DoubleBooleanConverter()); putAllConverter(new DoubleNumberConverter()); putAllConverter(new DoubleStringConverter()); @@ -153,6 +159,7 @@ private static void initDefaultWriteConverter() { putWriteConverter(new DateDateConverter()); putWriteConverter(new LocalDateTimeDateConverter()); putWriteConverter(new LocalDateDateConverter()); + putWriteConverter(new ZonedDateTimeDateConverter()); putWriteConverter(new DoubleNumberConverter()); putWriteConverter(new FloatNumberConverter()); putWriteConverter(new IntegerNumberConverter()); @@ -173,6 +180,7 @@ private static void initDefaultWriteConverter() { putWriteStringConverter(new DateStringConverter()); putWriteStringConverter(new LocalDateStringConverter()); putWriteStringConverter(new LocalDateTimeStringConverter()); + putWriteStringConverter(new ZonedDateTimeStringConverter()); putWriteStringConverter(new DoubleStringConverter()); putWriteStringConverter(new FloatStringConverter()); putWriteStringConverter(new IntegerStringConverter()); diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeDateConverter.java new file mode 100644 index 000000000..cc6d851c8 --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeDateConverter.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * This file is part of the Apache Fesod (Incubating) project, which was derived from Alibaba EasyExcel. + * + * Copyright (C) 2018-2024 Alibaba Group Holding Ltd. + */ + +package org.apache.fesod.sheet.converters.zoneddatetime; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import org.apache.fesod.sheet.converters.Converter; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; +import org.apache.fesod.sheet.util.DateUtils; +import org.apache.fesod.sheet.util.WorkBookUtil; + +/** ZonedDateTime and date converter. */ +public class ZonedDateTimeDateConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return ZonedDateTime.class; + } + + @Override + public WriteCellData convertToExcelData( + ZonedDateTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) + throws Exception { + LocalDateTime localDateTime = value.toLocalDateTime(); + WriteCellData cellData = new WriteCellData<>(localDateTime); + String format = null; + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + format = contentProperty.getDateTimeFormatProperty().getFormat(); + } + WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultDateFormat); + return cellData; + } +} diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeNumberConverter.java new file mode 100644 index 000000000..ed602ff4b --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeNumberConverter.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * This file is part of the Apache Fesod (Incubating) project, which was derived from Alibaba EasyExcel. + * + * Copyright (C) 2018-2024 Alibaba Group Holding Ltd. + */ + +package org.apache.fesod.sheet.converters.zoneddatetime; + +import java.math.BigDecimal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import org.apache.fesod.sheet.converters.Converter; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; +import org.apache.fesod.sheet.util.DateUtils; +import org.apache.poi.ss.usermodel.DateUtil; + +/** ZonedDateTime and number converter. */ +public class ZonedDateTimeNumberConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return ZonedDateTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public ZonedDateTime convertToJavaData( + ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + boolean use1904windowing = globalConfiguration.getUse1904windowing(); + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + use1904windowing = contentProperty.getDateTimeFormatProperty().getUse1904windowing(); + } + return DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), use1904windowing) + .atZone(ZoneId.systemDefault()); + } + + @Override + public WriteCellData convertToExcelData( + ZonedDateTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + boolean use1904windowing = globalConfiguration.getUse1904windowing(); + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + use1904windowing = contentProperty.getDateTimeFormatProperty().getUse1904windowing(); + } + return new WriteCellData<>( + BigDecimal.valueOf(DateUtil.getExcelDate(value.toLocalDateTime(), use1904windowing))); + } +} diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeStringConverter.java new file mode 100644 index 000000000..ea5e8344b --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/zoneddatetime/ZonedDateTimeStringConverter.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * This file is part of the Apache Fesod (Incubating) project, which was derived from Alibaba EasyExcel. + * + * Copyright (C) 2018-2024 Alibaba Group Holding Ltd. + */ + +package org.apache.fesod.sheet.converters.zoneddatetime; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Locale; +import org.apache.fesod.common.util.StringUtils; +import org.apache.fesod.sheet.converters.Converter; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; + +/** ZonedDateTime and string converter. */ +public class ZonedDateTimeStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return ZonedDateTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public ZonedDateTime convertToJavaData( + ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + DateTimeFormatter formatter = formatter(contentProperty, globalConfiguration.getLocale()); + try { + return ZonedDateTime.parse(cellData.getStringValue(), formatter); + } catch (DateTimeParseException e) { + return LocalDateTime.parse(cellData.getStringValue(), formatter).atZone(ZoneId.systemDefault()); + } + } + + @Override + public WriteCellData convertToExcelData( + ZonedDateTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value.format(formatter(contentProperty, globalConfiguration.getLocale()))); + } + + private DateTimeFormatter formatter(ExcelContentProperty contentProperty, Locale locale) { + if (contentProperty == null + || contentProperty.getDateTimeFormatProperty() == null + || StringUtils.isEmpty( + contentProperty.getDateTimeFormatProperty().getFormat())) { + return DateTimeFormatter.ISO_ZONED_DATE_TIME; + } + return DateTimeFormatter.ofPattern( + contentProperty.getDateTimeFormatProperty().getFormat(), locale); + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ZonedDateTimeConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ZonedDateTimeConverterTest.java new file mode 100644 index 000000000..0b7362698 --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ZonedDateTimeConverterTest.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * This file is part of the Apache Fesod (Incubating) project, which was derived from Alibaba EasyExcel. + * + * Copyright (C) 2018-2024 Alibaba Group Holding Ltd. + */ + +package org.apache.fesod.sheet.converter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import java.math.BigDecimal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import org.apache.fesod.sheet.converters.ConverterKeyBuild; +import org.apache.fesod.sheet.converters.DefaultConverterLoader; +import org.apache.fesod.sheet.converters.zoneddatetime.ZonedDateTimeDateConverter; +import org.apache.fesod.sheet.converters.zoneddatetime.ZonedDateTimeNumberConverter; +import org.apache.fesod.sheet.converters.zoneddatetime.ZonedDateTimeStringConverter; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.DateTimeFormatProperty; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; +import org.apache.fesod.sheet.testkit.Tags; +import org.apache.poi.ss.usermodel.DateUtil; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.UNIT) +class ZonedDateTimeConverterTest { + private static final ZonedDateTime VALUE = ZonedDateTime.of(2020, 1, 2, 3, 4, 5, 0, ZoneId.of("UTC")); + + @Test + void dateConverterDropsZoneWhilePreservingLocalDateTime() throws Exception { + WriteCellData result = + new ZonedDateTimeDateConverter().convertToExcelData(VALUE, null, new GlobalConfiguration()); + assertEquals(CellDataTypeEnum.DATE, result.getType()); + assertEquals(VALUE.toLocalDateTime(), result.getDateValue()); + } + + @Test + void numberConverterDropsZoneWhilePreservingLocalDateTime() { + ZonedDateTimeNumberConverter converter = new ZonedDateTimeNumberConverter(); + GlobalConfiguration globalConfiguration = new GlobalConfiguration(); + WriteCellData written = converter.convertToExcelData(VALUE, null, globalConfiguration); + ZonedDateTime read = + converter.convertToJavaData(new ReadCellData<>(written.getNumberValue()), null, globalConfiguration); + assertEquals(VALUE.toLocalDateTime(), read.toLocalDateTime()); + assertEquals(ZoneId.systemDefault(), read.getZone()); + } + + @Test + void numberConverterSupports1904Windowing() { + ZonedDateTimeNumberConverter converter = new ZonedDateTimeNumberConverter(); + + // 1. Configured via GlobalConfiguration + GlobalConfiguration global1900 = new GlobalConfiguration(); + GlobalConfiguration global1904 = new GlobalConfiguration(); + global1904.setUse1904windowing(Boolean.TRUE); + + WriteCellData written1900 = converter.convertToExcelData(VALUE, null, global1900); + WriteCellData written1904 = converter.convertToExcelData(VALUE, null, global1904); + + assertEquals( + BigDecimal.valueOf(DateUtil.getExcelDate(VALUE.toLocalDateTime(), false)), + written1900.getNumberValue()); + assertEquals( + BigDecimal.valueOf(DateUtil.getExcelDate(VALUE.toLocalDateTime(), true)), written1904.getNumberValue()); + assertNotEquals(written1900.getNumberValue(), written1904.getNumberValue()); + + ZonedDateTime read1904 = + converter.convertToJavaData(new ReadCellData<>(written1904.getNumberValue()), null, global1904); + assertEquals(VALUE.toLocalDateTime(), read1904.toLocalDateTime()); + assertEquals(ZoneId.systemDefault(), read1904.getZone()); + + ZonedDateTime read1904With1900 = + converter.convertToJavaData(new ReadCellData<>(written1904.getNumberValue()), null, global1900); + assertNotEquals(VALUE.toLocalDateTime(), read1904With1900.toLocalDateTime()); + + // 2. Configured via ExcelContentProperty + ExcelContentProperty property1904 = new ExcelContentProperty(); + property1904.setDateTimeFormatProperty(new DateTimeFormatProperty("yyyy-MM-dd HH:mm:ss", true)); + + WriteCellData writtenProperty1904 = converter.convertToExcelData(VALUE, property1904, global1900); + assertEquals( + BigDecimal.valueOf(DateUtil.getExcelDate(VALUE.toLocalDateTime(), true)), + writtenProperty1904.getNumberValue()); + + ZonedDateTime readProperty1904 = converter.convertToJavaData( + new ReadCellData<>(writtenProperty1904.getNumberValue()), property1904, global1900); + assertEquals(VALUE.toLocalDateTime(), readProperty1904.toLocalDateTime()); + assertEquals(ZoneId.systemDefault(), readProperty1904.getZone()); + } + + @Test + void stringConverterPreservesZoneInIsoText() throws Exception { + ZonedDateTimeStringConverter converter = new ZonedDateTimeStringConverter(); + GlobalConfiguration globalConfiguration = new GlobalConfiguration(); + WriteCellData written = converter.convertToExcelData(VALUE, null, globalConfiguration); + assertEquals( + VALUE, + converter.convertToJavaData(new ReadCellData<>(written.getStringValue()), null, globalConfiguration)); + } + + @Test + void stringConverterUsesConfiguredPattern() throws Exception { + ZonedDateTimeStringConverter converter = new ZonedDateTimeStringConverter(); + ExcelContentProperty property = new ExcelContentProperty(); + property.setDateTimeFormatProperty(new DateTimeFormatProperty("yyyy-MM-dd HH:mm:ss Z", false)); + GlobalConfiguration globalConfiguration = new GlobalConfiguration(); + assertEquals( + "2020-01-02 03:04:05 +0000", + converter + .convertToExcelData(VALUE, property, globalConfiguration) + .getStringValue()); + } + + @Test + void stringConverterWithEmptyOrNullPatternFallsBackToIso() throws Exception { + ZonedDateTimeStringConverter converter = new ZonedDateTimeStringConverter(); + GlobalConfiguration globalConfiguration = new GlobalConfiguration(); + + ExcelContentProperty emptyProperty = new ExcelContentProperty(); + emptyProperty.setDateTimeFormatProperty(new DateTimeFormatProperty("", false)); + WriteCellData writtenEmpty = converter.convertToExcelData(VALUE, emptyProperty, globalConfiguration); + assertEquals( + VALUE, + converter.convertToJavaData( + new ReadCellData<>(writtenEmpty.getStringValue()), emptyProperty, globalConfiguration)); + + ExcelContentProperty nullFormatProperty = new ExcelContentProperty(); + nullFormatProperty.setDateTimeFormatProperty(new DateTimeFormatProperty(null, false)); + WriteCellData writtenNullFormat = + converter.convertToExcelData(VALUE, nullFormatProperty, globalConfiguration); + assertEquals( + VALUE, + converter.convertToJavaData( + new ReadCellData<>(writtenNullFormat.getStringValue()), + nullFormatProperty, + globalConfiguration)); + } + + @Test + void convertersAreRegisteredForSupportedDirections() { + assertEquals( + ZonedDateTimeDateConverter.class, + DefaultConverterLoader.loadDefaultWriteConverter() + .get(ConverterKeyBuild.buildKey(ZonedDateTime.class)) + .getClass()); + assertEquals( + 2, + DefaultConverterLoader.loadAllConverter().entrySet().stream() + .filter(entry -> entry.getKey().getClazz() == ZonedDateTime.class) + .count()); + } +}