From 96b6a3fa477fddee895507530ce60cb00bad47a9 Mon Sep 17 00:00:00 2001 From: Mikkey-f <178465103+Mikkey-f@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:43:59 +0800 Subject: [PATCH] feat: add OffsetTime string converter (#1017) --- .../converters/DefaultConverterLoader.java | 4 + .../offsettime/OffsetTimeStringConverter.java | 62 +++++ .../apache/fesod/sheet/util/DateUtils.java | 34 +++ .../DefaultConverterLoaderTest.java | 17 ++ .../offsettime/OffsetTimeRoundTripData.java | 49 ++++ .../offsettime/OffsetTimeRoundTripTest.java | 53 +++++ .../OffsetTimeStringConverterTest.java | 212 ++++++++++++++++++ .../fesod/sheet/util/DateUtilsTest.java | 20 ++ 8 files changed, 451 insertions(+) create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverter.java create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeRoundTripData.java create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeRoundTripTest.java create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverterTest.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 95a247442..8da6883cd 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,7 @@ import org.apache.fesod.sheet.converters.offsetdatetime.OffsetDateTimeDateConverter; import org.apache.fesod.sheet.converters.offsetdatetime.OffsetDateTimeNumberConverter; import org.apache.fesod.sheet.converters.offsetdatetime.OffsetDateTimeStringConverter; +import org.apache.fesod.sheet.converters.offsettime.OffsetTimeStringConverter; import org.apache.fesod.sheet.converters.shortconverter.ShortBooleanConverter; import org.apache.fesod.sheet.converters.shortconverter.ShortNumberConverter; import org.apache.fesod.sheet.converters.shortconverter.ShortStringConverter; @@ -143,6 +144,7 @@ private static void initAllConverter() { putAllConverter(new TimestampStringConverter()); putAllConverter(new OffsetDateTimeNumberConverter()); putAllConverter(new OffsetDateTimeStringConverter()); + putAllConverter(new OffsetTimeStringConverter()); putAllConverter(new InstantStringConverter()); @@ -192,6 +194,7 @@ private static void initDefaultWriteConverter() { putWriteConverter(new SqlTimeDateConverter()); putWriteConverter(new TimestampDateConverter()); putWriteConverter(new OffsetDateTimeDateConverter()); + putWriteConverter(new OffsetTimeStringConverter()); putWriteConverter(new DoubleNumberConverter()); putWriteConverter(new FloatNumberConverter()); putWriteConverter(new IntegerNumberConverter()); @@ -220,6 +223,7 @@ private static void initDefaultWriteConverter() { putWriteStringConverter(new SqlTimeStringConverter()); putWriteStringConverter(new TimestampStringConverter()); putWriteStringConverter(new OffsetDateTimeStringConverter()); + putWriteStringConverter(new OffsetTimeStringConverter()); putWriteStringConverter(new DoubleStringConverter()); putWriteStringConverter(new FloatStringConverter()); putWriteStringConverter(new IntegerStringConverter()); diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverter.java new file mode 100644 index 000000000..455ec7b6f --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverter.java @@ -0,0 +1,62 @@ +/* + * 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.offsettime; + +import java.time.OffsetTime; +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; + +/** OffsetTime and string converter. */ +public class OffsetTimeStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return OffsetTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public OffsetTime convertToJavaData( + ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + return DateUtils.parseOffsetTime( + cellData.getStringValue(), format(contentProperty), globalConfiguration.getLocale()); + } + + @Override + public WriteCellData convertToExcelData( + OffsetTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(DateUtils.format(value, format(contentProperty), globalConfiguration.getLocale())); + } + + private String format(ExcelContentProperty contentProperty) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return null; + } + return contentProperty.getDateTimeFormatProperty().getFormat(); + } +} 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 7ea687fbc..a36fb4b2d 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 @@ -33,6 +33,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.time.OffsetTime; import java.time.Year; import java.time.format.DateTimeFormatter; import java.time.format.ResolverStyle; @@ -185,6 +186,21 @@ public static OffsetDateTime parseOffsetDateTime(String dateString, String dateF return OffsetDateTime.parse(dateString, getCacheDateTimeFormat(dateFormat, local)); } + /** + * convert string to time, requiring the string to carry an explicit offset + * + * @param timeString time string, e.g. 03:04:05+08:00 + * @param timeFormat time format, empty means ISO_OFFSET_TIME + * @param local local + * @return + */ + public static OffsetTime parseOffsetTime(String timeString, String timeFormat, Locale local) { + if (StringUtils.isEmpty(timeFormat)) { + return OffsetTime.parse(timeString, DateTimeFormatter.ISO_OFFSET_TIME); + } + return OffsetTime.parse(timeString, getCacheDateTimeFormat(timeFormat, local)); + } + /** * convert string to date * @@ -404,6 +420,24 @@ public static String format(OffsetDateTime date, String dateFormat, Locale local return date.format(getCacheDateTimeFormat(dateFormat, local)); } + /** + * Format time, preserving the offset in the output + * + * @param time time + * @param timeFormat time format, empty means ISO_OFFSET_TIME + * @param local local + * @return format string + */ + public static String format(OffsetTime time, String timeFormat, Locale local) { + if (time == null) { + return null; + } + if (StringUtils.isEmpty(timeFormat)) { + return time.format(DateTimeFormatter.ISO_OFFSET_TIME); + } + return time.format(getCacheDateTimeFormat(timeFormat, local)); + } + /** * Format date * 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 bd5f19d41..0a5e36b47 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 @@ -23,6 +23,7 @@ import java.sql.Timestamp; import java.time.Instant; import java.time.LocalTime; +import java.time.OffsetTime; import java.util.Map; import java.util.UUID; import org.apache.fesod.sheet.converters.ConverterKeyBuild.ConverterKey; @@ -30,6 +31,7 @@ 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.offsettime.OffsetTimeStringConverter; import org.apache.fesod.sheet.converters.sqltime.SqlTimeDateConverter; import org.apache.fesod.sheet.converters.sqltime.SqlTimeNumberConverter; import org.apache.fesod.sheet.converters.sqltime.SqlTimeStringConverter; @@ -143,6 +145,21 @@ void loadConvertersRegistersTimestampFamily() { writeConverter.get(ConverterKeyBuild.buildKey(Timestamp.class, CellDataTypeEnum.STRING))); } + @Test + void loadConvertersRegistersOffsetTimeStringConverter() { + Map> allConverter = DefaultConverterLoader.loadAllConverter(); + Assertions.assertInstanceOf( + OffsetTimeStringConverter.class, + allConverter.get(ConverterKeyBuild.buildKey(OffsetTime.class, CellDataTypeEnum.STRING))); + + Map> writeConverter = DefaultConverterLoader.loadDefaultWriteConverter(); + Assertions.assertInstanceOf( + OffsetTimeStringConverter.class, writeConverter.get(ConverterKeyBuild.buildKey(OffsetTime.class))); + Assertions.assertInstanceOf( + OffsetTimeStringConverter.class, + writeConverter.get(ConverterKeyBuild.buildKey(OffsetTime.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/offsettime/OffsetTimeRoundTripData.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeRoundTripData.java new file mode 100644 index 000000000..e58fda12a --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeRoundTripData.java @@ -0,0 +1,49 @@ +/* + * 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.offsettime; + +import java.time.OffsetTime; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.format.DateTimeFormat; + +public class OffsetTimeRoundTripData { + @ExcelProperty("iso") + private OffsetTime iso; + + @ExcelProperty("custom") + @DateTimeFormat("HH:mm:ss Z") + private OffsetTime custom; + + public OffsetTime getIso() { + return iso; + } + + public void setIso(OffsetTime iso) { + this.iso = iso; + } + + public OffsetTime getCustom() { + return custom; + } + + public void setCustom(OffsetTime custom) { + this.custom = custom; + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeRoundTripTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeRoundTripTest.java new file mode 100644 index 000000000..c20474a7a --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeRoundTripTest.java @@ -0,0 +1,53 @@ +/* + * 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.offsettime; + +import java.io.File; +import java.time.OffsetTime; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.List; +import org.apache.fesod.sheet.testkit.Tags; +import org.apache.fesod.sheet.testkit.base.AbstractExcelTest; +import org.apache.fesod.sheet.testkit.enums.ExcelFormat; +import org.apache.fesod.sheet.testkit.helpers.RoundTripHelper; +import org.apache.fesod.sheet.testkit.params.ExcelFormatSource; +import org.apache.fesod.sheet.testkit.params.FormatScope; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; + +@Tag(Tags.ROUND_TRIP) +class OffsetTimeRoundTripTest extends AbstractExcelTest { + @ParameterizedTest + @ExcelFormatSource(FormatScope.ALL) + void roundTrip(ExcelFormat format) throws Exception { + File file = createTempFile(format); + OffsetTimeRoundTripData input = new OffsetTimeRoundTripData(); + input.setIso(OffsetTime.of(9, 30, 45, 0, ZoneOffset.ofHours(8))); + input.setCustom(OffsetTime.of(18, 5, 1, 0, ZoneOffset.ofHours(-5))); + List result = + RoundTripHelper.writeAndRead(file, OffsetTimeRoundTripData.class, Collections.singletonList(input)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(input.getIso(), result.get(0).getIso()); + Assertions.assertEquals(input.getCustom(), result.get(0).getCustom()); + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverterTest.java new file mode 100644 index 000000000..705a05874 --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/offsettime/OffsetTimeStringConverterTest.java @@ -0,0 +1,212 @@ +/* + * 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.offsettime; + +import java.time.OffsetTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeParseException; +import java.util.Locale; +import java.util.stream.Stream; +import org.apache.fesod.sheet.converters.ConverterKeyBuild; +import org.apache.fesod.sheet.converters.DefaultConverterLoader; +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 OffsetTimeStringConverter}. + */ +@Tag(Tags.UNIT) +class OffsetTimeStringConverterTest { + + private static final GlobalConfiguration GLOBAL_CONFIGURATION = new GlobalConfiguration(); + private static final OffsetTime VALUE = OffsetTime.of(9, 30, 45, 0, ZoneOffset.ofHours(8)); + private final OffsetTimeStringConverter converter = new OffsetTimeStringConverter(); + + @AfterEach + void tearDown() { + DateUtils.removeThreadLocalCache(); + } + + @Test + void supportKeys() { + Assertions.assertEquals(OffsetTime.class, converter.supportJavaTypeKey()); + Assertions.assertEquals(CellDataTypeEnum.STRING, converter.supportExcelTypeKey()); + } + + @ParameterizedTest + @CsvSource({"09:30:45+08:00", "23:59:59-05:00", "00:00:00Z", "12:34:56.789+05:30"}) + void convertRoundTripWithDefaultFormat(String value) { + OffsetTime time = OffsetTime.parse(value); + + OffsetTime 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 convertUsesCustomFormatFromContentProperty() { + ExcelContentProperty contentProperty = contentProperty("HH:mm:ss Z", Boolean.FALSE); + + OffsetTime actual = converter.convertToJavaData( + new ReadCellData<>("09:30:45 +0800"), contentProperty, GLOBAL_CONFIGURATION); + WriteCellData written = converter.convertToExcelData(VALUE, contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(VALUE, actual); + Assertions.assertEquals("09:30:45 +0800", written.getStringValue()); + } + + @Test + void convertFallsBackToDefaultWhenContentPropertyHasNoDateTimeFormat() { + ExcelContentProperty contentProperty = new ExcelContentProperty(); + + OffsetTime actual = converter.convertToJavaData( + new ReadCellData<>("09:30:45+08:00"), contentProperty, GLOBAL_CONFIGURATION); + WriteCellData written = converter.convertToExcelData(VALUE, contentProperty, GLOBAL_CONFIGURATION); + + Assertions.assertEquals(VALUE, actual); + Assertions.assertEquals("09:30:45+08:00", written.getStringValue()); + } + + @Test + void convertWithEmptyOrNullPatternFallsBackToIso() { + ExcelContentProperty emptyProperty = contentProperty("", Boolean.FALSE); + ExcelContentProperty nullProperty = contentProperty(null, Boolean.FALSE); + + Assertions.assertEquals( + VALUE, + converter.convertToJavaData(new ReadCellData<>("09:30:45+08:00"), emptyProperty, GLOBAL_CONFIGURATION)); + Assertions.assertEquals( + VALUE, + converter.convertToJavaData(new ReadCellData<>("09:30:45+08:00"), nullProperty, GLOBAL_CONFIGURATION)); + Assertions.assertEquals( + "09:30:45+08:00", + converter + .convertToExcelData(VALUE, emptyProperty, GLOBAL_CONFIGURATION) + .getStringValue()); + Assertions.assertEquals( + "09:30:45+08:00", + converter + .convertToExcelData(VALUE, nullProperty, GLOBAL_CONFIGURATION) + .getStringValue()); + } + + @ParameterizedTest + @MethodSource("locales") + void convertToExcelDataKeepsOffsetAcrossLocales(Locale locale) { + GlobalConfiguration configuration = new GlobalConfiguration(); + configuration.setLocale(locale); + + WriteCellData written = converter.convertToExcelData(VALUE, null, configuration); + + Assertions.assertEquals("09:30:45+08:00", written.getStringValue()); + } + + @Test + void convertToExcelDataUsesLocaleSensitiveAmPmMarker() { + ExcelContentProperty contentProperty = contentProperty("hh:mm:ss a", Boolean.FALSE); + OffsetTime time = OffsetTime.of(12, 30, 45, 0, ZoneOffset.ofHours(8)); + + 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 convertToJavaDataRejectsTextWithoutOffset() { + Assertions.assertThrows( + DateTimeParseException.class, + () -> converter.convertToJavaData(new ReadCellData<>("09:30:45"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToJavaDataRejectsTextWithUnparsedOffsetWhenPatternHasNone() { + ExcelContentProperty contentProperty = contentProperty("HH:mm:ss", Boolean.FALSE); + + Assertions.assertThrows( + DateTimeParseException.class, + () -> converter.convertToJavaData( + new ReadCellData<>("09:30:45+08:00"), contentProperty, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToJavaDataRejectsInvalidClockTime() { + Assertions.assertThrows( + DateTimeParseException.class, + () -> converter.convertToJavaData(new ReadCellData<>("99:99:99+08:00"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToExcelDataRejectsNullValue() { + Assertions.assertThrows( + IllegalArgumentException.class, () -> converter.convertToExcelData(null, null, GLOBAL_CONFIGURATION)); + } + + @Test + void converterIsRegisteredForSupportedDirections() { + Assertions.assertEquals( + OffsetTimeStringConverter.class, + DefaultConverterLoader.loadAllConverter() + .get(ConverterKeyBuild.buildKey(OffsetTime.class, CellDataTypeEnum.STRING)) + .getClass()); + Assertions.assertEquals( + OffsetTimeStringConverter.class, + DefaultConverterLoader.loadDefaultWriteConverter() + .get(ConverterKeyBuild.buildKey(OffsetTime.class)) + .getClass()); + Assertions.assertEquals( + OffsetTimeStringConverter.class, + DefaultConverterLoader.loadDefaultWriteConverter() + .get(ConverterKeyBuild.buildKey(OffsetTime.class, CellDataTypeEnum.STRING)) + .getClass()); + } + + 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/util/DateUtilsTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java index c9b43c3e4..d07d8e6cb 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 @@ -27,6 +27,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.time.OffsetTime; import java.time.Year; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @@ -573,6 +574,25 @@ void test_format_OffsetDateTime() { "02 Januar 2020 03:04:05 +08:00", DateUtils.format(value, "dd MMMM yyyy HH:mm:ss XXX", Locale.GERMAN)); } + @Test + void test_parseOffsetTime() { + OffsetTime expected = OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHours(8)); + Assertions.assertEquals(expected, DateUtils.parseOffsetTime("03:04:05+08:00", null, Locale.US)); + Assertions.assertEquals(expected, DateUtils.parseOffsetTime("03:04:05+08:00", "", Locale.US)); + Assertions.assertEquals(expected, DateUtils.parseOffsetTime("03:04:05 +08:00", "HH:mm:ss XXX", Locale.US)); + Assertions.assertThrows( + DateTimeParseException.class, () -> DateUtils.parseOffsetTime("03:04:05", null, Locale.US)); + } + + @Test + void test_format_OffsetTime() { + OffsetTime value = OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHours(8)); + Assertions.assertNull(DateUtils.format((OffsetTime) null, null, Locale.US)); + Assertions.assertEquals("03:04:05+08:00", DateUtils.format(value, null, Locale.US)); + Assertions.assertEquals("03:04:05+08:00", DateUtils.format(value, "", Locale.US)); + Assertions.assertEquals("03:04:05 +08:00", DateUtils.format(value, "HH:mm:ss XXX", Locale.US)); + } + @Test void test_removeThreadLocalCache() throws NoSuchFieldException, IllegalAccessException { DateUtils.format(new Date(), "yyyy-MM-dd");