diff --git a/README.md b/README.md index 7fef9d91a..25bd3be33 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 3184d0875..035b984c4 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.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 @@ private static DateFormat getCacheDateFormat(String dateFormat) { * @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 1229b2382..fa7a517ba 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 @@ 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 @@ @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 void t02Read03() { 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 000000000..8b9c7cbb5 Binary files /dev/null and b/easyexcel-test/src/test/resources/dataformat/dataformatv2.xlsx differ diff --git a/pom.xml b/pom.xml index a9734c4da..ab227234b 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 829bcb8ac..c31e8bdf5 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,7 @@ +# 4.0.3 + +* 兼容部分日期格式读取异常的问题 + # 4.0.2 * 兼容某些特殊的xls: 修改了内置的样式导致判断样式错误