From 6c81ca4bc80fbe75f564a4e8dd4bad541394eebb Mon Sep 17 00:00:00 2001 From: CodeMan-cmd <2291415248@qq.com> Date: Wed, 30 Sep 2026 15:22:34 +0800 Subject: [PATCH] feat: add java.time.YearMonth converters Excel has no month type, so a month is written as a date cell of the first day of that month with a yyyy-MM number format, which keeps sorting and date arithmetic in the sheet working. The string family reads and writes yyyy-MM, uses the locale of the read or write when a month name is parsed or written, and treats an empty @DateTimeFormat like a missing one because an empty pattern is not a usable one. --- .../converters/DefaultConverterLoader.java | 8 + .../yearmonth/YearMonthDateConverter.java | 61 +++++++ .../yearmonth/YearMonthNumberConverter.java | 66 +++++++ .../yearmonth/YearMonthStringConverter.java | 90 ++++++++++ .../yearmonth/YearMonthDataTest.java | 161 ++++++++++++++++++ 5 files changed, 386 insertions(+) create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDateConverter.java create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthNumberConverter.java create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthStringConverter.java create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDataTest.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 f17d9b62c..3f745d745 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 @@ -78,6 +78,9 @@ import org.apache.fesod.sheet.converters.string.StringNumberConverter; import org.apache.fesod.sheet.converters.string.StringStringConverter; import org.apache.fesod.sheet.converters.url.UrlImageConverter; +import org.apache.fesod.sheet.converters.yearmonth.YearMonthDateConverter; +import org.apache.fesod.sheet.converters.yearmonth.YearMonthNumberConverter; +import org.apache.fesod.sheet.converters.yearmonth.YearMonthStringConverter; /** * Load default handler @@ -114,6 +117,9 @@ private static void initAllConverter() { putAllConverter(new DateNumberConverter()); putAllConverter(new DateStringConverter()); + putAllConverter(new YearMonthNumberConverter()); + putAllConverter(new YearMonthStringConverter()); + putAllConverter(new LocalDateNumberConverter()); putAllConverter(new LocalDateStringConverter()); @@ -157,6 +163,7 @@ private static void initDefaultWriteConverter() { putWriteConverter(new BooleanBooleanConverter()); putWriteConverter(new ByteNumberConverter()); putWriteConverter(new DateDateConverter()); + putWriteConverter(new YearMonthDateConverter()); putWriteConverter(new LocalDateTimeDateConverter()); putWriteConverter(new LocalDateDateConverter()); putWriteConverter(new LocalTimeDateConverter()); @@ -178,6 +185,7 @@ private static void initDefaultWriteConverter() { putWriteStringConverter(new BooleanStringConverter()); putWriteStringConverter(new ByteStringConverter()); putWriteStringConverter(new DateStringConverter()); + putWriteStringConverter(new YearMonthStringConverter()); putWriteStringConverter(new LocalDateStringConverter()); putWriteStringConverter(new LocalDateTimeStringConverter()); putWriteStringConverter(new LocalTimeStringConverter()); diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDateConverter.java new file mode 100644 index 000000000..8110370b4 --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDateConverter.java @@ -0,0 +1,61 @@ +/* + * 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.yearmonth; + +import java.time.YearMonth; +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.WorkBookUtil; + +/** + * YearMonth and date converter + * + *
Excel has no month type, so a month is written as a date cell of the first day of that month, formatted with
+ * {@link #DEFAULT_FORMAT}, which keeps sorting and date arithmetic in the sheet working.
+ *
+ */
+public class YearMonthDateConverter implements Converter A month is read from and written to the first day of that month, so the value stays a real date in the sheet.
+ *
+ */
+public class YearMonthNumberConverter implements Converter {@code @DateTimeFormat} defaults to an empty value and an empty pattern is not a usable one, so an empty
+ * format is handled like a missing one. The locale of the read or write is used, so a month name in the
+ * configured language can be parsed and written.
+ *
+ * @param contentProperty the property of the field
+ * @param globalConfiguration the configuration of the read or write, which carries the locale
+ * @return the formatter to use
+ */
+ private static DateTimeFormatter formatter(
+ ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ String format = null;
+ if (contentProperty != null
+ && contentProperty.getDateTimeFormatProperty() != null
+ && StringUtils.isNotBlank(
+ contentProperty.getDateTimeFormatProperty().getFormat())) {
+ format = contentProperty.getDateTimeFormatProperty().getFormat();
+ }
+ if (format == null) {
+ format = YearMonthDateConverter.DEFAULT_FORMAT;
+ }
+ Locale locale = globalConfiguration == null ? null : globalConfiguration.getLocale();
+ if (locale == null) {
+ return DateTimeFormatter.ofPattern(format);
+ }
+ return DateTimeFormatter.ofPattern(format, locale);
+ }
+}
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDataTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDataTest.java
new file mode 100644
index 000000000..d21f8add7
--- /dev/null
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthDataTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.yearmonth;
+
+import java.io.File;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fesod.sheet.FesodSheet;
+import org.apache.fesod.sheet.annotation.ExcelProperty;
+import org.apache.fesod.sheet.annotation.format.DateTimeFormat;
+import org.apache.fesod.sheet.support.ExcelTypeEnum;
+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.listeners.CollectingReadListener;
+import org.apache.fesod.sheet.testkit.params.ExcelFormatSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+
+/**
+ * Round trip tests for the java.time.YearMonth converters.
+ */
+@Tag(Tags.ROUND_TRIP)
+public class YearMonthDataTest extends AbstractExcelTest {
+
+ /**
+ * A month name is written and parsed with the locale of the read or write, so the same French pattern is used on
+ * both sides.
+ */
+ private static final DateTimeFormatter FRENCH_MONTH = DateTimeFormatter.ofPattern("MMM yyyy", Locale.FRENCH);
+
+ @ParameterizedTest
+ @ExcelFormatSource
+ void readAndWrite(ExcelFormat format) throws Exception {
+ File file = createTempFile(format);
+ FesodSheet.write(file, YearMonthData.class).sheet().doWrite(data());
+
+ CollectingReadListener