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 {
+
+ /**
+ * Default format of a month cell
+ */
+ public static final String DEFAULT_FORMAT = "yyyy-MM";
+
+ @Override
+ public Class supportJavaTypeKey() {
+ return YearMonth.class;
+ }
+
+ @Override
+ public WriteCellData> convertToExcelData(
+ YearMonth value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration)
+ throws Exception {
+ WriteCellData> cellData =
+ new WriteCellData<>(value == null ? null : value.atDay(1).atStartOfDay());
+ String format = null;
+ if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) {
+ format = contentProperty.getDateTimeFormatProperty().getFormat();
+ }
+ WorkBookUtil.fillDataFormat(cellData, format == null || format.isEmpty() ? null : format, DEFAULT_FORMAT);
+ return cellData;
+ }
+}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthNumberConverter.java
new file mode 100644
index 000000000..4391f7994
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthNumberConverter.java
@@ -0,0 +1,66 @@
+/*
+ * 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.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.YearMonth;
+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;
+
+/**
+ * YearMonth and number 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 {
+
+ @Override
+ public Class> supportJavaTypeKey() {
+ return YearMonth.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return CellDataTypeEnum.NUMBER;
+ }
+
+ @Override
+ public YearMonth convertToJavaData(
+ ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ LocalDate localDate = DateUtils.getLocalDate(
+ cellData.getNumberValue().doubleValue(), DateUtils.isDate1904(contentProperty, globalConfiguration));
+ return YearMonth.from(localDate);
+ }
+
+ @Override
+ public WriteCellData> convertToExcelData(
+ YearMonth value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ return new WriteCellData<>(BigDecimal.valueOf(DateUtil.getExcelDate(
+ value.atDay(1).atStartOfDay(), DateUtils.isDate1904(contentProperty, globalConfiguration))));
+ }
+}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthStringConverter.java
new file mode 100644
index 000000000..ed55b1dd2
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/yearmonth/YearMonthStringConverter.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import org.apache.fesod.common.util.StringUtils;
+import org.apache.fesod.sheet.converters.Converter;
+import org.apache.fesod.sheet.enums.CellDataTypeEnum;
+import org.apache.fesod.sheet.metadata.GlobalConfiguration;
+import org.apache.fesod.sheet.metadata.data.ReadCellData;
+import org.apache.fesod.sheet.metadata.data.WriteCellData;
+import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
+
+/**
+ * YearMonth and string converter
+ *
+ */
+public class YearMonthStringConverter implements Converter {
+
+ @Override
+ public Class> supportJavaTypeKey() {
+ return YearMonth.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return CellDataTypeEnum.STRING;
+ }
+
+ @Override
+ public YearMonth convertToJavaData(
+ ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ return YearMonth.parse(cellData.getStringValue(), formatter(contentProperty, globalConfiguration));
+ }
+
+ @Override
+ public WriteCellData> convertToExcelData(
+ YearMonth value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ return new WriteCellData<>(value.format(formatter(contentProperty, globalConfiguration)));
+ }
+
+ /**
+ * The formatter of the field, {@link YearMonthDateConverter#DEFAULT_FORMAT} when no format is set.
+ *
+ * {@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 listener = new CollectingReadListener<>();
+ FesodSheet.read(file, YearMonthData.class, listener).sheet().doRead();
+
+ Assertions.assertEquals(1, listener.getRowCount());
+ YearMonthData row = listener.getFirstRow();
+ Assertions.assertEquals(YearMonth.of(2020, 1), row.getMonth());
+ Assertions.assertEquals(YearMonth.of(2020, 1), row.getMonthString());
+ Assertions.assertEquals(YearMonth.of(2021, 12), row.getMonthFormattedString());
+ }
+
+ /**
+ * {@code @DateTimeFormat} defaults to an empty value and an empty pattern is not a usable one, so an empty format
+ * has to behave like a missing one.
+ */
+ @ParameterizedTest
+ @ExcelFormatSource
+ void emptyDateTimeFormat(ExcelFormat format) throws Exception {
+ File file = createTempFile(format);
+ FesodSheet.write(file, YearMonthData.class).sheet().doWrite(data());
+
+ CollectingReadListener listener = new CollectingReadListener<>();
+ FesodSheet.read(file, YearMonthData.class, listener).sheet().doRead();
+
+ Assertions.assertEquals(1, listener.getRowCount());
+ Assertions.assertEquals(YearMonth.of(2020, 1), listener.getFirstRow().getMonthEmptyFormat());
+ }
+
+ /**
+ * The locale of the read or write decides how a month name is written and parsed. The cells of a csv file are
+ * always strings, so the written text can be checked there as well.
+ */
+ @ParameterizedTest
+ @ExcelFormatSource
+ void localeOfTheConfigurationIsUsed(ExcelFormat format) throws Exception {
+ File file = createTempFile(format);
+ FesodSheet.write(file, YearMonthData.class)
+ .locale(Locale.FRENCH)
+ .sheet()
+ .doWrite(data());
+
+ if (format.toExcelTypeEnum() == ExcelTypeEnum.CSV) {
+ List