diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateDateConverter.java index 8304cc515..f10303bf2 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateDateConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateDateConverter.java @@ -49,11 +49,7 @@ public WriteCellData> convertToExcelData( Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception { WriteCellData> cellData = new WriteCellData<>(value); - String format = null; - if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { - format = contentProperty.getDateTimeFormatProperty().getFormat(); - } - WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultDateFormat); + WorkBookUtil.fillDataFormat(cellData, DateUtils.resolveDateTimeFormat(contentProperty, globalConfiguration)); return cellData; } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateStringConverter.java index 528050572..57b6111e5 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateStringConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/date/DateStringConverter.java @@ -67,11 +67,7 @@ public Date convertToJavaData( @Override public WriteCellData> convertToExcelData( Date value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { - return new WriteCellData<>(DateUtils.format(value, null)); - } else { - return new WriteCellData<>(DateUtils.format( - value, contentProperty.getDateTimeFormatProperty().getFormat())); - } + return new WriteCellData<>( + DateUtils.format(value, DateUtils.resolveDateTimeFormat(contentProperty, globalConfiguration))); } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateDateConverter.java index 902fd33f4..3e8204c6c 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateDateConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateDateConverter.java @@ -51,11 +51,7 @@ public WriteCellData> convertToExcelData( throws Exception { LocalDateTime localDateTime = value == null ? null : value.atTime(0, 0); WriteCellData> cellData = new WriteCellData<>(localDateTime); - String format = null; - if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { - format = contentProperty.getDateTimeFormatProperty().getFormat(); - } - WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultLocalDateFormat); + WorkBookUtil.fillDataFormat(cellData, DateUtils.resolveLocalDateFormat(contentProperty, globalConfiguration)); return cellData; } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateStringConverter.java index 21bff20ca..89a1359be 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateStringConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdate/LocalDateStringConverter.java @@ -68,11 +68,9 @@ public LocalDate convertToJavaData( @Override public WriteCellData> convertToExcelData( LocalDate 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())); - } + return new WriteCellData<>(DateUtils.format( + value, + DateUtils.resolveLocalDateFormat(contentProperty, globalConfiguration), + globalConfiguration.getLocale())); } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeDateConverter.java index 0f0353044..0afc02e34 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeDateConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeDateConverter.java @@ -49,11 +49,7 @@ public WriteCellData> convertToExcelData( LocalDateTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception { WriteCellData> cellData = new WriteCellData<>(value); - String format = null; - if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { - format = contentProperty.getDateTimeFormatProperty().getFormat(); - } - WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultDateFormat); + WorkBookUtil.fillDataFormat(cellData, DateUtils.resolveDateTimeFormat(contentProperty, globalConfiguration)); return cellData; } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeStringConverter.java index d7ab2cab7..6f025397c 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeStringConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/localdatetime/LocalDateTimeStringConverter.java @@ -68,11 +68,9 @@ public LocalDateTime convertToJavaData( @Override public WriteCellData> convertToExcelData( LocalDateTime 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())); - } + return new WriteCellData<>(DateUtils.format( + value, + DateUtils.resolveDateTimeFormat(contentProperty, globalConfiguration), + globalConfiguration.getLocale())); } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsetdatetime/OffsetDateTimeDateConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsetdatetime/OffsetDateTimeDateConverter.java index 940f38162..6d6906722 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsetdatetime/OffsetDateTimeDateConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/offsetdatetime/OffsetDateTimeDateConverter.java @@ -21,7 +21,6 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; -import org.apache.fesod.common.util.StringUtils; import org.apache.fesod.sheet.converters.Converter; import org.apache.fesod.sheet.metadata.GlobalConfiguration; import org.apache.fesod.sheet.metadata.data.WriteCellData; @@ -42,14 +41,7 @@ public WriteCellData> convertToExcelData( throws Exception { LocalDateTime localDateTime = value.toLocalDateTime(); WriteCellData> cellData = new WriteCellData<>(localDateTime); - String format = null; - if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) { - format = contentProperty.getDateTimeFormatProperty().getFormat(); - if (StringUtils.isEmpty(format)) { - format = null; - } - } - WorkBookUtil.fillDataFormat(cellData, format, DateUtils.defaultDateFormat); + WorkBookUtil.fillDataFormat(cellData, DateUtils.resolveDateTimeFormat(contentProperty, globalConfiguration)); return cellData; } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/string/StringNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/string/StringNumberConverter.java index 94d72cfbf..9e84d5bb8 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/string/StringNumberConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/string/StringNumberConverter.java @@ -62,7 +62,7 @@ public String convertToJavaData( return DateUtils.format( cellData.getNumberValue(), DateUtils.isDate1904(contentProperty, globalConfiguration), - contentProperty.getDateTimeFormatProperty().getFormat()); + DateUtils.resolveDateTimeFormat(contentProperty, globalConfiguration)); } // If there are "NumberFormat", read as number if (contentProperty != null && contentProperty.getNumberFormatProperty() != null) { diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/year/YearStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/year/YearStringConverter.java index 4dd2f07e7..8f55a4067 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/year/YearStringConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/year/YearStringConverter.java @@ -46,20 +46,17 @@ public CellDataTypeEnum supportExcelTypeKey() { public Year convertToJavaData( ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return DateUtils.parseYear( - cellData.getStringValue(), getYearFormat(contentProperty), globalConfiguration.getLocale()); + cellData.getStringValue(), + DateUtils.resolveYearFormat(contentProperty, globalConfiguration), + globalConfiguration.getLocale()); } @Override public WriteCellData> convertToExcelData( Year value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return new WriteCellData<>( - DateUtils.format(value, getYearFormat(contentProperty), globalConfiguration.getLocale())); - } - - private static String getYearFormat(ExcelContentProperty contentProperty) { - if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { - return null; - } - return contentProperty.getDateTimeFormatProperty().getFormat(); + return new WriteCellData<>(DateUtils.format( + value, + DateUtils.resolveYearFormat(contentProperty, globalConfiguration), + globalConfiguration.getLocale())); } } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractHolder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractHolder.java index 35b63aff4..cf1087974 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractHolder.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractHolder.java @@ -31,8 +31,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.apache.fesod.common.util.StringUtils; import org.apache.fesod.sheet.converters.Converter; import org.apache.fesod.sheet.converters.ConverterKeyBuild; +import org.apache.fesod.sheet.util.DateUtils; /** * Write/read holder @@ -125,6 +127,37 @@ public AbstractHolder(BasicParameter basicParameter, AbstractHolder prentAbstrac } else { globalConfiguration.setFiledCacheLocation(basicParameter.getFiledCacheLocation()); } + + // Date patterns resolve eagerly: a workbook snapshots the process-wide default once so every cell in it uses + // one consistent value, while sheet/table holders inherit the workbook's snapshot. A blank value is treated as + // "not set" here rather than stored as-is: the resolvers read an empty pattern as a fallback signal, so + // keeping it in the snapshot would let a later global change leak into an in-flight workbook. + if (StringUtils.isBlank(basicParameter.getDefaultDateFormat())) { + globalConfiguration.setDefaultDateFormat( + prentAbstractHolder != null + ? prentAbstractHolder.getGlobalConfiguration().getDefaultDateFormat() + : DateUtils.getDefaultDateFormat()); + } else { + globalConfiguration.setDefaultDateFormat(basicParameter.getDefaultDateFormat()); + } + + if (StringUtils.isBlank(basicParameter.getDefaultLocalDateFormat())) { + globalConfiguration.setDefaultLocalDateFormat( + prentAbstractHolder != null + ? prentAbstractHolder.getGlobalConfiguration().getDefaultLocalDateFormat() + : DateUtils.getDefaultLocalDateFormat()); + } else { + globalConfiguration.setDefaultLocalDateFormat(basicParameter.getDefaultLocalDateFormat()); + } + + if (StringUtils.isBlank(basicParameter.getDefaultYearFormat())) { + globalConfiguration.setDefaultYearFormat( + prentAbstractHolder != null + ? prentAbstractHolder.getGlobalConfiguration().getDefaultYearFormat() + : DateUtils.getDefaultYearFormat()); + } else { + globalConfiguration.setDefaultYearFormat(basicParameter.getDefaultYearFormat()); + } } @Override diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java index de25c3c08..764ca0a82 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java @@ -31,6 +31,7 @@ import java.util.Objects; import java.util.function.Consumer; import org.apache.fesod.common.util.ListUtils; +import org.apache.fesod.common.util.StringUtils; import org.apache.fesod.sheet.converters.Converter; import org.apache.fesod.sheet.enums.CacheLocationEnum; @@ -184,6 +185,72 @@ public T autoStrip(Boolean autoStrip) { return self(); } + /** + * Default pattern for {@code Date}, {@code LocalDateTime} and {@code OffsetDateTime} fields without a + * {@code @DateTimeFormat} pattern. + *
+ * default is the process-wide default ({@link org.apache.fesod.sheet.util.DateUtils#getDefaultDateFormat()}), + * {@code yyyy-MM-dd HH:mm:ss} unless changed. + *
+ * Passing {@code null} leaves the pattern unset, so the read/write context inherits the process-wide default. An + * empty or whitespace-only pattern is rejected rather than silently treated as "not set". + * + * @param defaultDateFormat the pattern, or {@code null} to inherit the process-wide default + * @return + * @throws IllegalArgumentException if the pattern is empty or whitespace-only + */ + public T defaultDateFormat(String defaultDateFormat) { + parameter().setDefaultDateFormat(requireNonBlankPattern(defaultDateFormat, "defaultDateFormat")); + return self(); + } + + /** + * Default pattern for {@code LocalDate} fields without a {@code @DateTimeFormat} pattern. + *
+ * default is the process-wide default ({@link org.apache.fesod.sheet.util.DateUtils#getDefaultLocalDateFormat()}), + * {@code yyyy-MM-dd} unless changed. + *
+ * Passing {@code null} leaves the pattern unset, so the read/write context inherits the process-wide default. An + * empty or whitespace-only pattern is rejected rather than silently treated as "not set". + * + * @param defaultLocalDateFormat the pattern, or {@code null} to inherit the process-wide default + * @return + * @throws IllegalArgumentException if the pattern is empty or whitespace-only + */ + public T defaultLocalDateFormat(String defaultLocalDateFormat) { + parameter().setDefaultLocalDateFormat(requireNonBlankPattern(defaultLocalDateFormat, "defaultLocalDateFormat")); + return self(); + } + + /** + * Default pattern for {@code Year} fields without a {@code @DateTimeFormat} pattern. + *
+ * default is the process-wide default ({@link org.apache.fesod.sheet.util.DateUtils#getDefaultYearFormat()}), + * {@code yyyy} unless changed. + *
+ * Passing {@code null} leaves the pattern unset, so the read/write context inherits the process-wide default. An + * empty or whitespace-only pattern is rejected rather than silently treated as "not set". + * + * @param defaultYearFormat the pattern, or {@code null} to inherit the process-wide default + * @return + * @throws IllegalArgumentException if the pattern is empty or whitespace-only + */ + public T defaultYearFormat(String defaultYearFormat) { + parameter().setDefaultYearFormat(requireNonBlankPattern(defaultYearFormat, "defaultYearFormat")); + return self(); + } + + /** + * Rejects an empty or whitespace-only pattern while keeping {@code null} as "not set", which is how the sibling + * builder parameters behave: the holder then falls back to the parent or process-wide default. + */ + private static String requireNonBlankPattern(String format, String name) { + if (format != null && StringUtils.isBlank(format)) { + throw new IllegalArgumentException(name + " can not be blank"); + } + return format; + } + @SuppressWarnings("unchecked") protected T self() { return (T) this; diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/BasicParameter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/BasicParameter.java index 19a2bece0..6d3c22985 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/BasicParameter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/BasicParameter.java @@ -89,4 +89,21 @@ public class BasicParameter { * Automatic strip includes sheet name and content */ private Boolean autoStrip; + /** + * Default pattern for {@code Date}, {@code LocalDateTime} and {@code OffsetDateTime} fields without a + * {@code @DateTimeFormat} pattern; {@code null} falls back to the process-wide default + * ({@link org.apache.fesod.sheet.util.DateUtils#setDefaultDateFormat(String)}). + */ + private String defaultDateFormat; + /** + * Default pattern for {@code LocalDate} fields without a {@code @DateTimeFormat} pattern; {@code null} falls back + * to the process-wide default + * ({@link org.apache.fesod.sheet.util.DateUtils#setDefaultLocalDateFormat(String)}). + */ + private String defaultLocalDateFormat; + /** + * Default pattern for {@code Year} fields without a {@code @DateTimeFormat} pattern; {@code null} falls back to + * the process-wide default ({@link org.apache.fesod.sheet.util.DateUtils#setDefaultYearFormat(String)}). + */ + private String defaultYearFormat; } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/GlobalConfiguration.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/GlobalConfiguration.java index 6a894dc57..e8bb0b4ac 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/GlobalConfiguration.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/GlobalConfiguration.java @@ -77,6 +77,28 @@ public class GlobalConfiguration { */ private Boolean autoStrip; + /** + * Per-call default pattern for {@code Date}, {@code LocalDateTime} and {@code OffsetDateTime} fields without a + * {@code @DateTimeFormat} pattern; {@code null} falls back to the process-wide default + * ({@link org.apache.fesod.sheet.util.DateUtils#getDefaultDateFormat()}, snapshotted when the context is created). + */ + private String defaultDateFormat; + + /** + * Per-call default pattern for {@code LocalDate} fields without a {@code @DateTimeFormat} pattern; {@code null} + * falls back to the process-wide default + * ({@link org.apache.fesod.sheet.util.DateUtils#getDefaultLocalDateFormat()}, snapshotted when the context is + * created). + */ + private String defaultLocalDateFormat; + + /** + * Per-call default pattern for {@code Year} fields without a {@code @DateTimeFormat} pattern; {@code null} falls + * back to the process-wide default + * ({@link org.apache.fesod.sheet.util.DateUtils#getDefaultYearFormat()}, snapshotted when the context is created). + */ + private String defaultYearFormat; + public GlobalConfiguration() { this.autoTrim = Boolean.TRUE; this.autoStrip = Boolean.FALSE; diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvSheet.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvSheet.java index af9c2f6c7..626e89253 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvSheet.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvSheet.java @@ -709,7 +709,10 @@ private String buildCellValue(CsvCell csvCell) { } // date if (dataFormat == null) { - dataFormatString = DateUtils.defaultDateFormat; + String defaultDateFormat = csvWorkbook.getDefaultDateFormat(); + dataFormatString = !StringUtils.isEmpty(defaultDateFormat) + ? defaultDateFormat + : DateUtils.getDefaultDateFormat(); dataFormat = csvWorkbook.createDataFormat().getFormat(dataFormatString); } if (dataFormatString == null) { diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvWorkbook.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvWorkbook.java index 628767696..014b29cd4 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvWorkbook.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/metadata/csv/CsvWorkbook.java @@ -78,6 +78,12 @@ public class CsvWorkbook implements Workbook { */ private Locale locale; + /** + * Per-call default pattern for date values without an explicit cell style format; {@code null} falls back to the + * process-wide default ({@link org.apache.fesod.sheet.util.DateUtils#getDefaultDateFormat()}). + */ + private String defaultDateFormat; + /** * Whether to use scientific Format. *
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 062d890ac..49b4046eb 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
@@ -90,13 +90,45 @@ public class DateUtils {
public static final String TIME_FORMAT_8 = "HH:mm:ss";
private static final String MINUS = "-";
- public static String defaultDateFormat = DATE_FORMAT_19;
+ /**
+ * Built-in default pattern for {@code java.time.Year} values ({@value}).
+ */
+ public static final String DEFAULT_YEAR_FORMAT = "yyyy";
- public static String defaultLocalDateFormat = DATE_FORMAT_10;
+ /**
+ * Built-in default pattern for time-only values ({@value}).
+ */
+ public static final String DEFAULT_LOCAL_TIME_FORMAT = TIME_FORMAT_8;
- public static String defaultYearFormat = "yyyy";
+ /**
+ * Process-wide default pattern for {@code Date}, {@code LocalDateTime} and {@code OffsetDateTime} values when no
+ * explicit pattern applies. Each read/write context snapshots this value at creation time, so a change only
+ * affects contexts created afterwards.
+ *
+ * @deprecated direct field writes make an in-flight workbook nondeterministically mixed between the old and the
+ * new value. Use {@link #setDefaultDateFormat(String)} to change the process-wide default, the
+ * per-call {@code defaultDateFormat(...)} builder parameter, or a {@code @DateTimeFormat} field
+ * annotation. This field will be removed in the next major version.
+ */
+ @Deprecated
+ public static volatile String defaultDateFormat = DATE_FORMAT_19;
- public static final String DEFAULT_LOCAL_TIME_FORMAT = TIME_FORMAT_8;
+ /**
+ * Process-wide default pattern for {@code LocalDate} values when no explicit pattern applies; snapshot semantics
+ * as described on {@link #defaultDateFormat}.
+ *
+ * @deprecated use {@link #setDefaultLocalDateFormat(String)}, the per-call {@code defaultLocalDateFormat(...)}
+ * builder parameter, or a {@code @DateTimeFormat} field annotation. This field will be removed in
+ * the next major version.
+ */
+ @Deprecated
+ public static volatile String defaultLocalDateFormat = DATE_FORMAT_10;
+
+ /**
+ * Process-wide default pattern for {@code Year} values when no explicit pattern applies; snapshot semantics as
+ * described on {@link #defaultDateFormat}.
+ */
+ private static volatile String defaultYearFormat = DEFAULT_YEAR_FORMAT;
public static final int SECONDS_PER_MINUTE = 60;
public static final int MINUTES_PER_HOUR = 60;
@@ -109,6 +141,78 @@ public class DateUtils {
private DateUtils() {}
+ /**
+ * Returns the process-wide default pattern for {@code Date}, {@code LocalDateTime} and {@code OffsetDateTime}
+ * values.
+ */
+ public static String getDefaultDateFormat() {
+ return defaultDateFormat;
+ }
+
+ /**
+ * Sets the process-wide default pattern for {@code Date}, {@code LocalDateTime} and {@code OffsetDateTime}
+ * values. The new value applies to every read/write context created afterwards; an already running context keeps
+ * the pattern it snapshotted at creation.
+ *
+ * @param format the pattern, must not be blank
+ * @throws IllegalArgumentException if the pattern is blank
+ */
+ public static void setDefaultDateFormat(String format) {
+ defaultDateFormat = requireNonBlank(format, "default date format");
+ }
+
+ /**
+ * Returns the process-wide default pattern for {@code LocalDate} values.
+ */
+ public static String getDefaultLocalDateFormat() {
+ return defaultLocalDateFormat;
+ }
+
+ /**
+ * Sets the process-wide default pattern for {@code LocalDate} values; snapshot semantics as described on
+ * {@link #setDefaultDateFormat(String)}.
+ *
+ * @param format the pattern, must not be blank
+ * @throws IllegalArgumentException if the pattern is blank
+ */
+ public static void setDefaultLocalDateFormat(String format) {
+ defaultLocalDateFormat = requireNonBlank(format, "default local date format");
+ }
+
+ /**
+ * Returns the process-wide default pattern for {@code Year} values.
+ */
+ public static String getDefaultYearFormat() {
+ return defaultYearFormat;
+ }
+
+ /**
+ * Sets the process-wide default pattern for {@code Year} values; snapshot semantics as described on
+ * {@link #setDefaultDateFormat(String)}.
+ *
+ * @param format the pattern, must not be blank
+ * @throws IllegalArgumentException if the pattern is blank
+ */
+ public static void setDefaultYearFormat(String format) {
+ defaultYearFormat = requireNonBlank(format, "default year format");
+ }
+
+ /**
+ * Restores every process-wide default date pattern to its built-in value.
+ */
+ public static void resetDefaultDateFormats() {
+ defaultDateFormat = DATE_FORMAT_19;
+ defaultLocalDateFormat = DATE_FORMAT_10;
+ defaultYearFormat = DEFAULT_YEAR_FORMAT;
+ }
+
+ private static String requireNonBlank(String format, String name) {
+ if (StringUtils.isBlank(format)) {
+ throw new IllegalArgumentException(name + " can not be blank");
+ }
+ return format;
+ }
+
/**
* convert string to date
*
@@ -450,6 +554,81 @@ public static boolean isDate1904(ExcelContentProperty contentProperty, GlobalCon
return globalUse1904windowing != null && globalUse1904windowing;
}
+ /**
+ * Resolves the pattern for a datetime-typed value ({@code Date}, {@code LocalDateTime}, {@code OffsetDateTime}):
+ * a non-empty {@code @DateTimeFormat} pattern wins, otherwise the per-call configuration, otherwise the
+ * process-wide default. Types whose string form has its own canonical representation (for example ISO-based
+ * formats) should keep that representation instead of routing through this helper.
+ *
+ * @param contentProperty the field's content property, may be null
+ * @param globalConfiguration the per-call configuration, may be null
+ * @return the resolved pattern, never null or empty
+ */
+ public static String resolveDateTimeFormat(
+ ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ String format = annotationFormat(contentProperty);
+ if (format != null) {
+ return format;
+ }
+ if (globalConfiguration != null && !StringUtils.isEmpty(globalConfiguration.getDefaultDateFormat())) {
+ return globalConfiguration.getDefaultDateFormat();
+ }
+ return getDefaultDateFormat();
+ }
+
+ /**
+ * Resolves the pattern for a {@code LocalDate} value; precedence as described on
+ * {@link #resolveDateTimeFormat(ExcelContentProperty, GlobalConfiguration)}.
+ *
+ * @param contentProperty the field's content property, may be null
+ * @param globalConfiguration the per-call configuration, may be null
+ * @return the resolved pattern, never null or empty
+ */
+ public static String resolveLocalDateFormat(
+ ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ String format = annotationFormat(contentProperty);
+ if (format != null) {
+ return format;
+ }
+ if (globalConfiguration != null && !StringUtils.isEmpty(globalConfiguration.getDefaultLocalDateFormat())) {
+ return globalConfiguration.getDefaultLocalDateFormat();
+ }
+ return getDefaultLocalDateFormat();
+ }
+
+ /**
+ * Resolves the pattern for a {@code Year} value; precedence as described on
+ * {@link #resolveDateTimeFormat(ExcelContentProperty, GlobalConfiguration)}.
+ *
+ * @param contentProperty the field's content property, may be null
+ * @param globalConfiguration the per-call configuration, may be null
+ * @return the resolved pattern, never null or empty
+ */
+ public static String resolveYearFormat(
+ ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ String format = annotationFormat(contentProperty);
+ if (format != null) {
+ return format;
+ }
+ if (globalConfiguration != null && !StringUtils.isEmpty(globalConfiguration.getDefaultYearFormat())) {
+ return globalConfiguration.getDefaultYearFormat();
+ }
+ return getDefaultYearFormat();
+ }
+
+ /**
+ * Extracts the {@code @DateTimeFormat} pattern of a field, treating null and empty as "not set".
+ */
+ private static String annotationFormat(ExcelContentProperty contentProperty) {
+ if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) {
+ String format = contentProperty.getDateTimeFormatProperty().getFormat();
+ if (!StringUtils.isEmpty(format)) {
+ return format;
+ }
+ }
+ return null;
+ }
+
private static DateTimeFormatter getCacheDateTimeFormat(String dateFormat, Locale locale) {
Locale actualLocale = locale == null ? Locale.getDefault(Locale.Category.FORMAT) : locale;
Map
THREAD_LOCAL: Default, the cache will be cleared each time, but not the same time.
MEMORY: Placed in global memory, theoretically better performance, but cannot modify the exported object through reflection, exclusion, etc.
NONE: No caching, performance will degrade, consider using it when reading and writing at the same time, and you need to reflectively exclude, modify objects, etc. |
diff --git a/website/docs/sheet/read/format.md b/website/docs/sheet/read/format.md
index 83132907f..417775fde 100644
--- a/website/docs/sheet/read/format.md
+++ b/website/docs/sheet/read/format.md
@@ -98,3 +98,14 @@ Converts number cells to formatted strings when the field type is `String`.
|:---|:---|:---|
| `value` | Empty | Number format pattern, refer to `java.text.DecimalFormat` |
| `roundingMode` | `HALF_UP` | Rounding mode when formatting |
+
+---
+
+## Default Date Formats on Read
+
+Reading rarely needs a default pattern: `String` cells are parsed by length-based pattern detection, and date-formatted numeric cells use the format stored in the file. The default patterns matter on read in two edge cases:
+
+- `Year` fields: a `String` cell is parsed with the resolved `Year` pattern (`@DateTimeFormat` over the per-call `defaultYearFormat` parameter over the process-wide default, `yyyy`).
+- `String` fields with an empty `@DateTimeFormat` reading a numeric date cell: the resolved date pattern renders the value.
+
+The full cascade, the builder parameters and the process-wide setters are documented in [write format - Default Date Formats](../write/format.md#default-date-formats); each read context snapshots the process-wide defaults at creation.
diff --git a/website/docs/sheet/write/format.md b/website/docs/sheet/write/format.md
index eb48f5a60..4bb4ae545 100644
--- a/website/docs/sheet/write/format.md
+++ b/website/docs/sheet/write/format.md
@@ -77,3 +77,60 @@ public void converterWrite() {
+
+## Default Date Formats
+
+### Overview
+
+When a date-typed field carries no `@DateTimeFormat` pattern, Fesod resolves its pattern through a three-tier cascade:
+
+| Priority | Source | Scope |
+| --- | --- | --- |
+| 1 | `@DateTimeFormat("...")` | per field |
+| 2 | `.defaultDateFormat(...)` builder parameter | per read/write call |
+| 3 | `DateUtils.setDefaultDateFormat(...)` | process-wide |
+| — | built-in constant | `yyyy-MM-dd HH:mm:ss` |
+
+Three builder parameters cover the date types:
+
+| Builder parameter | Applies to | Built-in constant |
+| --- | --- | --- |
+| `defaultDateFormat` | `Date`, `LocalDateTime`, `OffsetDateTime` | `yyyy-MM-dd HH:mm:ss` |
+| `defaultLocalDateFormat` | `LocalDate` | `yyyy-MM-dd` |
+| `defaultYearFormat` | `Year` | `yyyy` |
+
+### Per-Call Default
+
+```java
+FesodSheet.write(fileName, OrderData.class)
+ .defaultDateFormat("yyyy/MM/dd HH:mm:ss")
+ .defaultLocalDateFormat("yyyy/MM/dd")
+ .sheet()
+ .doWrite(data());
+```
+
+The parameters are also available on the read side (`FesodSheet.read(...)`), which mainly affects `Year` parsing when no pattern is annotated.
+
+### Process-Wide Default
+
+Applications that want one default everywhere can set it once at startup instead of annotating every field or repeating the builder parameter:
+
+```java
+DateUtils.setDefaultDateFormat("yyyy/MM/dd HH:mm:ss");
+DateUtils.setDefaultLocalDateFormat("yyyy/MM/dd");
+DateUtils.setDefaultYearFormat("yyyy");
+```
+
+The setters reject blank patterns and `DateUtils.resetDefaultDateFormats()` restores the built-in values.
+
+### Snapshot Semantics
+
+Each read/write context snapshots the process-wide default when it is created, so every cell of one workbook is written with one consistent pattern. Changing the process-wide default mid-run only affects contexts created afterwards — it never leaves an in-flight workbook half-formatted with the old value and half with the new one.
+
+:::note Migration from `DateUtils.defaultDateFormat` field writes
+The public static fields `DateUtils.defaultDateFormat` and `DateUtils.defaultLocalDateFormat` (inherited from the EasyExcel lineage, never documented) are deprecated in favor of the setters above; they still work and are now `volatile`, but direct field writes make an in-flight workbook nondeterministically mixed. `DateUtils.defaultYearFormat` never shipped in a release and was replaced by the setter outright. The fields will be removed in the next major version.
+:::
+
+:::info For converter contributors
+New converters that need a default pattern should take a `GlobalConfiguration` field plus a `DateUtils.resolve*Format(contentProperty, globalConfiguration)` call instead of introducing new public static mutable state on `DateUtils`. Types whose string form has a canonical representation (for example ISO-based formats) may keep that representation instead of joining the cascade.
+:::
diff --git a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/parameter.md b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/parameter.md
index 83a05e5a0..9ecba595c 100644
--- a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/parameter.md
+++ b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/parameter.md
@@ -126,7 +126,10 @@ WriteWorkbook --|> WriteBasicParameter
| clazz | 空 | 与 `head` 二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据 |
| customConverterList | 空 | 默认加载了很多转换器,这里可以加入不支持的字段 |
| autoTrim | true | 会对表头、读取数据等进行自动去除前后空格 |
-| use1904windowing | false | 电子表格中时间是存储 1900 年起的一个双精度浮点数,但是有时候默认开始日期是 1904,所以设置这个值改成默认 1904 年开始 |
+| use1904windowing | false | 电子表格中时间是存储 1900 年起的一个双精度浮点数,但是有时候默认开始日期是 1904,所以设置这个值改成默认 1904 年开始 |
+| defaultDateFormat | yyyy-MM-dd HH:mm:ss | `Date`、`LocalDateTime`、`OffsetDateTime` 字段没有 `@DateTimeFormat` 格式时的默认格式。未设置时回退到进程级默认值(`DateUtils.setDefaultDateFormat`)。见[写侧格式文档 - 默认日期格式](../write/format.md#默认日期格式) |
+| defaultLocalDateFormat | yyyy-MM-dd | `LocalDate` 字段没有 `@DateTimeFormat` 格式时的默认格式。未设置时回退到进程级默认值(`DateUtils.setDefaultLocalDateFormat`) |
+| defaultYearFormat| yyyy| `Year` 字段没有 `@DateTimeFormat` 格式时的默认格式。未设置时回退到进程级默认值(`DateUtils.setDefaultYearFormat`) |
| useScientificFormat | false | 数字转文本的时候在较大的数值的是否是否采用科学计数法 |
| locale | 空 | 此参数用于格式化日期和数字 |
| filedCacheLocation | THREAD_LOCAL | 解析 class 的 field 会有缓存,默认放到 ThreadLocal ,也就是说每次读写都会重新解析 class,可以反射修改 class 的注解,并发场景不会相互影响
THREAD_LOCAL:默认,每次读写都会缓存,但是不是同一次不会影响
MEMORY:放到全局的内存里面,理论上性能会更好,但是无法通过反射、排除等方法修改导出的对象
NONE:不缓存,性能会变差,涉及到读的同时要写,而且还要反射、排除等方法去修改对象的情况下可以考虑使用。 |
diff --git a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/read/format.md b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/read/format.md
index 53c73d4ef..1d071bb2f 100644
--- a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/read/format.md
+++ b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/read/format.md
@@ -98,3 +98,14 @@ public void converterRead() {
|:---|:---|:---|
| `value` | 空 | 数字格式模式,参考 `java.text.DecimalFormat` |
| `roundingMode` | `HALF_UP` | 格式化时的舍入模式 |
+
+---
+
+## 读侧的默认日期格式
+
+读取通常不需要默认格式:`String` 单元格按长度嗅探模式解析,带日期格式的数字单元格使用文件中存储的格式。默认格式在读侧只影响两个边缘场景:
+
+- `Year` 字段:`String` 单元格按解析出的 `Year` 格式解析(`@DateTimeFormat` 优先,其次是按调用的 `defaultYearFormat` 参数,最后是进程级默认值 `yyyy`)。
+- 带空 `@DateTimeFormat` 的 `String` 字段读取数字日期单元格:按解析出的日期格式渲染值。
+
+完整的级联规则、构建器参数与进程级 setter 见[写侧文档 - 默认日期格式](../write/format.md#默认日期格式);每个读上下文在创建时对进程级默认值做快照。
diff --git a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/write/format.md b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/write/format.md
index 5ede970a3..60a72d7e5 100644
--- a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/write/format.md
+++ b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/write/format.md
@@ -77,3 +77,60 @@ public void converterWrite() {
+
+## 默认日期格式
+
+### 概述
+
+当日期类型字段没有 `@DateTimeFormat` 格式时,Fesod 按三层级联解析其格式:
+
+| 优先级 | 来源 | 作用域 |
+| --- | --- | --- |
+| 1 | `@DateTimeFormat("...")` | 单个字段 |
+| 2 | `.defaultDateFormat(...)` 构建器参数 | 单次读/写调用 |
+| 3 | `DateUtils.setDefaultDateFormat(...)` | 进程级 |
+| — | 内置常量 | `yyyy-MM-dd HH:mm:ss` |
+
+三个构建器参数分别对应不同日期类型:
+
+| 构建器参数 | 适用类型 | 内置常量 |
+| --- | --- | --- |
+| `defaultDateFormat` | `Date`、`LocalDateTime`、`OffsetDateTime` | `yyyy-MM-dd HH:mm:ss` |
+| `defaultLocalDateFormat` | `LocalDate` | `yyyy-MM-dd` |
+| `defaultYearFormat` | `Year` | `yyyy` |
+
+### 按调用设置默认值
+
+```java
+FesodSheet.write(fileName, OrderData.class)
+ .defaultDateFormat("yyyy/MM/dd HH:mm:ss")
+ .defaultLocalDateFormat("yyyy/MM/dd")
+ .sheet()
+ .doWrite(data());
+```
+
+该参数在读侧(`FesodSheet.read(...)`)同样可用,主要影响未标注格式时 `Year` 的解析。
+
+### 进程级默认值
+
+希望全局统一默认格式的应用,可以在启动时设置一次,而不必逐字段加注解或逐调用传参数:
+
+```java
+DateUtils.setDefaultDateFormat("yyyy/MM/dd HH:mm:ss");
+DateUtils.setDefaultLocalDateFormat("yyyy/MM/dd");
+DateUtils.setDefaultYearFormat("yyyy");
+```
+
+setter 会拒绝空白格式,`DateUtils.resetDefaultDateFormats()` 可恢复内置值。
+
+### 快照语义
+
+每个读/写上下文在创建时对进程级默认值做一次快照,因此同一个工作簿的所有单元格使用一致的格式。运行中途修改进程级默认值只影响之后新建的上下文,绝不会让正在写入的工作簿出现新旧格式混杂。
+
+:::note 从 `DateUtils.defaultDateFormat` 字段赋值迁移
+继承自 EasyExcel 血统、从未写入文档的公共静态字段 `DateUtils.defaultDateFormat` 与 `DateUtils.defaultLocalDateFormat` 已标记废弃,推荐改用上述 setter;字段仍然有效且现在是 `volatile` 的,但直接写字段会让进行中的工作簿不确定地混杂新旧值。`DateUtils.defaultYearFormat` 尚未随任何版本发布,已直接改为 setter。字段将在下一个大版本移除。
+:::
+
+:::info 转换器贡献者须知
+需要默认格式的新转换器应当使用 `GlobalConfiguration` 字段配合 `DateUtils.resolve*Format(contentProperty, globalConfiguration)`,而不是在 `DateUtils` 上新增公共静态可变状态。字符串形式有规范表示的类型(例如基于 ISO 的格式)可以保留该表示、不接入级联。
+:::