From fab7653cd42256adefad49ce6c2624bed3e0f0f1 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 11 Sep 2024 20:30:11 +0800 Subject: [PATCH] # 4.0.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 兼容部分日期格式读取异常的问题 --- README.md | 2 +- .../com/alibaba/excel/util/DateUtils.java | 101 +++++++++++++++++- .../test/core/dataformat/DateFormatTest.java | 15 +++ .../resources/dataformat/dataformatv2.xlsx | Bin 0 -> 8782 bytes pom.xml | 2 +- update.md | 4 + 6 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 easyexcel-test/src/test/resources/dataformat/dataformatv2.xlsx diff --git a/README.md b/README.md index 7fef9d91..25bd3be3 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析 com.alibaba easyexcel - 4.0.2 + 4.0.3 ``` diff --git a/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java b/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java index 3184d087..035b984c 100644 --- a/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -1,3 +1,20 @@ +/* ==================================================================== + 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 com.alibaba.excel.util; import java.math.BigDecimal; @@ -9,13 +26,16 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import java.util.regex.Pattern; import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.util.LocaleUtil; /** * Date utils @@ -63,6 +83,15 @@ public class DateUtils { public static String defaultLocalDateFormat = DATE_FORMAT_10; + public static final int SECONDS_PER_MINUTE = 60; + public static final int MINUTES_PER_HOUR = 60; + public static final int HOURS_PER_DAY = 24; + public static final int SECONDS_PER_DAY = (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE); + + // used to specify that date is invalid + private static final int BAD_DATE = -1; + public static final long DAY_MILLISECONDS = SECONDS_PER_DAY * 1000L; + private DateUtils() {} /** @@ -301,13 +330,75 @@ public class DateUtils { * @return Java representation of the date, or null if date is not a valid Excel date */ public static Date getJavaDate(double date, boolean use1904windowing) { - //To calculate the Date, in the use of `org.apache.poi.ss.usermodel.DateUtil.getJavaDate(double, boolean, - // java.util.TimeZone, boolean), Date when similar `2023-01-01 00:00:00.500`, returns the`2023-01-01 - // 00:00:01`, but excel in fact shows the `2023-01-01 00:00:00`. - // `org.apache.poi.ss.usermodel.DateUtil.getLocalDateTime(double, boolean, boolean)` There is no problem. - return Date.from(getLocalDateTime(date, use1904windowing).atZone(ZoneId.systemDefault()).toInstant()); + Calendar calendar = getJavaCalendar(date, use1904windowing, null, true); + return calendar == null ? null : calendar.getTime(); } + /** + * Get EXCEL date as Java Calendar with given time zone. + * @param date The Excel date. + * @param use1904windowing true if date uses 1904 windowing, + * or false if using 1900 date windowing. + * @param timeZone The TimeZone to evaluate the date in + * @param roundSeconds round to closest second + * @return Java representation of the date, or null if date is not a valid Excel date + */ + public static Calendar getJavaCalendar(double date, boolean use1904windowing, TimeZone timeZone, boolean roundSeconds) { + if (!isValidExcelDate(date)) { + return null; + } + int wholeDays = (int)Math.floor(date); + int millisecondsInDay = (int)((date - wholeDays) * DAY_MILLISECONDS + 0.5); + Calendar calendar; + if (timeZone != null) { + calendar = LocaleUtil.getLocaleCalendar(timeZone); + } else { + calendar = LocaleUtil.getLocaleCalendar(); // using default time-zone + } + setCalendar(calendar, wholeDays, millisecondsInDay, use1904windowing, roundSeconds); + return calendar; + } + + + public static void setCalendar(Calendar calendar, int wholeDays, + int millisecondsInDay, boolean use1904windowing, boolean roundSeconds) { + int startYear = 1900; + int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't + if (use1904windowing) { + startYear = 1904; + dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day + } + else if (wholeDays < 61) { + // Date is prior to 3/1/1900, so adjust because Excel thinks 2/29/1900 exists + // If Excel date == 2/29/1900, will become 3/1/1900 in Java representation + dayAdjust = 0; + } + calendar.set(startYear, Calendar.JANUARY, wholeDays + dayAdjust, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, millisecondsInDay); + if (calendar.get(Calendar.MILLISECOND) == 0) { + calendar.clear(Calendar.MILLISECOND); + } + if (roundSeconds) { + // This is different from poi where you need to change 500 to 499 + calendar.add(Calendar.MILLISECOND, 499); + calendar.clear(Calendar.MILLISECOND); + } + } + + + /** + * Given a double, checks if it is a valid Excel date. + * + * @return true if valid + * @param value the double value + */ + + public static boolean isValidExcelDate(double value) + { + return (value > -Double.MIN_VALUE); + } + + /** * Given an Excel date with either 1900 or 1904 date windowing, * converts it to a java.time.LocalDateTime. diff --git a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java index 1229b238..fa7a517b 100644 --- a/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java +++ b/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java @@ -3,10 +3,12 @@ package com.alibaba.easyexcel.test.core.dataformat; import java.io.File; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; @@ -22,6 +24,7 @@ import org.junit.jupiter.api.TestMethodOrder; @Slf4j public class DateFormatTest { + private static File file07V2; private static File file07; private static File file03; @@ -29,6 +32,8 @@ public class DateFormatTest { public static void init() { file07 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx"); file03 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xls"); + file07V2 = TestFileUtil.readFile("dataformat" + File.separator + "dataformatv2.xlsx"); + } @Test @@ -43,6 +48,16 @@ public class DateFormatTest { readUs(file03); } + @Test + public void t03Read() { + List> dataMap = EasyExcel.read(file07V2).headRowNumber(0).doReadAllSync(); + log.info("dataMap:{}", JSON.toJSONString(dataMap)); + Assertions.assertEquals("15:00", dataMap.get(0).get(0)); + Assertions.assertEquals("2023-1-01 00:00:00", dataMap.get(1).get(0)); + Assertions.assertEquals("2023-1-01 00:00:00", dataMap.get(2).get(0)); + Assertions.assertEquals("2023-1-01 00:00:01", dataMap.get(3).get(0)); + } + private void readCn(File file) { List list = EasyExcel.read(file, DateFormatData.class, null).locale(Locale.CHINA).sheet().doReadSync(); diff --git a/easyexcel-test/src/test/resources/dataformat/dataformatv2.xlsx b/easyexcel-test/src/test/resources/dataformat/dataformatv2.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8b9c7cbb59edd507fffb216c6fde3cbe282554df GIT binary patch literal 8782 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 BG(i9W literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index a9734c4d..ab227234 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ - 4.0.2 + 4.0.3 UTF-8 1.8 true diff --git a/update.md b/update.md index 829bcb8a..c31e8bdf 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,7 @@ +# 4.0.3 + +* 兼容部分日期格式读取异常的问题 + # 4.0.2 * 兼容某些特殊的xls: 修改了内置的样式导致判断样式错误