From 44ade170504394874035f6541b3c12d074ccb8c7 Mon Sep 17 00:00:00 2001 From: lance Date: Sun, 23 Aug 2026 11:57:34 +0800 Subject: [PATCH 1/5] feat: add java.time.LocalTime converters Signed-off-by: lance --- .../converters/DefaultConverterLoader.java | 8 + .../localtime/LocalTimeDateConverter.java | 59 ++++++ .../localtime/LocalTimeNumberConverter.java | 79 ++++++++ .../localtime/LocalTimeStringConverter.java | 74 +++++++ .../apache/fesod/sheet/util/DateUtils.java | 85 ++++++++ .../sheet/converter/ConverterDataTest.java | 1 + .../DefaultConverterLoaderTest.java | 23 +++ .../localtime/LocalTimeDateConverterTest.java | 115 +++++++++++ .../LocalTimeNumberConverterTest.java | 138 +++++++++++++ .../LocalTimeStringConverterTest.java | 170 ++++++++++++++++ .../testkit/builders/TestDataBuilder.java | 3 +- .../testkit/builders/TestDataBuilderTest.java | 1 + .../testkit/models/ConverterBaseData.java | 6 +- .../fesod/sheet/util/DateUtilsTest.java | 181 +++++++++++------- .../org/apache/fesod/sheet/util/TestUtil.java | 2 + 15 files changed, 878 insertions(+), 67 deletions(-) create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverterTest.java create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverterTest.java create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java 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..f17d9b62c 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 @@ -64,6 +64,9 @@ import org.apache.fesod.sheet.converters.localdatetime.LocalDateTimeDateConverter; import org.apache.fesod.sheet.converters.localdatetime.LocalDateTimeNumberConverter; import org.apache.fesod.sheet.converters.localdatetime.LocalDateTimeStringConverter; +import org.apache.fesod.sheet.converters.localtime.LocalTimeDateConverter; +import org.apache.fesod.sheet.converters.localtime.LocalTimeNumberConverter; +import org.apache.fesod.sheet.converters.localtime.LocalTimeStringConverter; import org.apache.fesod.sheet.converters.longconverter.LongBooleanConverter; import org.apache.fesod.sheet.converters.longconverter.LongNumberConverter; import org.apache.fesod.sheet.converters.longconverter.LongStringConverter; @@ -117,6 +120,9 @@ private static void initAllConverter() { putAllConverter(new LocalDateTimeNumberConverter()); putAllConverter(new LocalDateTimeStringConverter()); + putAllConverter(new LocalTimeNumberConverter()); + putAllConverter(new LocalTimeStringConverter()); + 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 LocalTimeDateConverter()); 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 LocalTimeStringConverter()); putWriteStringConverter(new DoubleStringConverter()); putWriteStringConverter(new FloatStringConverter()); putWriteStringConverter(new IntegerStringConverter()); diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java new file mode 100644 index 000000000..9658e1582 --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java @@ -0,0 +1,59 @@ +/* + * 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.localtime; + +import java.time.LocalDateTime; +import java.time.LocalTime; +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; + +/** + * LocalTime and date converter + */ +public class LocalTimeDateConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return LocalTime.class; + } + + @Override + public WriteCellData convertToExcelData( + LocalTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) + throws Exception { + LocalDateTime localDateTime = value == null ? null : value.atDate(DateUtils.EPOCH); + WriteCellData cellData = new WriteCellData<>(localDateTime); + String format = null; + if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { + format = contentProperty.getDateTimeFormatProperty().getFormat(); + } + WorkBookUtil.fillDataFormat(cellData, format, DateUtils.DEFAULT_LOCAL_TIME_FORMAT); + return cellData; + } +} diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java new file mode 100644 index 000000000..4c9c42996 --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java @@ -0,0 +1,79 @@ +/* + * 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.localtime; + +import java.math.BigDecimal; +import java.time.LocalTime; +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; + +/** + * LocalTime and number converter + */ +public class LocalTimeNumberConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return LocalTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.NUMBER; + } + + @Override + public LocalTime convertToJavaData( + ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.getLocalTime( + cellData.getNumberValue().doubleValue(), globalConfiguration.getUse1904windowing()); + } else { + return DateUtils.getLocalTime( + cellData.getNumberValue().doubleValue(), + contentProperty.getDateTimeFormatProperty().getUse1904windowing()); + } + } + + @Override + public WriteCellData convertToExcelData( + LocalTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>(BigDecimal.valueOf( + DateUtil.getExcelDate(value.atDate(DateUtils.EPOCH), globalConfiguration.getUse1904windowing()))); + } else { + return new WriteCellData<>(BigDecimal.valueOf(DateUtil.getExcelDate( + value.atDate(DateUtils.EPOCH), + contentProperty.getDateTimeFormatProperty().getUse1904windowing()))); + } + } +} diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java new file mode 100644 index 000000000..cdc7ba93d --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java @@ -0,0 +1,74 @@ +/* + * 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.localtime; + +import java.time.LocalTime; +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; + +/** + * LocalTime and string converter + */ +public class LocalTimeStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return LocalTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public LocalTime convertToJavaData( + ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return DateUtils.parseLocalTime(cellData.getStringValue(), null, globalConfiguration.getLocale()); + } else { + return DateUtils.parseLocalTime( + cellData.getStringValue(), + contentProperty.getDateTimeFormatProperty().getFormat(), + globalConfiguration.getLocale()); + } + } + + @Override + public WriteCellData convertToExcelData( + LocalTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return new WriteCellData<>(DateUtils.format(value, null, globalConfiguration.getLocale())); + } else { + return new WriteCellData<>(DateUtils.format( + value, contentProperty.getDateTimeFormatProperty().getFormat(), globalConfiguration.getLocale())); + } + } +} diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java index 895f6dcec..58425caab 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java @@ -31,6 +31,7 @@ import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; @@ -100,12 +101,16 @@ public class DateUtils { public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss"; public static final String DATE_FORMAT_19_FORWARD_SLASH = "yyyy/MM/dd HH:mm:ss"; + public static final String TIME_FORMAT_5 = "HH:mm"; + public static final String TIME_FORMAT_8 = "HH:mm:ss"; private static final String MINUS = "-"; public static String defaultDateFormat = DATE_FORMAT_19; public static String defaultLocalDateFormat = DATE_FORMAT_10; + public static final String DEFAULT_LOCAL_TIME_FORMAT = TIME_FORMAT_8; + public static final int SECONDS_PER_MINUTE = 60; public static final int MINUTES_PER_HOUR = 60; public static final int HOURS_PER_DAY = 24; @@ -162,6 +167,21 @@ public static LocalDate parseLocalDate(String dateString, String dateFormat, Loc return LocalDate.parse(dateString, getCacheDateTimeFormat(dateFormat, local)); } + /** + * convert string to time + * + * @param timeString + * @param timeFormat + * @param local + * @return + */ + public static LocalTime parseLocalTime(String timeString, String timeFormat, Locale local) { + if (StringUtils.isEmpty(timeFormat)) { + timeFormat = switchTimeFormat(timeString); + } + return LocalTime.parse(timeString, getCacheDateTimeFormat(timeFormat, local)); + } + /** * convert string to date * @@ -205,6 +225,24 @@ public static String switchDateFormat(String dateString) { } } + /** + * switch time-only format + * + * @param timeString + * @return + */ + public static String switchTimeFormat(String timeString) { + int length = timeString.length(); + switch (length) { + case 8: + return TIME_FORMAT_8; + case 5: + return TIME_FORMAT_5; + default: + throw new IllegalArgumentException("can not find time format for:" + timeString); + } + } + /** * Format date *

@@ -279,6 +317,35 @@ public static String format(LocalDate date, String dateFormat, Locale local) { return date.format(getCacheDateTimeFormat(dateFormat, local)); } + /** + * Format time + * + * @param time LocalTime + * @param timeFormat time format + * @return format string + */ + public static String format(LocalTime time, String timeFormat) { + return format(time, timeFormat, null); + } + + /** + * Format time + * + * @param time LocalTime + * @param timeFormat time format + * @param local local + * @return format string + */ + public static String format(LocalTime time, String timeFormat, Locale local) { + if (time == null) { + return null; + } + if (StringUtils.isEmpty(timeFormat)) { + timeFormat = DEFAULT_LOCAL_TIME_FORMAT; + } + return time.format(getCacheDateTimeFormat(timeFormat, local)); + } + /** * Format date * @@ -458,6 +525,24 @@ public static LocalDate getLocalDate(double date, boolean use1904windowing) { return localDateTime == null ? null : localDateTime.toLocalDate(); } + /** + * Given an Excel date with either 1900 or 1904 date windowing, + * converts it to a java.time.LocalTime. + * + * Excel Dates and Times are stored without any timezone + * information. The date component is discarded; only the + * wall-clock time of day is returned. + * + * @param date The Excel date. + * @param use1904windowing true if date uses 1904 windowing, + * or false if using 1900 date windowing. + * @return Java representation of the time, or null if date is not a valid Excel date + */ + public static LocalTime getLocalTime(double date, boolean use1904windowing) { + LocalDateTime localDateTime = getLocalDateTime(date, use1904windowing); + return localDateTime == null ? null : localDateTime.toLocalTime(); + } + /** * Determine if it is a date format. * diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterDataTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterDataTest.java index 1222641a1..77eda9139 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterDataTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterDataTest.java @@ -66,6 +66,7 @@ void readAndWrite(ExcelFormat format) throws Exception { Assertions.assertEquals(TestUtil.TEST_DATE, row.getDate()); Assertions.assertEquals(TestUtil.TEST_LOCAL_DATE, row.getLocalDate()); Assertions.assertEquals(TestUtil.TEST_LOCAL_DATE_TIME, row.getLocalDateTime()); + Assertions.assertEquals(TestUtil.TEST_LOCAL_TIME, row.getLocalTime()); Assertions.assertEquals(Boolean.TRUE, row.getBooleanData()); Assertions.assertEquals(row.getBigDecimal().doubleValue(), BigDecimal.ONE.doubleValue(), 0.0); Assertions.assertEquals(row.getBigInteger().intValue(), BigInteger.ONE.intValue(), 0.0); diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/DefaultConverterLoaderTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/DefaultConverterLoaderTest.java index e05837b86..a30708a52 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/DefaultConverterLoaderTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/DefaultConverterLoaderTest.java @@ -19,8 +19,13 @@ package org.apache.fesod.sheet.converters; +import java.time.LocalTime; import java.util.Map; import org.apache.fesod.sheet.converters.ConverterKeyBuild.ConverterKey; +import org.apache.fesod.sheet.converters.localtime.LocalTimeDateConverter; +import org.apache.fesod.sheet.converters.localtime.LocalTimeNumberConverter; +import org.apache.fesod.sheet.converters.localtime.LocalTimeStringConverter; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -44,6 +49,24 @@ void loadAllConverterIsImmutableAndCopyIsMutable() { DefaultConverterLoader.loadAllConverter(), DefaultConverterLoader.copyAllConverter()); } + @Test + void loadConvertersRegistersLocalTimeFamily() { + Map> allConverter = DefaultConverterLoader.loadAllConverter(); + Assertions.assertInstanceOf( + LocalTimeNumberConverter.class, + allConverter.get(ConverterKeyBuild.buildKey(LocalTime.class, CellDataTypeEnum.NUMBER))); + Assertions.assertInstanceOf( + LocalTimeStringConverter.class, + allConverter.get(ConverterKeyBuild.buildKey(LocalTime.class, CellDataTypeEnum.STRING))); + + Map> writeConverter = DefaultConverterLoader.loadDefaultWriteConverter(); + Assertions.assertInstanceOf( + LocalTimeDateConverter.class, writeConverter.get(ConverterKeyBuild.buildKey(LocalTime.class))); + Assertions.assertInstanceOf( + LocalTimeStringConverter.class, + writeConverter.get(ConverterKeyBuild.buildKey(LocalTime.class, CellDataTypeEnum.STRING))); + } + private static void assertLoadIsImmutableAndCopyIsMutable( Map> loaded, Map> copy) { Map.Entry> entry = diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverterTest.java new file mode 100644 index 000000000..436a22e3b --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverterTest.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +package org.apache.fesod.sheet.converters.localtime; + +import java.time.LocalTime; +import java.util.stream.Stream; +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.fesod.sheet.util.DateUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link LocalTimeDateConverter}. + */ +@Tag(Tags.UNIT) +class LocalTimeDateConverterTest { + + private static final GlobalConfiguration GLOBAL_CONFIGURATION = new GlobalConfiguration(); + private final LocalTimeDateConverter converter = new LocalTimeDateConverter(); + + @AfterEach + void tearDown() { + DateUtils.removeThreadLocalCache(); + } + + @Test + void supportJavaTypeKey() { + Assertions.assertEquals(LocalTime.class, converter.supportJavaTypeKey()); + } + + @Test + void convertToJavaDataIsUnsupported() { + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> converter.convertToJavaData(new ReadCellData<>("01:01:01"), null, GLOBAL_CONFIGURATION)); + } + + @ParameterizedTest + @MethodSource("sampleTimes") + void convertToExcelDataUsesEpochDateAndDefaultFormat(LocalTime time) throws Exception { + WriteCellData cellData = converter.convertToExcelData(time, null, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(CellDataTypeEnum.DATE, cellData.getType()); + Assertions.assertEquals(time.atDate(DateUtils.EPOCH), cellData.getDateValue()); + Assertions.assertEquals( + DateUtils.DEFAULT_LOCAL_TIME_FORMAT, + cellData.getWriteCellStyle().getDataFormatData().getFormat()); + } + + @Test + void convertToExcelDataFallsBackToDefaultFormatWhenContentPropertyHasNoDateTimeFormat() throws Exception { + WriteCellData cellData = + converter.convertToExcelData(LocalTime.NOON, new ExcelContentProperty(), GLOBAL_CONFIGURATION); + + Assertions.assertEquals( + DateUtils.DEFAULT_LOCAL_TIME_FORMAT, + cellData.getWriteCellStyle().getDataFormatData().getFormat()); + } + + @Test + void convertToExcelDataUsesCustomFormat() throws Exception { + ExcelContentProperty contentProperty = contentProperty(DateUtils.TIME_FORMAT_5, Boolean.FALSE); + + WriteCellData cellData = + converter.convertToExcelData(LocalTime.of(1, 1, 1), contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.of(1, 1, 1).atDate(DateUtils.EPOCH), cellData.getDateValue()); + Assertions.assertEquals( + DateUtils.TIME_FORMAT_5, + cellData.getWriteCellStyle().getDataFormatData().getFormat()); + } + + @Test + void convertToExcelDataRejectsNullValue() { + Assertions.assertThrows( + IllegalArgumentException.class, () -> converter.convertToExcelData(null, null, GLOBAL_CONFIGURATION)); + } + + static Stream sampleTimes() { + return Stream.of(LocalTime.MIDNIGHT, LocalTime.NOON, LocalTime.of(1, 1, 1), LocalTime.of(23, 59, 59)); + } + + private static ExcelContentProperty contentProperty(String format, Boolean use1904windowing) { + ExcelContentProperty contentProperty = new ExcelContentProperty(); + contentProperty.setDateTimeFormatProperty(new DateTimeFormatProperty(format, use1904windowing)); + return contentProperty; + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverterTest.java new file mode 100644 index 000000000..13628db03 --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverterTest.java @@ -0,0 +1,138 @@ +/* + * 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. + */ + +package org.apache.fesod.sheet.converters.localtime; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.stream.Stream; +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.fesod.sheet.util.DateUtils; +import org.apache.poi.ss.usermodel.DateUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link LocalTimeNumberConverter}. + */ +@Tag(Tags.UNIT) +class LocalTimeNumberConverterTest { + + private static final GlobalConfiguration GLOBAL_CONFIGURATION = new GlobalConfiguration(); + private final LocalTimeNumberConverter converter = new LocalTimeNumberConverter(); + + @AfterEach + void tearDown() { + DateUtils.removeThreadLocalCache(); + } + + @Test + void supportKeys() { + Assertions.assertEquals(LocalTime.class, converter.supportJavaTypeKey()); + Assertions.assertEquals(CellDataTypeEnum.NUMBER, converter.supportExcelTypeKey()); + } + + @ParameterizedTest + @CsvSource({"0.5, 12:00:00", "1.0, 00:00:00", "43831.5, 12:00:00"}) + void convertToJavaDataReadsSerialAndDropsDate(double serial, String expected) { + LocalTime actual = + converter.convertToJavaData(new ReadCellData<>(BigDecimal.valueOf(serial)), null, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.parse(expected), actual); + } + + @Test + void convertToJavaDataDropsDateFromFullDatetimeSerial() { + double serial = DateUtil.getExcelDate(LocalTime.of(1, 1, 1).atDate(LocalDate.of(2020, 1, 1)), false); + + LocalTime actual = + converter.convertToJavaData(new ReadCellData<>(BigDecimal.valueOf(serial)), null, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.of(1, 1, 1), actual); + } + + @ParameterizedTest + @MethodSource("sampleTimes") + void convertToExcelDataRoundTrip(LocalTime time) { + WriteCellData written = converter.convertToExcelData(time, null, GLOBAL_CONFIGURATION); + LocalTime actual = + converter.convertToJavaData(new ReadCellData<>(written.getNumberValue()), null, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(CellDataTypeEnum.NUMBER, written.getType()); + Assertions.assertEquals(time, actual); + Assertions.assertEquals( + DateUtil.getExcelDate(time.atDate(DateUtils.EPOCH), false), + written.getNumberValue().doubleValue(), + 1e-8); + } + + @Test + void convertUsesGlobal1904WindowingWhenContentPropertyHasNoDateTimeFormat() { + GlobalConfiguration configuration = new GlobalConfiguration(); + configuration.setUse1904windowing(Boolean.TRUE); + + WriteCellData written = + converter.convertToExcelData(LocalTime.NOON, new ExcelContentProperty(), configuration); + LocalTime actual = + converter.convertToJavaData(new ReadCellData<>(written.getNumberValue()), null, configuration); + + Assertions.assertEquals(LocalTime.NOON, actual); + Assertions.assertEquals( + DateUtil.getExcelDate(LocalTime.NOON.atDate(DateUtils.EPOCH), true), + written.getNumberValue().doubleValue(), + 1e-8); + } + + @Test + void convertPrefersContentPropertyWindowingOverGlobal() { + ExcelContentProperty contentProperty = contentProperty(null, Boolean.TRUE); + + WriteCellData written = converter.convertToExcelData(LocalTime.NOON, contentProperty, GLOBAL_CONFIGURATION); + LocalTime actual = converter.convertToJavaData( + new ReadCellData<>(written.getNumberValue()), contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.NOON, actual); + Assertions.assertEquals( + DateUtil.getExcelDate(LocalTime.NOON.atDate(DateUtils.EPOCH), true), + written.getNumberValue().doubleValue(), + 1e-8); + } + + static Stream sampleTimes() { + return Stream.of(LocalTime.MIDNIGHT, LocalTime.NOON, LocalTime.of(1, 1, 1), LocalTime.of(23, 59, 59)); + } + + private static ExcelContentProperty contentProperty(String format, Boolean use1904windowing) { + ExcelContentProperty contentProperty = new ExcelContentProperty(); + contentProperty.setDateTimeFormatProperty(new DateTimeFormatProperty(format, use1904windowing)); + return contentProperty; + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java new file mode 100644 index 000000000..c15424461 --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java @@ -0,0 +1,170 @@ +/* + * 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. + */ + +package org.apache.fesod.sheet.converters.localtime; + +import java.time.LocalTime; +import java.time.format.DateTimeParseException; +import java.util.Locale; +import java.util.stream.Stream; +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.fesod.sheet.util.DateUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link LocalTimeStringConverter}. + */ +@Tag(Tags.UNIT) +class LocalTimeStringConverterTest { + + private static final GlobalConfiguration GLOBAL_CONFIGURATION = new GlobalConfiguration(); + private final LocalTimeStringConverter converter = new LocalTimeStringConverter(); + + @AfterEach + void tearDown() { + DateUtils.removeThreadLocalCache(); + } + + @Test + void supportKeys() { + Assertions.assertEquals(LocalTime.class, converter.supportJavaTypeKey()); + Assertions.assertEquals(CellDataTypeEnum.STRING, converter.supportExcelTypeKey()); + } + + @ParameterizedTest + @CsvSource({"00:00:00", "01:01:01", "12:00:00", "23:59:59"}) + void convertRoundTripWithDefaultFormat(String value) { + LocalTime time = LocalTime.parse(value); + + LocalTime actual = converter.convertToJavaData(new ReadCellData<>(value), null, GLOBAL_CONFIGURATION); + WriteCellData written = converter.convertToExcelData(time, null, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(time, actual); + Assertions.assertEquals(CellDataTypeEnum.STRING, written.getType()); + Assertions.assertEquals(value, written.getStringValue()); + } + + @Test + void convertToJavaDataAutoDetectsHourMinute() { + LocalTime actual = converter.convertToJavaData(new ReadCellData<>("12:30"), null, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.of(12, 30), actual); + } + + @Test + void convertUsesCustomFormatFromContentProperty() { + ExcelContentProperty contentProperty = contentProperty(DateUtils.TIME_FORMAT_5, Boolean.FALSE); + + LocalTime actual = + converter.convertToJavaData(new ReadCellData<>("12:30"), contentProperty, GLOBAL_CONFIGURATION); + WriteCellData written = converter.convertToExcelData(LocalTime.NOON, contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.of(12, 30), actual); + Assertions.assertEquals("12:00", written.getStringValue()); + } + + @Test + void convertFallsBackToDefaultWhenContentPropertyHasNoDateTimeFormat() { + ExcelContentProperty contentProperty = new ExcelContentProperty(); + + LocalTime actual = + converter.convertToJavaData(new ReadCellData<>("01:01:01"), contentProperty, GLOBAL_CONFIGURATION); + WriteCellData written = + converter.convertToExcelData(LocalTime.of(1, 1, 1), contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.of(1, 1, 1), actual); + Assertions.assertEquals("01:01:01", written.getStringValue()); + } + + @Test + void convertToJavaDataUsesSwitchTimeFormatWhenConfiguredFormatIsEmpty() { + ExcelContentProperty contentProperty = contentProperty("", Boolean.FALSE); + + LocalTime withSeconds = + converter.convertToJavaData(new ReadCellData<>("12:30:45"), contentProperty, GLOBAL_CONFIGURATION); + LocalTime withoutSeconds = + converter.convertToJavaData(new ReadCellData<>("12:30"), contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(LocalTime.of(12, 30, 45), withSeconds); + Assertions.assertEquals(LocalTime.of(12, 30), withoutSeconds); + } + + @Test + void convertToExcelDataUsesDefaultFormatWhenConfiguredFormatIsEmpty() { + ExcelContentProperty contentProperty = contentProperty("", Boolean.FALSE); + + WriteCellData written = + converter.convertToExcelData(LocalTime.of(12, 30, 45), contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals("12:30:45", written.getStringValue()); + } + + @ParameterizedTest + @MethodSource("locales") + void convertToExcelDataKeepsNumericTimeAcrossLocales(Locale locale) { + GlobalConfiguration configuration = new GlobalConfiguration(); + configuration.setLocale(locale); + + WriteCellData written = converter.convertToExcelData(LocalTime.of(12, 30, 45), null, configuration); + + Assertions.assertEquals("12:30:45", written.getStringValue()); + } + + @Test + void convertToJavaDataRejectsUnknownPattern() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> converter.convertToJavaData(new ReadCellData<>("not-a-time"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToJavaDataRejectsInvalidClockTime() { + Assertions.assertThrows( + DateTimeParseException.class, + () -> converter.convertToJavaData(new ReadCellData<>("99:99:99"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToExcelDataRejectsNullValue() { + Assertions.assertThrows( + IllegalArgumentException.class, () -> converter.convertToExcelData(null, null, GLOBAL_CONFIGURATION)); + } + + static Stream locales() { + return Stream.of(Locale.US, Locale.FRANCE, Locale.SIMPLIFIED_CHINESE); + } + + private static ExcelContentProperty contentProperty(String format, Boolean use1904windowing) { + ExcelContentProperty contentProperty = new ExcelContentProperty(); + contentProperty.setDateTimeFormatProperty(new DateTimeFormatProperty(format, use1904windowing)); + return contentProperty; + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilder.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilder.java index 24fff7ffc..0b6897c86 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilder.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilder.java @@ -73,7 +73,7 @@ public static List simpleData(int count) { } /** - * Creates a single-element list of {@link ConverterWriteData} with all 14 fields populated. + * Creates a single-element list of {@link ConverterWriteData} with all common fields populated. */ public static List converterWriteData() { List list = new ArrayList<>(); @@ -81,6 +81,7 @@ public static List converterWriteData() { data.setDate(TestUtil.TEST_DATE); data.setLocalDate(TestUtil.TEST_LOCAL_DATE); data.setLocalDateTime(TestUtil.TEST_LOCAL_DATE_TIME); + data.setLocalTime(TestUtil.TEST_LOCAL_TIME); data.setBooleanData(Boolean.TRUE); data.setBigDecimal(BigDecimal.ONE); data.setBigInteger(BigInteger.ONE); diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilderTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilderTest.java index 70ecac50c..7b21edff8 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilderTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/builders/TestDataBuilderTest.java @@ -73,6 +73,7 @@ void converterWriteDataFieldValues() { Assertions.assertEquals(TestUtil.TEST_DATE, data.getDate()); Assertions.assertEquals(TestUtil.TEST_LOCAL_DATE, data.getLocalDate()); Assertions.assertEquals(TestUtil.TEST_LOCAL_DATE_TIME, data.getLocalDateTime()); + Assertions.assertEquals(TestUtil.TEST_LOCAL_TIME, data.getLocalTime()); Assertions.assertEquals(Boolean.TRUE, data.getBooleanData()); Assertions.assertEquals(1, data.getBigDecimal().intValue()); Assertions.assertEquals(1, data.getBigInteger().intValue()); diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/models/ConverterBaseData.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/models/ConverterBaseData.java index 853d8097b..c47dcbed5 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/models/ConverterBaseData.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/testkit/models/ConverterBaseData.java @@ -23,6 +23,7 @@ import java.math.BigInteger; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Date; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -30,7 +31,7 @@ import org.apache.fesod.sheet.annotation.ExcelProperty; /** - * Abstract base class extracting the 13 common fields shared by + * Abstract base class extracting the 14 common fields shared by * {@code ConverterReadData} and {@code ConverterWriteData}. */ @Getter @@ -46,6 +47,9 @@ public abstract class ConverterBaseData { @ExcelProperty("Local Date Time") private LocalDateTime localDateTime; + @ExcelProperty("Local Time") + private LocalTime localTime; + @ExcelProperty("Boolean") private Boolean booleanData; diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java index 33494369c..ce2a5dfe3 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java @@ -19,12 +19,15 @@ package org.apache.fesod.sheet.util; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.lang.reflect.Field; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; @@ -55,19 +58,26 @@ void tearDown() { @Test void test_switchDateFormat() { - Assertions.assertEquals(DateUtils.DATE_FORMAT_19, DateUtils.switchDateFormat("2026-01-01 12:00:00")); - Assertions.assertEquals( - DateUtils.DATE_FORMAT_19_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00:00")); + assertEquals(DateUtils.DATE_FORMAT_19, DateUtils.switchDateFormat("2026-01-01 12:00:00")); + assertEquals(DateUtils.DATE_FORMAT_19_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00:00")); - Assertions.assertEquals(DateUtils.DATE_FORMAT_16, DateUtils.switchDateFormat("2026-01-01 12:00")); - Assertions.assertEquals(DateUtils.DATE_FORMAT_16_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00")); + assertEquals(DateUtils.DATE_FORMAT_16, DateUtils.switchDateFormat("2026-01-01 12:00")); + assertEquals(DateUtils.DATE_FORMAT_16_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00")); - Assertions.assertEquals(DateUtils.DATE_FORMAT_17, DateUtils.switchDateFormat("20260101 12:00:00")); - Assertions.assertEquals(DateUtils.DATE_FORMAT_14, DateUtils.switchDateFormat("20260101120000")); - Assertions.assertEquals(DateUtils.DATE_FORMAT_10, DateUtils.switchDateFormat("2026-01-01")); + assertEquals(DateUtils.DATE_FORMAT_17, DateUtils.switchDateFormat("20260101 12:00:00")); + assertEquals(DateUtils.DATE_FORMAT_14, DateUtils.switchDateFormat("20260101120000")); + assertEquals(DateUtils.DATE_FORMAT_10, DateUtils.switchDateFormat("2026-01-01")); - Assertions.assertThrows( - IllegalArgumentException.class, () -> DateUtils.switchDateFormat("invalid_datestring_length")); + assertThrows(IllegalArgumentException.class, () -> DateUtils.switchDateFormat("invalid_datestring_length")); + } + + @Test + void test_switchTimeFormat() { + assertEquals(DateUtils.TIME_FORMAT_8, DateUtils.switchTimeFormat("12:30:45")); + assertEquals(DateUtils.TIME_FORMAT_5, DateUtils.switchTimeFormat("12:30")); + + assertThrows(IllegalArgumentException.class, () -> DateUtils.switchTimeFormat("12:30:45.123")); + assertThrows(IllegalArgumentException.class, () -> DateUtils.switchTimeFormat("invalid")); } @Test @@ -77,17 +87,17 @@ void test_parseDate() throws ParseException { Calendar cal1 = Calendar.getInstance(); cal1.setTime(date1); - Assertions.assertEquals(2026, cal1.get(Calendar.YEAR)); - Assertions.assertEquals(Calendar.OCTOBER, cal1.get(Calendar.MONTH)); - Assertions.assertEquals(30, cal1.get(Calendar.MINUTE)); + assertEquals(2026, cal1.get(Calendar.YEAR)); + assertEquals(Calendar.OCTOBER, cal1.get(Calendar.MONTH)); + assertEquals(30, cal1.get(Calendar.MINUTE)); Date date2 = DateUtils.parseDate(dateStr, ""); Calendar cal2 = Calendar.getInstance(); cal2.setTime(date2); - Assertions.assertEquals(2026, cal2.get(Calendar.YEAR)); - Assertions.assertEquals(Calendar.OCTOBER, cal2.get(Calendar.MONTH)); - Assertions.assertEquals(30, cal2.get(Calendar.MINUTE)); + assertEquals(2026, cal2.get(Calendar.YEAR)); + assertEquals(Calendar.OCTOBER, cal2.get(Calendar.MONTH)); + assertEquals(30, cal2.get(Calendar.MINUTE)); } @Test @@ -96,30 +106,30 @@ void test_parseLocalDateTime() { String format = "yyyy-MM-dd HH:mm:ss"; LocalDateTime usResult = DateUtils.parseLocalDateTime(dateStr, format, Locale.US); - Assertions.assertEquals(2026, usResult.getYear()); - Assertions.assertEquals(10, usResult.getMonthValue()); - Assertions.assertEquals(1, usResult.getDayOfMonth()); - Assertions.assertEquals(12, usResult.getHour()); - Assertions.assertEquals(30, usResult.getMinute()); - Assertions.assertEquals(45, usResult.getSecond()); + assertEquals(2026, usResult.getYear()); + assertEquals(10, usResult.getMonthValue()); + assertEquals(1, usResult.getDayOfMonth()); + assertEquals(12, usResult.getHour()); + assertEquals(30, usResult.getMinute()); + assertEquals(45, usResult.getSecond()); LocalDateTime result = DateUtils.parseLocalDateTime(dateStr, format, null); - Assertions.assertEquals(2026, result.getYear()); - Assertions.assertEquals(10, result.getMonthValue()); - Assertions.assertEquals(1, result.getDayOfMonth()); - Assertions.assertEquals(12, result.getHour()); - Assertions.assertEquals(30, result.getMinute()); - Assertions.assertEquals(45, result.getSecond()); + assertEquals(2026, result.getYear()); + assertEquals(10, result.getMonthValue()); + assertEquals(1, result.getDayOfMonth()); + assertEquals(12, result.getHour()); + assertEquals(30, result.getMinute()); + assertEquals(45, result.getSecond()); LocalDateTime autoDetectFormatResult = DateUtils.parseLocalDateTime(dateStr, "", null); - Assertions.assertEquals(2026, autoDetectFormatResult.getYear()); - Assertions.assertEquals(10, autoDetectFormatResult.getMonthValue()); - Assertions.assertEquals(1, autoDetectFormatResult.getDayOfMonth()); - Assertions.assertEquals(12, autoDetectFormatResult.getHour()); - Assertions.assertEquals(30, autoDetectFormatResult.getMinute()); - Assertions.assertEquals(45, autoDetectFormatResult.getSecond()); + assertEquals(2026, autoDetectFormatResult.getYear()); + assertEquals(10, autoDetectFormatResult.getMonthValue()); + assertEquals(1, autoDetectFormatResult.getDayOfMonth()); + assertEquals(12, autoDetectFormatResult.getHour()); + assertEquals(30, autoDetectFormatResult.getMinute()); + assertEquals(45, autoDetectFormatResult.getSecond()); } @Test @@ -128,21 +138,36 @@ void test_parseLocalDate() { String format = "yyyy-MM-dd"; LocalDate usResult = DateUtils.parseLocalDate(dateStr, format, Locale.US); - Assertions.assertEquals(2026, usResult.getYear()); - Assertions.assertEquals(10, usResult.getMonthValue()); - Assertions.assertEquals(1, usResult.getDayOfMonth()); + assertEquals(2026, usResult.getYear()); + assertEquals(10, usResult.getMonthValue()); + assertEquals(1, usResult.getDayOfMonth()); LocalDate result = DateUtils.parseLocalDate(dateStr, format, null); - Assertions.assertEquals(2026, result.getYear()); - Assertions.assertEquals(10, result.getMonthValue()); - Assertions.assertEquals(1, result.getDayOfMonth()); + assertEquals(2026, result.getYear()); + assertEquals(10, result.getMonthValue()); + assertEquals(1, result.getDayOfMonth()); LocalDate autoDetectFormatResult = DateUtils.parseLocalDate(dateStr, "", null); - Assertions.assertEquals(2026, autoDetectFormatResult.getYear()); - Assertions.assertEquals(10, autoDetectFormatResult.getMonthValue()); - Assertions.assertEquals(1, autoDetectFormatResult.getDayOfMonth()); + assertEquals(2026, autoDetectFormatResult.getYear()); + assertEquals(10, autoDetectFormatResult.getMonthValue()); + assertEquals(1, autoDetectFormatResult.getDayOfMonth()); + } + + @Test + void test_parseLocalTime() { + LocalTime usResult = DateUtils.parseLocalTime("12:30:45", DateUtils.TIME_FORMAT_8, Locale.US); + assertEquals(LocalTime.of(12, 30, 45), usResult); + + LocalTime result = DateUtils.parseLocalTime("12:30:45", DateUtils.TIME_FORMAT_8, null); + assertEquals(LocalTime.of(12, 30, 45), result); + + LocalTime autoDetectSeconds = DateUtils.parseLocalTime("12:30:45", "", null); + assertEquals(LocalTime.of(12, 30, 45), autoDetectSeconds); + + LocalTime autoDetectMinutes = DateUtils.parseLocalTime("12:30", "", null); + assertEquals(LocalTime.of(12, 30), autoDetectMinutes); } @Test @@ -151,7 +176,7 @@ void test_format_default() { String result = DateUtils.format(now); Assertions.assertNotNull(result); // yyyy-MM-dd HH:mm:ss - Assertions.assertEquals(19, result.length()); + assertEquals(19, result.length()); Assertions.assertNull(DateUtils.format(null)); } @@ -162,7 +187,7 @@ void test_format_LocalDateTime() { String format = "dd-MMM-yyyy"; String usResult = DateUtils.format(ldt, format, Locale.US); - Assertions.assertEquals("01-Oct-2026", usResult); + assertEquals("01-Oct-2026", usResult); String cnResult = DateUtils.format(ldt, format, Locale.SIMPLIFIED_CHINESE); Assertions.assertNotNull(cnResult); @@ -170,7 +195,7 @@ void test_format_LocalDateTime() { Assertions.assertNull(DateUtils.format((LocalDateTime) null, format, Locale.US)); String defaultUSResult = DateUtils.format(ldt, "", Locale.US); - Assertions.assertEquals("2026-10-01 12:00:00", defaultUSResult); + assertEquals("2026-10-01 12:00:00", defaultUSResult); } @Test @@ -179,7 +204,7 @@ void test_format_LocalDate() { String format = "dd-MMM-yyyy"; String usResult = DateUtils.format(ld, format, Locale.US); - Assertions.assertEquals("01-Oct-2026", usResult); + assertEquals("01-Oct-2026", usResult); String cnResult = DateUtils.format(ld, format, Locale.SIMPLIFIED_CHINESE); Assertions.assertNotNull(cnResult); @@ -187,13 +212,23 @@ void test_format_LocalDate() { Assertions.assertNull(DateUtils.format((LocalDate) null, format, Locale.US)); String defaultUSResult = DateUtils.format(ld, "", Locale.US); - Assertions.assertEquals("2026-10-01", defaultUSResult); + assertEquals("2026-10-01", defaultUSResult); String defaultFormatResult = DateUtils.format(ld, "", null); - Assertions.assertEquals("2026-10-01", defaultFormatResult); + assertEquals("2026-10-01", defaultFormatResult); String defaultFormatResult2 = DateUtils.format(ld, ""); - Assertions.assertEquals("2026-10-01", defaultFormatResult2); + assertEquals("2026-10-01", defaultFormatResult2); + } + + @Test + void test_format_LocalTime() { + LocalTime time = LocalTime.of(12, 30, 45); + + assertEquals("12:30:45", DateUtils.format(time, null, Locale.US)); + assertEquals("12:30", DateUtils.format(time, DateUtils.TIME_FORMAT_5, Locale.US)); + assertEquals("12:30:45", DateUtils.format(time, "")); + Assertions.assertNull(DateUtils.format((LocalTime) null, DateUtils.TIME_FORMAT_8, Locale.US)); } @Test @@ -208,8 +243,8 @@ void test_dateTimeFormatterCache_distinguishesRootFromDefaultLocale() { String rootResult = DateUtils.format(date, format, Locale.ROOT); String defaultResult = DateUtils.format(date, format, null); - Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.ROOT)), rootResult); - Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), defaultResult); + assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.ROOT)), rootResult); + assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), defaultResult); Assertions.assertNotEquals(rootResult, defaultResult); } finally { Locale.setDefault(Locale.Category.FORMAT, originalLocale); @@ -229,8 +264,8 @@ void test_dateTimeFormatterCache_tracksDefaultFormatLocaleChanges() { Locale.setDefault(Locale.Category.FORMAT, Locale.US); String usResult = DateUtils.format(date, format, null); - Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), franceResult); - Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.US)), usResult); + assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), franceResult); + assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.US)), usResult); Assertions.assertNotEquals(franceResult, usResult); } finally { Locale.setDefault(Locale.Category.FORMAT, originalLocale); @@ -253,12 +288,12 @@ void test_dateTimeFormatterCache_isBounded() throws NoSuchFieldException, Illega ThreadLocal>> threadLocal = (ThreadLocal>>) field.get(null); Map> localeCache = threadLocal.get(); - Assertions.assertEquals(formatCap, localeCache.get(Locale.US).size()); + assertEquals(formatCap, localeCache.get(Locale.US).size()); for (int i = 0; i < localeCap + 2; i++) { DateUtils.format(date, "yyyy-MM-dd", new Locale("en", "X" + i)); } - Assertions.assertEquals(localeCap, localeCache.size()); + assertEquals(localeCap, localeCache.size()); } private static int readIntConstant(String name) throws NoSuchFieldException, IllegalAccessException { @@ -292,7 +327,7 @@ void test_getJavaDate_1900(double excelValue, String expectedStr) { SimpleDateFormat sdf = new SimpleDateFormat(DateUtils.DATE_FORMAT_19); sdf.setTimeZone(TimeZone.getDefault()); - Assertions.assertEquals(expectedStr, sdf.format(date)); + assertEquals(expectedStr, sdf.format(date)); } @ParameterizedTest @@ -303,7 +338,7 @@ void test_getJavaDate_1904(double excelValue, String expectedStr) { SimpleDateFormat sdf = new SimpleDateFormat(DateUtils.DATE_FORMAT_19); sdf.setTimeZone(TimeZone.getDefault()); - Assertions.assertEquals(expectedStr, sdf.format(date)); + assertEquals(expectedStr, sdf.format(date)); } @ParameterizedTest @@ -318,7 +353,7 @@ void test_getLocalDateTime_1900(double excelValue, String expectedStr) { String formatted = date.atZone(TimeZone.getDefault().toZoneId()) .format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_19)); - Assertions.assertEquals(expectedStr, formatted); + assertEquals(expectedStr, formatted); } @ParameterizedTest @@ -329,7 +364,7 @@ void test_getLocalDateTime_1904(double excelValue, String expectedStr) { String formatted = date.atZone(TimeZone.getDefault().toZoneId()) .format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_19)); - Assertions.assertEquals(expectedStr, formatted); + assertEquals(expectedStr, formatted); } @ParameterizedTest @@ -339,7 +374,7 @@ void test_getLocalDate_1900(double excelValue, String expectedStr) { Assertions.assertNotNull(date); String formatted = date.format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_10)); - Assertions.assertEquals(expectedStr, formatted); + assertEquals(expectedStr, formatted); } @ParameterizedTest @@ -349,7 +384,23 @@ void test_getLocalDate_1904(double excelValue, String expectedStr) { Assertions.assertNotNull(date); String formatted = date.format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_10)); - Assertions.assertEquals(expectedStr, formatted); + assertEquals(expectedStr, formatted); + } + + @ParameterizedTest + @CsvSource({"0.5, 12:00:00", "1.0, 00:00:00", "43831.5, 12:00:00"}) + void test_getLocalTime_1900(double excelValue, String expectedStr) { + LocalTime time = DateUtils.getLocalTime(excelValue, false); + Assertions.assertNotNull(time); + assertEquals(expectedStr, time.format(DateTimeFormatter.ofPattern(DateUtils.TIME_FORMAT_8))); + } + + @ParameterizedTest + @CsvSource({"0.5, 12:00:00", "0.0, 00:00:00", "42369.5, 12:00:00"}) + void test_getLocalTime_1904(double excelValue, String expectedStr) { + LocalTime time = DateUtils.getLocalTime(excelValue, true); + Assertions.assertNotNull(time); + assertEquals(expectedStr, time.format(DateTimeFormatter.ofPattern(DateUtils.TIME_FORMAT_8))); } @Test @@ -366,9 +417,9 @@ void test_getJavaCalendar_rounding() { Calendar cal = DateUtils.getJavaCalendar(base + halfDay, false, null, true); Assertions.assertNotNull(cal); - Assertions.assertEquals(12, cal.get(Calendar.HOUR_OF_DAY)); - Assertions.assertEquals(0, cal.get(Calendar.SECOND)); - Assertions.assertEquals(0, cal.get(Calendar.MILLISECOND)); + assertEquals(12, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(0, cal.get(Calendar.SECOND)); + assertEquals(0, cal.get(Calendar.MILLISECOND)); } @ParameterizedTest diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/TestUtil.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/TestUtil.java index b7bae356f..ba1eec55b 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/TestUtil.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/TestUtil.java @@ -28,6 +28,7 @@ import java.text.ParseException; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Date; import lombok.extern.slf4j.Slf4j; @@ -42,6 +43,7 @@ public class TestUtil { public static final Date TEST_DATE; public static final LocalDate TEST_LOCAL_DATE = LocalDate.of(2020, 1, 1); public static final LocalDateTime TEST_LOCAL_DATE_TIME = LocalDateTime.of(2020, 1, 1, 1, 1, 1); + public static final LocalTime TEST_LOCAL_TIME = LocalTime.of(1, 1, 1); static { try { From bc5a133a411d7da1b37d1f5495615849b197206a Mon Sep 17 00:00:00 2001 From: lance Date: Sun, 23 Aug 2026 14:35:54 +0800 Subject: [PATCH 2/5] feat: add java.time.LocalTime converters Signed-off-by: lance --- .../sheet/converters/localtime/LocalTimeDateConverter.java | 6 ------ .../converters/localtime/LocalTimeNumberConverter.java | 6 ------ .../converters/localtime/LocalTimeStringConverter.java | 6 ------ 3 files changed, 18 deletions(-) diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java index 9658e1582..8395d5fab 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java @@ -17,12 +17,6 @@ * 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.localtime; import java.time.LocalDateTime; diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java index 4c9c42996..df1b80df0 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeNumberConverter.java @@ -17,12 +17,6 @@ * 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.localtime; import java.math.BigDecimal; diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java index cdc7ba93d..1a25405dc 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverter.java @@ -17,12 +17,6 @@ * 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.localtime; import java.time.LocalTime; From 301684e154027bbc01d39a57c35dd9bb62c5909e Mon Sep 17 00:00:00 2001 From: Lance Date: Sun, 23 Aug 2026 18:43:04 +0800 Subject: [PATCH 3/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../sheet/converters/localtime/LocalTimeDateConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java index 8395d5fab..d1c03301c 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java @@ -47,7 +47,7 @@ public WriteCellData convertToExcelData( if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { format = contentProperty.getDateTimeFormatProperty().getFormat(); } - WorkBookUtil.fillDataFormat(cellData, format, DateUtils.DEFAULT_LOCAL_TIME_FORMAT); + WorkBookUtil.fillDataFormat(cellData, format == null || format.isEmpty() ? null : format, DateUtils.DEFAULT_LOCAL_TIME_FORMAT); return cellData; } } From ae9e47e6b8a2a30942ab4492a0bd21f3bcd60bdb Mon Sep 17 00:00:00 2001 From: lance Date: Sun, 23 Aug 2026 18:45:51 +0800 Subject: [PATCH 4/5] feat: add java.time.LocalTime converters Signed-off-by: lance --- .../sheet/converters/localtime/LocalTimeDateConverter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java index d1c03301c..9bb505f8a 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localtime/LocalTimeDateConverter.java @@ -47,7 +47,8 @@ public WriteCellData convertToExcelData( if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { format = contentProperty.getDateTimeFormatProperty().getFormat(); } - WorkBookUtil.fillDataFormat(cellData, format == null || format.isEmpty() ? null : format, DateUtils.DEFAULT_LOCAL_TIME_FORMAT); + WorkBookUtil.fillDataFormat( + cellData, format == null || format.isEmpty() ? null : format, DateUtils.DEFAULT_LOCAL_TIME_FORMAT); return cellData; } } From bcfd88d3e2ae44d5d4268ff3a2bc179aee2bb7fd Mon Sep 17 00:00:00 2001 From: lance Date: Mon, 24 Aug 2026 19:47:51 +0800 Subject: [PATCH 5/5] fix avoid using wildcard imports Signed-off-by: lance --- .../LocalTimeStringConverterTest.java | 17 ++ .../fesod/sheet/util/DateUtilsTest.java | 156 +++++++++--------- 2 files changed, 95 insertions(+), 78 deletions(-) diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java index c15424461..f87f1ea54 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/localtime/LocalTimeStringConverterTest.java @@ -138,6 +138,23 @@ void convertToExcelDataKeepsNumericTimeAcrossLocales(Locale locale) { Assertions.assertEquals("12:30:45", written.getStringValue()); } + @Test + void convertToExcelDataUsesLocaleSensitiveAmPmMarker() { + ExcelContentProperty contentProperty = contentProperty("hh:mm:ss a", Boolean.FALSE); + LocalTime time = LocalTime.of(12, 30, 45); + + GlobalConfiguration us = new GlobalConfiguration(); + us.setLocale(Locale.US); + GlobalConfiguration china = new GlobalConfiguration(); + china.setLocale(Locale.CHINA); + + WriteCellData usWritten = converter.convertToExcelData(time, contentProperty, us); + WriteCellData chinaWritten = converter.convertToExcelData(time, contentProperty, china); + + Assertions.assertEquals("12:30:45 PM", usWritten.getStringValue()); + Assertions.assertEquals("12:30:45 下午", chinaWritten.getStringValue()); + } + @Test void convertToJavaDataRejectsUnknownPattern() { Assertions.assertThrows( diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java index ce2a5dfe3..dc845345b 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java @@ -19,8 +19,6 @@ package org.apache.fesod.sheet.util; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import java.lang.reflect.Field; import java.math.BigDecimal; import java.text.ParseException; @@ -58,26 +56,28 @@ void tearDown() { @Test void test_switchDateFormat() { - assertEquals(DateUtils.DATE_FORMAT_19, DateUtils.switchDateFormat("2026-01-01 12:00:00")); - assertEquals(DateUtils.DATE_FORMAT_19_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00:00")); + Assertions.assertEquals(DateUtils.DATE_FORMAT_19, DateUtils.switchDateFormat("2026-01-01 12:00:00")); + Assertions.assertEquals( + DateUtils.DATE_FORMAT_19_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00:00")); - assertEquals(DateUtils.DATE_FORMAT_16, DateUtils.switchDateFormat("2026-01-01 12:00")); - assertEquals(DateUtils.DATE_FORMAT_16_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00")); + Assertions.assertEquals(DateUtils.DATE_FORMAT_16, DateUtils.switchDateFormat("2026-01-01 12:00")); + Assertions.assertEquals(DateUtils.DATE_FORMAT_16_FORWARD_SLASH, DateUtils.switchDateFormat("2026/01/01 12:00")); - assertEquals(DateUtils.DATE_FORMAT_17, DateUtils.switchDateFormat("20260101 12:00:00")); - assertEquals(DateUtils.DATE_FORMAT_14, DateUtils.switchDateFormat("20260101120000")); - assertEquals(DateUtils.DATE_FORMAT_10, DateUtils.switchDateFormat("2026-01-01")); + Assertions.assertEquals(DateUtils.DATE_FORMAT_17, DateUtils.switchDateFormat("20260101 12:00:00")); + Assertions.assertEquals(DateUtils.DATE_FORMAT_14, DateUtils.switchDateFormat("20260101120000")); + Assertions.assertEquals(DateUtils.DATE_FORMAT_10, DateUtils.switchDateFormat("2026-01-01")); - assertThrows(IllegalArgumentException.class, () -> DateUtils.switchDateFormat("invalid_datestring_length")); + Assertions.assertThrows( + IllegalArgumentException.class, () -> DateUtils.switchDateFormat("invalid_datestring_length")); } @Test void test_switchTimeFormat() { - assertEquals(DateUtils.TIME_FORMAT_8, DateUtils.switchTimeFormat("12:30:45")); - assertEquals(DateUtils.TIME_FORMAT_5, DateUtils.switchTimeFormat("12:30")); + Assertions.assertEquals(DateUtils.TIME_FORMAT_8, DateUtils.switchTimeFormat("12:30:45")); + Assertions.assertEquals(DateUtils.TIME_FORMAT_5, DateUtils.switchTimeFormat("12:30")); - assertThrows(IllegalArgumentException.class, () -> DateUtils.switchTimeFormat("12:30:45.123")); - assertThrows(IllegalArgumentException.class, () -> DateUtils.switchTimeFormat("invalid")); + Assertions.assertThrows(IllegalArgumentException.class, () -> DateUtils.switchTimeFormat("12:30:45.123")); + Assertions.assertThrows(IllegalArgumentException.class, () -> DateUtils.switchTimeFormat("invalid")); } @Test @@ -87,17 +87,17 @@ void test_parseDate() throws ParseException { Calendar cal1 = Calendar.getInstance(); cal1.setTime(date1); - assertEquals(2026, cal1.get(Calendar.YEAR)); - assertEquals(Calendar.OCTOBER, cal1.get(Calendar.MONTH)); - assertEquals(30, cal1.get(Calendar.MINUTE)); + Assertions.assertEquals(2026, cal1.get(Calendar.YEAR)); + Assertions.assertEquals(Calendar.OCTOBER, cal1.get(Calendar.MONTH)); + Assertions.assertEquals(30, cal1.get(Calendar.MINUTE)); Date date2 = DateUtils.parseDate(dateStr, ""); Calendar cal2 = Calendar.getInstance(); cal2.setTime(date2); - assertEquals(2026, cal2.get(Calendar.YEAR)); - assertEquals(Calendar.OCTOBER, cal2.get(Calendar.MONTH)); - assertEquals(30, cal2.get(Calendar.MINUTE)); + Assertions.assertEquals(2026, cal2.get(Calendar.YEAR)); + Assertions.assertEquals(Calendar.OCTOBER, cal2.get(Calendar.MONTH)); + Assertions.assertEquals(30, cal2.get(Calendar.MINUTE)); } @Test @@ -106,30 +106,30 @@ void test_parseLocalDateTime() { String format = "yyyy-MM-dd HH:mm:ss"; LocalDateTime usResult = DateUtils.parseLocalDateTime(dateStr, format, Locale.US); - assertEquals(2026, usResult.getYear()); - assertEquals(10, usResult.getMonthValue()); - assertEquals(1, usResult.getDayOfMonth()); - assertEquals(12, usResult.getHour()); - assertEquals(30, usResult.getMinute()); - assertEquals(45, usResult.getSecond()); + Assertions.assertEquals(2026, usResult.getYear()); + Assertions.assertEquals(10, usResult.getMonthValue()); + Assertions.assertEquals(1, usResult.getDayOfMonth()); + Assertions.assertEquals(12, usResult.getHour()); + Assertions.assertEquals(30, usResult.getMinute()); + Assertions.assertEquals(45, usResult.getSecond()); LocalDateTime result = DateUtils.parseLocalDateTime(dateStr, format, null); - assertEquals(2026, result.getYear()); - assertEquals(10, result.getMonthValue()); - assertEquals(1, result.getDayOfMonth()); - assertEquals(12, result.getHour()); - assertEquals(30, result.getMinute()); - assertEquals(45, result.getSecond()); + Assertions.assertEquals(2026, result.getYear()); + Assertions.assertEquals(10, result.getMonthValue()); + Assertions.assertEquals(1, result.getDayOfMonth()); + Assertions.assertEquals(12, result.getHour()); + Assertions.assertEquals(30, result.getMinute()); + Assertions.assertEquals(45, result.getSecond()); LocalDateTime autoDetectFormatResult = DateUtils.parseLocalDateTime(dateStr, "", null); - assertEquals(2026, autoDetectFormatResult.getYear()); - assertEquals(10, autoDetectFormatResult.getMonthValue()); - assertEquals(1, autoDetectFormatResult.getDayOfMonth()); - assertEquals(12, autoDetectFormatResult.getHour()); - assertEquals(30, autoDetectFormatResult.getMinute()); - assertEquals(45, autoDetectFormatResult.getSecond()); + Assertions.assertEquals(2026, autoDetectFormatResult.getYear()); + Assertions.assertEquals(10, autoDetectFormatResult.getMonthValue()); + Assertions.assertEquals(1, autoDetectFormatResult.getDayOfMonth()); + Assertions.assertEquals(12, autoDetectFormatResult.getHour()); + Assertions.assertEquals(30, autoDetectFormatResult.getMinute()); + Assertions.assertEquals(45, autoDetectFormatResult.getSecond()); } @Test @@ -138,36 +138,36 @@ void test_parseLocalDate() { String format = "yyyy-MM-dd"; LocalDate usResult = DateUtils.parseLocalDate(dateStr, format, Locale.US); - assertEquals(2026, usResult.getYear()); - assertEquals(10, usResult.getMonthValue()); - assertEquals(1, usResult.getDayOfMonth()); + Assertions.assertEquals(2026, usResult.getYear()); + Assertions.assertEquals(10, usResult.getMonthValue()); + Assertions.assertEquals(1, usResult.getDayOfMonth()); LocalDate result = DateUtils.parseLocalDate(dateStr, format, null); - assertEquals(2026, result.getYear()); - assertEquals(10, result.getMonthValue()); - assertEquals(1, result.getDayOfMonth()); + Assertions.assertEquals(2026, result.getYear()); + Assertions.assertEquals(10, result.getMonthValue()); + Assertions.assertEquals(1, result.getDayOfMonth()); LocalDate autoDetectFormatResult = DateUtils.parseLocalDate(dateStr, "", null); - assertEquals(2026, autoDetectFormatResult.getYear()); - assertEquals(10, autoDetectFormatResult.getMonthValue()); - assertEquals(1, autoDetectFormatResult.getDayOfMonth()); + Assertions.assertEquals(2026, autoDetectFormatResult.getYear()); + Assertions.assertEquals(10, autoDetectFormatResult.getMonthValue()); + Assertions.assertEquals(1, autoDetectFormatResult.getDayOfMonth()); } @Test void test_parseLocalTime() { LocalTime usResult = DateUtils.parseLocalTime("12:30:45", DateUtils.TIME_FORMAT_8, Locale.US); - assertEquals(LocalTime.of(12, 30, 45), usResult); + Assertions.assertEquals(LocalTime.of(12, 30, 45), usResult); LocalTime result = DateUtils.parseLocalTime("12:30:45", DateUtils.TIME_FORMAT_8, null); - assertEquals(LocalTime.of(12, 30, 45), result); + Assertions.assertEquals(LocalTime.of(12, 30, 45), result); LocalTime autoDetectSeconds = DateUtils.parseLocalTime("12:30:45", "", null); - assertEquals(LocalTime.of(12, 30, 45), autoDetectSeconds); + Assertions.assertEquals(LocalTime.of(12, 30, 45), autoDetectSeconds); LocalTime autoDetectMinutes = DateUtils.parseLocalTime("12:30", "", null); - assertEquals(LocalTime.of(12, 30), autoDetectMinutes); + Assertions.assertEquals(LocalTime.of(12, 30), autoDetectMinutes); } @Test @@ -176,7 +176,7 @@ void test_format_default() { String result = DateUtils.format(now); Assertions.assertNotNull(result); // yyyy-MM-dd HH:mm:ss - assertEquals(19, result.length()); + Assertions.assertEquals(19, result.length()); Assertions.assertNull(DateUtils.format(null)); } @@ -187,7 +187,7 @@ void test_format_LocalDateTime() { String format = "dd-MMM-yyyy"; String usResult = DateUtils.format(ldt, format, Locale.US); - assertEquals("01-Oct-2026", usResult); + Assertions.assertEquals("01-Oct-2026", usResult); String cnResult = DateUtils.format(ldt, format, Locale.SIMPLIFIED_CHINESE); Assertions.assertNotNull(cnResult); @@ -195,7 +195,7 @@ void test_format_LocalDateTime() { Assertions.assertNull(DateUtils.format((LocalDateTime) null, format, Locale.US)); String defaultUSResult = DateUtils.format(ldt, "", Locale.US); - assertEquals("2026-10-01 12:00:00", defaultUSResult); + Assertions.assertEquals("2026-10-01 12:00:00", defaultUSResult); } @Test @@ -204,7 +204,7 @@ void test_format_LocalDate() { String format = "dd-MMM-yyyy"; String usResult = DateUtils.format(ld, format, Locale.US); - assertEquals("01-Oct-2026", usResult); + Assertions.assertEquals("01-Oct-2026", usResult); String cnResult = DateUtils.format(ld, format, Locale.SIMPLIFIED_CHINESE); Assertions.assertNotNull(cnResult); @@ -212,22 +212,22 @@ void test_format_LocalDate() { Assertions.assertNull(DateUtils.format((LocalDate) null, format, Locale.US)); String defaultUSResult = DateUtils.format(ld, "", Locale.US); - assertEquals("2026-10-01", defaultUSResult); + Assertions.assertEquals("2026-10-01", defaultUSResult); String defaultFormatResult = DateUtils.format(ld, "", null); - assertEquals("2026-10-01", defaultFormatResult); + Assertions.assertEquals("2026-10-01", defaultFormatResult); String defaultFormatResult2 = DateUtils.format(ld, ""); - assertEquals("2026-10-01", defaultFormatResult2); + Assertions.assertEquals("2026-10-01", defaultFormatResult2); } @Test void test_format_LocalTime() { LocalTime time = LocalTime.of(12, 30, 45); - assertEquals("12:30:45", DateUtils.format(time, null, Locale.US)); - assertEquals("12:30", DateUtils.format(time, DateUtils.TIME_FORMAT_5, Locale.US)); - assertEquals("12:30:45", DateUtils.format(time, "")); + Assertions.assertEquals("12:30:45", DateUtils.format(time, null, Locale.US)); + Assertions.assertEquals("12:30", DateUtils.format(time, DateUtils.TIME_FORMAT_5, Locale.US)); + Assertions.assertEquals("12:30:45", DateUtils.format(time, "")); Assertions.assertNull(DateUtils.format((LocalTime) null, DateUtils.TIME_FORMAT_8, Locale.US)); } @@ -243,8 +243,8 @@ void test_dateTimeFormatterCache_distinguishesRootFromDefaultLocale() { String rootResult = DateUtils.format(date, format, Locale.ROOT); String defaultResult = DateUtils.format(date, format, null); - assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.ROOT)), rootResult); - assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), defaultResult); + Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.ROOT)), rootResult); + Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), defaultResult); Assertions.assertNotEquals(rootResult, defaultResult); } finally { Locale.setDefault(Locale.Category.FORMAT, originalLocale); @@ -264,8 +264,8 @@ void test_dateTimeFormatterCache_tracksDefaultFormatLocaleChanges() { Locale.setDefault(Locale.Category.FORMAT, Locale.US); String usResult = DateUtils.format(date, format, null); - assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), franceResult); - assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.US)), usResult); + Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.FRANCE)), franceResult); + Assertions.assertEquals(date.format(DateTimeFormatter.ofPattern(format, Locale.US)), usResult); Assertions.assertNotEquals(franceResult, usResult); } finally { Locale.setDefault(Locale.Category.FORMAT, originalLocale); @@ -288,12 +288,12 @@ void test_dateTimeFormatterCache_isBounded() throws NoSuchFieldException, Illega ThreadLocal>> threadLocal = (ThreadLocal>>) field.get(null); Map> localeCache = threadLocal.get(); - assertEquals(formatCap, localeCache.get(Locale.US).size()); + Assertions.assertEquals(formatCap, localeCache.get(Locale.US).size()); for (int i = 0; i < localeCap + 2; i++) { DateUtils.format(date, "yyyy-MM-dd", new Locale("en", "X" + i)); } - assertEquals(localeCap, localeCache.size()); + Assertions.assertEquals(localeCap, localeCache.size()); } private static int readIntConstant(String name) throws NoSuchFieldException, IllegalAccessException { @@ -327,7 +327,7 @@ void test_getJavaDate_1900(double excelValue, String expectedStr) { SimpleDateFormat sdf = new SimpleDateFormat(DateUtils.DATE_FORMAT_19); sdf.setTimeZone(TimeZone.getDefault()); - assertEquals(expectedStr, sdf.format(date)); + Assertions.assertEquals(expectedStr, sdf.format(date)); } @ParameterizedTest @@ -338,7 +338,7 @@ void test_getJavaDate_1904(double excelValue, String expectedStr) { SimpleDateFormat sdf = new SimpleDateFormat(DateUtils.DATE_FORMAT_19); sdf.setTimeZone(TimeZone.getDefault()); - assertEquals(expectedStr, sdf.format(date)); + Assertions.assertEquals(expectedStr, sdf.format(date)); } @ParameterizedTest @@ -353,7 +353,7 @@ void test_getLocalDateTime_1900(double excelValue, String expectedStr) { String formatted = date.atZone(TimeZone.getDefault().toZoneId()) .format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_19)); - assertEquals(expectedStr, formatted); + Assertions.assertEquals(expectedStr, formatted); } @ParameterizedTest @@ -364,7 +364,7 @@ void test_getLocalDateTime_1904(double excelValue, String expectedStr) { String formatted = date.atZone(TimeZone.getDefault().toZoneId()) .format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_19)); - assertEquals(expectedStr, formatted); + Assertions.assertEquals(expectedStr, formatted); } @ParameterizedTest @@ -374,7 +374,7 @@ void test_getLocalDate_1900(double excelValue, String expectedStr) { Assertions.assertNotNull(date); String formatted = date.format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_10)); - assertEquals(expectedStr, formatted); + Assertions.assertEquals(expectedStr, formatted); } @ParameterizedTest @@ -384,7 +384,7 @@ void test_getLocalDate_1904(double excelValue, String expectedStr) { Assertions.assertNotNull(date); String formatted = date.format(DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_10)); - assertEquals(expectedStr, formatted); + Assertions.assertEquals(expectedStr, formatted); } @ParameterizedTest @@ -392,7 +392,7 @@ void test_getLocalDate_1904(double excelValue, String expectedStr) { void test_getLocalTime_1900(double excelValue, String expectedStr) { LocalTime time = DateUtils.getLocalTime(excelValue, false); Assertions.assertNotNull(time); - assertEquals(expectedStr, time.format(DateTimeFormatter.ofPattern(DateUtils.TIME_FORMAT_8))); + Assertions.assertEquals(expectedStr, time.format(DateTimeFormatter.ofPattern(DateUtils.TIME_FORMAT_8))); } @ParameterizedTest @@ -400,7 +400,7 @@ void test_getLocalTime_1900(double excelValue, String expectedStr) { void test_getLocalTime_1904(double excelValue, String expectedStr) { LocalTime time = DateUtils.getLocalTime(excelValue, true); Assertions.assertNotNull(time); - assertEquals(expectedStr, time.format(DateTimeFormatter.ofPattern(DateUtils.TIME_FORMAT_8))); + Assertions.assertEquals(expectedStr, time.format(DateTimeFormatter.ofPattern(DateUtils.TIME_FORMAT_8))); } @Test @@ -417,9 +417,9 @@ void test_getJavaCalendar_rounding() { Calendar cal = DateUtils.getJavaCalendar(base + halfDay, false, null, true); Assertions.assertNotNull(cal); - assertEquals(12, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(0, cal.get(Calendar.SECOND)); - assertEquals(0, cal.get(Calendar.MILLISECOND)); + Assertions.assertEquals(12, cal.get(Calendar.HOUR_OF_DAY)); + Assertions.assertEquals(0, cal.get(Calendar.SECOND)); + Assertions.assertEquals(0, cal.get(Calendar.MILLISECOND)); } @ParameterizedTest