Browse Source

新增日期转换

2.1.x
Jiaju Zhuang 5 years ago
parent
commit
9c7e06022b
  1. 5
      pom.xml
  2. 10
      src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java
  3. 7
      src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java
  4. 62
      src/main/java/com/alibaba/excel/constant/BuiltinFormats.java
  5. 12
      src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java
  6. 13
      src/main/java/com/alibaba/excel/metadata/BasicParameter.java
  7. 786
      src/main/java/com/alibaba/excel/metadata/DataFormatter.java
  8. 15
      src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java
  9. 408
      src/main/java/com/alibaba/excel/util/DateUtils.java
  10. 201
      src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java
  11. 14
      src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java
  12. 14
      src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java
  13. 50
      src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java
  14. 63
      src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java
  15. 61
      src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java
  16. BIN
      src/test/resources/dataformat/dataformat.xlsx

5
pom.xml

@ -66,6 +66,11 @@
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>

10
src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java

@ -7,6 +7,7 @@ import org.apache.poi.hssf.record.NumberRecord;
import org.apache.poi.hssf.record.Record;
import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler;
import com.alibaba.excel.constant.BuiltinFormats;
import com.alibaba.excel.metadata.CellData;
/**
@ -32,8 +33,13 @@ public class NumberRecordHandler extends AbstractXlsRecordHandler {
this.row = numrec.getRow();
this.column = numrec.getColumn();
this.cellData = new CellData(BigDecimal.valueOf(numrec.getValue()));
this.cellData.setDataFormat(formatListener.getFormatIndex(numrec));
this.cellData.setDataFormatString(formatListener.getFormatString(numrec));
int dataFormat = formatListener.getFormatIndex(numrec);
this.cellData.setDataFormat(dataFormat);
if (dataFormat <= BuiltinFormats.builtinFormats.length) {
this.cellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat));
} else {
this.cellData.setDataFormatString(formatListener.getFormatString(numrec));
}
}
@Override

7
src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java

@ -13,7 +13,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
@ -21,6 +20,7 @@ import org.xml.sax.Attributes;
import com.alibaba.excel.analysis.v07.XlsxCellHandler;
import com.alibaba.excel.analysis.v07.XlsxRowResultHolder;
import com.alibaba.excel.constant.BuiltinFormats;
import com.alibaba.excel.constant.ExcelXmlConstants;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
@ -87,12 +87,11 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder
int dateFormatIndexInteger = Integer.parseInt(dateFormatIndex);
XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger);
int dataFormat = xssfCellStyle.getDataFormat();
String dataFormatString = xssfCellStyle.getDataFormatString();
currentCellData.setDataFormat(dataFormat);
if (dataFormatString == null) {
if (dataFormat <= BuiltinFormats.builtinFormats.length) {
currentCellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat));
} else {
currentCellData.setDataFormatString(dataFormatString);
currentCellData.setDataFormatString(xssfCellStyle.getDataFormatString());
}
}
}

62
src/main/java/com/alibaba/excel/constant/BuiltinFormats.java

@ -47,7 +47,8 @@ public class BuiltinFormats {
// 13
"# ??/??",
// 14
"m/d/yy",
// The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d".
"yyyy/m/d",
// 15
"d-mmm-yy",
// 16
@ -63,7 +64,8 @@ public class BuiltinFormats {
// 21
"h:mm:ss",
// 22
"m/d/yy h:mm",
// The official documentation shows "m/d/yy h:mm", but the actual test is "yyyy/m/d h:mm".
"yyyy/m/d h:mm",
// 23-26 No specific correspondence found in the official documentation.
// 23
null,
@ -74,25 +76,25 @@ public class BuiltinFormats {
// 26
null,
// 27
"yyyy\"5E74\"m\"6708\"",
"yyyy\"年\"m\"月\"",
// 28
"m\"6708\"d\"65E5\"",
"m\"月\"d\"日\"",
// 29
"m\"6708\"d\"65E5\"",
"m\"月\"d\"日\"",
// 30
"m-d-yy",
// 31
"yyyy\"5E74\"m\"6708\"d\"65E5\"",
"yyyy\"年\"m\"月\"d\"日\"",
// 32
"h\"65F6\"mm\"5206\"",
"h\"时\"mm\"分\"",
// 33
"h\"65F6\"mm\"5206\"ss\"79D2\"",
"h\"时\"mm\"分\"ss\"秒\"",
// 34
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"",
"上午/下午h\"时\"mm\"分\"",
// 35
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"",
"上午/下午h\"时\"mm\"分\"ss\"秒\"",
// 36
"yyyy\"5E74\"m\"6708\"",
"yyyy\"年\"m\"月\"",
// 37
"#,##0_);(#,##0)",
// 38
@ -120,23 +122,23 @@ public class BuiltinFormats {
// 49
"@",
// 50
"yyyy\"5E74\"m\"6708\"",
"yyyy\"年\"m\"月\"",
// 51
"m\"6708\"d\"65E5\"",
"m\"月\"d\"日\"",
// 52
"yyyy\"5E74\"m\"6708\"",
"yyyy\"年\"m\"月\"",
// 53
"m\"6708\"d\"65E5\"",
"m\"月\"d\"日\"",
// 54
"m\"6708\"d\"65E5\"",
"m\"月\"d\"日\"",
// 55
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"",
"上午/下午h\"时\"mm\"分\"",
// 56
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"",
"上午/下午h\"时\"mm\"分\"ss\"秒\"",
// 57
"yyyy\"5E74\"m\"6708\"",
"yyyy\"年\"m\"月\"",
// 58
"m\"6708\"d\"65E5\"",
"m\"月\"d\"日\"",
// 59
"t0",
// 60
@ -163,25 +165,25 @@ public class BuiltinFormats {
// 70
"t# ??/??",
// 71
"0E27/0E14/0E1B0E1B0E1B0E1B",
"ว/ด/ปปปป",
// 72
"0E27-0E140E140E14-0E1B0E1B",
"ว-ดดด-ปป",
// 73
"0E27-0E140E140E14",
"ว-ดดด",
// 74
"0E140E140E14-0E1B0E1B",
"ดดด-ปป",
// 75
"0E0A:0E190E19",
"ช:นน",
// 76
"0E0A:0E190E19:0E170E17",
"ช:นน:ทท",
// 77
"0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19",
"ว/ด/ปปปป ช:นน",
// 78
"0E190E19:0E170E17",
"นน:ทท",
// 79
"[0E0A]:0E190E19:0E170E17",
"[ช]:นน:ทท",
// 80
"0E190E19:0E170E17.0",
"นน:ทท.0",
// 81
"d/m/bb",
// end

12
src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java

@ -10,7 +10,9 @@ import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.DateUtils;
import com.alibaba.excel.util.NumberDataFormatterUtils;
import com.alibaba.excel.util.NumberUtils;
import com.alibaba.excel.util.StringUtils;
/**
* String and number converter
@ -44,13 +46,9 @@ public class StringNumberConverter implements Converter<String> {
return NumberUtils.format(cellData.getNumberValue(), contentProperty);
}
// Excel defines formatting
if (cellData.getDataFormat() != null) {
if (DateUtil.isADateFormat(cellData.getDataFormat(), cellData.getDataFormatString())) {
return DateUtils.format(DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(),
globalConfiguration.getUse1904windowing(), null));
} else {
return NumberUtils.format(cellData.getNumberValue(), contentProperty);
}
if (cellData.getDataFormat() != null && !StringUtils.isEmpty(cellData.getDataFormatString())) {
return NumberDataFormatterUtils.format(cellData.getNumberValue().doubleValue(), cellData.getDataFormat(),
cellData.getDataFormatString(), globalConfiguration);
}
// Default conversion number
return NumberUtils.format(cellData.getNumberValue(), contentProperty);

13
src/main/java/com/alibaba/excel/metadata/BasicParameter.java

@ -1,6 +1,7 @@
package com.alibaba.excel.metadata;
import java.util.List;
import java.util.Locale;
import com.alibaba.excel.converters.Converter;
@ -34,6 +35,11 @@ public class BasicParameter {
* @return
*/
private Boolean use1904windowing;
/**
* A <code>Locale</code> object represents a specific geographical, political, or cultural region. This parameter is
* used when formatting dates and numbers.
*/
private Locale locale;
public List<List<String>> getHead() {
return head;
@ -75,4 +81,11 @@ public class BasicParameter {
this.use1904windowing = use1904windowing;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
}

786
src/main/java/com/alibaba/excel/metadata/DataFormatter.java

@ -0,0 +1,786 @@
/*
* ==================================================================== 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.
*
* 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file The details of changes as svn diff
* can be found in svn at location root/projects/3rd-party/src
* ====================================================================
*/
package com.alibaba.excel.metadata;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormatSymbols;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.ExcelGeneralNumberFormat;
import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter;
import org.apache.poi.ss.usermodel.FractionFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.excel.util.DateUtils;
/**
* Written with reference to {@link org.apache.poi.ss.usermodel.DataFormatter}.Made some optimizations for date
* conversion.
* <p>
* This is a non-thread-safe class.
*
* @author Jiaju Zhuang
*/
public class DataFormatter {
/** For logging any problems we find */
private static final Logger LOGGER = LoggerFactory.getLogger(DataFormatter.class);
private static final String defaultFractionWholePartFormat = "#";
private static final String defaultFractionFractionPartFormat = "#/##";
/** Pattern to find a number format: "0" or "#" */
private static final Pattern numPattern = Pattern.compile("[0#]+");
/** Pattern to find days of week as text "ddd...." */
private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE);
/** Pattern to find "AM/PM" marker */
private static final Pattern amPmPattern = Pattern.compile("(([AP])[M/P]*)|(([上下])[午/下]*)", Pattern.CASE_INSENSITIVE);
/** Pattern to find formats with condition ranges e.g. [>=100] */
private static final Pattern rangeConditionalPattern =
Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*");
/**
* A regex to find locale patterns like [$$-1009] and [$?-452]. Note that we don't currently process these into
* locales
*/
private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+])");
/**
* A regex to match the colour formattings rules. Allowed colours are: Black, Blue, Cyan, Green, Magenta, Red,
* White, Yellow, "Color n" (1<=n<=56)
*/
private static final Pattern colorPattern = Pattern.compile(
"(\\[BLACK])|(\\[BLUE])|(\\[CYAN])|(\\[GREEN])|" + "(\\[MAGENTA])|(\\[RED])|(\\[WHITE])|(\\[YELLOW])|"
+ "(\\[COLOR\\s*\\d])|(\\[COLOR\\s*[0-5]\\d])|(\\[DBNum(1|2|3)])|(\\[\\$-\\d{0,3}])",
Pattern.CASE_INSENSITIVE);
/**
* A regex to identify a fraction pattern. This requires that replaceAll("\\?", "#") has already been called
*/
private static final Pattern fractionPattern = Pattern.compile("(?:([#\\d]+)\\s+)?(#+)\\s*/\\s*([#\\d]+)");
/**
* A regex to strip junk out of fraction formats
*/
private static final Pattern fractionStripper = Pattern.compile("(\"[^\"]*\")|([^ ?#\\d/]+)");
/**
* A regex to detect if an alternate grouping character is used in a numeric format
*/
private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})");
/**
* Cells formatted with a date or time format and which contain invalid date or time values show 255 pound signs
* ("#").
*/
private static final String invalidDateTimeString;
static {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < 255; i++)
buf.append('#');
invalidDateTimeString = buf.toString();
}
/**
* The decimal symbols of the locale used for formatting values.
*/
private DecimalFormatSymbols decimalSymbols;
/**
* The date symbols of the locale used for formatting values.
*/
private DateFormatSymbols dateSymbols;
/** A default format to use when a number pattern cannot be parsed. */
private Format defaultNumFormat;
/**
* A map to cache formats. Map<String,Format> formats
*/
private final Map<String, Format> formats = new HashMap<String, Format>();
/** stores the locale valid it the last formatting call */
private Locale locale;
/**
* true if date uses 1904 windowing, or false if using 1900 date windowing.
*
* default is false
*
* @return
*/
private Boolean use1904windowing;
/**
* Creates a formatter using the {@link Locale#getDefault() default locale}.
*/
public DataFormatter() {
this(null, null);
}
/**
* Creates a formatter using the given locale.
*
*/
public DataFormatter(Locale locale, Boolean use1904windowing) {
this.use1904windowing = use1904windowing != null ? use1904windowing : Boolean.FALSE;
this.locale = locale != null ? locale : Locale.getDefault();
this.locale = Locale.US;
this.dateSymbols = DateFormatSymbols.getInstance(this.locale);
this.decimalSymbols = DecimalFormatSymbols.getInstance(this.locale);
}
private Format getFormat(Integer dataFormat, String dataFormatString) {
// See if we already have it cached
Format format = formats.get(dataFormatString);
if (format != null) {
return format;
}
// Is it one of the special built in types, General or @?
if ("General".equalsIgnoreCase(dataFormatString) || "@".equals(dataFormatString)) {
format = getDefaultFormat();
addFormat(dataFormatString, format);
return format;
}
// Build a formatter, and cache it
format = createFormat(dataFormat, dataFormatString);
addFormat(dataFormatString, format);
return format;
}
private Format createFormat(Integer dataFormat, String dataFormatString) {
String formatStr = dataFormatString;
Format format = checkSpecialConverter(formatStr);
if (format != null) {
return format;
}
// Remove colour formatting if present
Matcher colourM = colorPattern.matcher(formatStr);
while (colourM.find()) {
String colour = colourM.group();
// Paranoid replacement...
int at = formatStr.indexOf(colour);
if (at == -1)
break;
String nFormatStr = formatStr.substring(0, at) + formatStr.substring(at + colour.length());
if (nFormatStr.equals(formatStr))
break;
// Try again in case there's multiple
formatStr = nFormatStr;
colourM = colorPattern.matcher(formatStr);
}
// Strip off the locale information, we use an instance-wide locale for everything
Matcher m = localePatternGroup.matcher(formatStr);
while (m.find()) {
String match = m.group();
String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-'));
if (symbol.indexOf('$') > -1) {
symbol = symbol.substring(0, symbol.indexOf('$')) + '\\' + symbol.substring(symbol.indexOf('$'));
}
formatStr = m.replaceAll(symbol);
m = localePatternGroup.matcher(formatStr);
}
// Check for special cases
if (formatStr == null || formatStr.trim().length() == 0) {
return getDefaultFormat();
}
if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
return getDefaultFormat();
}
if (DateUtils.isADateFormat(dataFormat, formatStr)) {
return createDateFormat(formatStr);
}
// Excel supports fractions in format strings, which Java doesn't
if (formatStr.contains("#/") || formatStr.contains("?/")) {
String[] chunks = formatStr.split(";");
for (String chunk1 : chunks) {
String chunk = chunk1.replaceAll("\\?", "#");
Matcher matcher = fractionStripper.matcher(chunk);
chunk = matcher.replaceAll(" ");
chunk = chunk.replaceAll(" +", " ");
Matcher fractionMatcher = fractionPattern.matcher(chunk);
// take the first match
if (fractionMatcher.find()) {
String wholePart = (fractionMatcher.group(1) == null) ? "" : defaultFractionWholePartFormat;
return new FractionFormat(wholePart, fractionMatcher.group(3));
}
}
// Strip custom text in quotes and escaped characters for now as it can cause performance problems in
// fractions.
// String strippedFormatStr = formatStr.replaceAll("\\\\ ", " ").replaceAll("\\\\.",
// "").replaceAll("\"[^\"]*\"", " ").replaceAll("\\?", "#");
return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat);
}
if (numPattern.matcher(formatStr).find()) {
return createNumberFormat(formatStr);
}
return getDefaultFormat();
}
private Format checkSpecialConverter(String dataFormatString) {
if ("00000\\-0000".equals(dataFormatString) || "00000-0000".equals(dataFormatString)) {
return new ZipPlusFourFormat();
}
if ("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####".equals(dataFormatString)
|| "[<=9999999]###-####;(###) ###-####".equals(dataFormatString)
|| "###\\-####;\\(###\\)\\ ###\\-####".equals(dataFormatString)
|| "###-####;(###) ###-####".equals(dataFormatString)) {
return new PhoneFormat();
}
if ("000\\-00\\-0000".equals(dataFormatString) || "000-00-0000".equals(dataFormatString)) {
return new SSNFormat();
}
return null;
}
private Format createDateFormat(String pFormatStr) {
String formatStr = pFormatStr;
formatStr = formatStr.replaceAll("\\\\-", "-");
formatStr = formatStr.replaceAll("\\\\,", ",");
formatStr = formatStr.replaceAll("\\\\\\.", "."); // . is a special regexp char
formatStr = formatStr.replaceAll("\\\\ ", " ");
formatStr = formatStr.replaceAll("\\\\/", "/"); // weird: m\\/d\\/yyyy
formatStr = formatStr.replaceAll(";@", "");
formatStr = formatStr.replaceAll("\"/\"", "/"); // "/" is escaped for no reason in: mm"/"dd"/"yyyy
formatStr = formatStr.replace("\"\"", "'"); // replace Excel quoting with Java style quoting
formatStr = formatStr.replaceAll("\\\\T", "'T'"); // Quote the T is iso8601 style dates
formatStr = formatStr.replace("\"", "");
boolean hasAmPm = false;
Matcher amPmMatcher = amPmPattern.matcher(formatStr);
while (amPmMatcher.find()) {
formatStr = amPmMatcher.replaceAll("@");
hasAmPm = true;
amPmMatcher = amPmPattern.matcher(formatStr);
}
formatStr = formatStr.replaceAll("@", "a");
Matcher dateMatcher = daysAsText.matcher(formatStr);
if (dateMatcher.find()) {
String match = dateMatcher.group(0).toUpperCase(Locale.ROOT).replaceAll("D", "E");
formatStr = dateMatcher.replaceAll(match);
}
// Convert excel date format to SimpleDateFormat.
// Excel uses lower and upper case 'm' for both minutes and months.
// From Excel help:
/*
The "m" or "mm" code must appear immediately after the "h" or"hh"
code or immediately before the "ss" code; otherwise, Microsoft
Excel displays the month instead of minutes."
*/
StringBuilder sb = new StringBuilder();
char[] chars = formatStr.toCharArray();
boolean mIsMonth = true;
List<Integer> ms = new ArrayList<Integer>();
boolean isElapsed = false;
for (int j = 0; j < chars.length; j++) {
char c = chars[j];
if (c == '\'') {
sb.append(c);
j++;
// skip until the next quote
while (j < chars.length) {
c = chars[j];
sb.append(c);
if (c == '\'') {
break;
}
j++;
}
} else if (c == '[' && !isElapsed) {
isElapsed = true;
mIsMonth = false;
sb.append(c);
} else if (c == ']' && isElapsed) {
isElapsed = false;
sb.append(c);
} else if (isElapsed) {
if (c == 'h' || c == 'H') {
sb.append('H');
} else if (c == 'm' || c == 'M') {
sb.append('m');
} else if (c == 's' || c == 'S') {
sb.append('s');
} else {
sb.append(c);
}
} else if (c == 'h' || c == 'H') {
mIsMonth = false;
if (hasAmPm) {
sb.append('h');
} else {
sb.append('H');
}
} else if (c == 'm' || c == 'M') {
if (mIsMonth) {
sb.append('M');
ms.add(Integer.valueOf(sb.length() - 1));
} else {
sb.append('m');
}
} else if (c == 's' || c == 'S') {
sb.append('s');
// if 'M' precedes 's' it should be minutes ('m')
for (int index : ms) {
if (sb.charAt(index) == 'M') {
sb.replace(index, index + 1, "m");
}
}
mIsMonth = true;
ms.clear();
} else if (Character.isLetter(c)) {
mIsMonth = true;
ms.clear();
if (c == 'y' || c == 'Y') {
sb.append('y');
} else if (c == 'd' || c == 'D') {
sb.append('d');
} else {
sb.append(c);
}
} else {
if (Character.isWhitespace(c)) {
ms.clear();
}
sb.append(c);
}
}
formatStr = sb.toString();
try {
return new ExcelStyleDateFormatter(formatStr, dateSymbols);
} catch (IllegalArgumentException iae) {
LOGGER.debug("Formatting failed for format {}, falling back", formatStr, iae);
// the pattern could not be parsed correctly,
// so fall back to the default number format
return getDefaultFormat();
}
}
private String cleanFormatForNumber(String formatStr) {
StringBuilder sb = new StringBuilder(formatStr);
// If they requested spacers, with "_",
// remove those as we don't do spacing
// If they requested full-column-width
// padding, with "*", remove those too
for (int i = 0; i < sb.length(); i++) {
char c = sb.charAt(i);
if (c == '_' || c == '*') {
if (i > 0 && sb.charAt((i - 1)) == '\\') {
// It's escaped, don't worry
continue;
}
if (i < sb.length() - 1) {
// Remove the character we're supposed
// to match the space of / pad to the
// column width with
sb.deleteCharAt(i + 1);
}
// Remove the _ too
sb.deleteCharAt(i);
i--;
}
}
// Now, handle the other aspects like
// quoting and scientific notation
for (int i = 0; i < sb.length(); i++) {
char c = sb.charAt(i);
// remove quotes and back slashes
if (c == '\\' || c == '"') {
sb.deleteCharAt(i);
i--;
// for scientific/engineering notation
} else if (c == '+' && i > 0 && sb.charAt(i - 1) == 'E') {
sb.deleteCharAt(i);
i--;
}
}
return sb.toString();
}
private static class InternalDecimalFormatWithScale extends Format {
private static final Pattern endsWithCommas = Pattern.compile("(,+)$");
private BigDecimal divider;
private static final BigDecimal ONE_THOUSAND = new BigDecimal(1000);
private final DecimalFormat df;
private static String trimTrailingCommas(String s) {
return s.replaceAll(",+$", "");
}
public InternalDecimalFormatWithScale(String pattern, DecimalFormatSymbols symbols) {
df = new DecimalFormat(trimTrailingCommas(pattern), symbols);
setExcelStyleRoundingMode(df);
Matcher endsWithCommasMatcher = endsWithCommas.matcher(pattern);
if (endsWithCommasMatcher.find()) {
String commas = (endsWithCommasMatcher.group(1));
BigDecimal temp = BigDecimal.ONE;
for (int i = 0; i < commas.length(); ++i) {
temp = temp.multiply(ONE_THOUSAND);
}
divider = temp;
} else {
divider = null;
}
}
private Object scaleInput(Object obj) {
if (divider != null) {
if (obj instanceof BigDecimal) {
obj = ((BigDecimal)obj).divide(divider, RoundingMode.HALF_UP);
} else if (obj instanceof Double) {
obj = (Double)obj / divider.doubleValue();
} else {
throw new UnsupportedOperationException();
}
}
return obj;
}
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
obj = scaleInput(obj);
return df.format(obj, toAppendTo, pos);
}
@Override
public Object parseObject(String source, ParsePosition pos) {
throw new UnsupportedOperationException();
}
}
private Format createNumberFormat(String formatStr) {
String format = cleanFormatForNumber(formatStr);
DecimalFormatSymbols symbols = decimalSymbols;
// Do we need to change the grouping character?
// eg for a format like #'##0 which wants 12'345 not 12,345
Matcher agm = alternateGrouping.matcher(format);
if (agm.find()) {
char grouping = agm.group(2).charAt(0);
// Only replace the grouping character if it is not the default
// grouping character for the US locale (',') in order to enable
// correct grouping for non-US locales.
if (grouping != ',') {
symbols = DecimalFormatSymbols.getInstance(locale);
symbols.setGroupingSeparator(grouping);
String oldPart = agm.group(1);
String newPart = oldPart.replace(grouping, ',');
format = format.replace(oldPart, newPart);
}
}
try {
return new InternalDecimalFormatWithScale(format, symbols);
} catch (IllegalArgumentException iae) {
LOGGER.debug("Formatting failed for format {}, falling back", formatStr, iae);
// the pattern could not be parsed correctly,
// so fall back to the default number format
return getDefaultFormat();
}
}
private Format getDefaultFormat() {
// for numeric cells try user supplied default
if (defaultNumFormat != null) {
return defaultNumFormat;
// otherwise use general format
}
defaultNumFormat = new ExcelGeneralNumberFormat(locale);
return defaultNumFormat;
}
/**
* Performs Excel-style date formatting, using the supplied Date and format
*/
private String performDateFormatting(Date d, Format dateFormat) {
Format df = dateFormat != null ? dateFormat : getDefaultFormat();
return df.format(d);
}
/**
* Returns the formatted value of an Excel date as a <tt>String</tt> based on the cell's <code>DataFormat</code>.
* i.e. "Thursday, January 02, 2003" , "01/02/2003" , "02-Jan" , etc.
* <p>
* If any conditional format rules apply, the highest priority with a number format is used. If no rules contain a
* number format, or no rules apply, the cell's style format is used. If the style does not have a format, the
* default date format is applied.
*
* @param data
* to format
* @param dataFormat
* @param dataFormatString
* @return Formatted value
*/
private String getFormattedDateString(Double data, Integer dataFormat, String dataFormatString) {
Format dateFormat = getFormat(dataFormat, dataFormatString);
if (dateFormat instanceof ExcelStyleDateFormatter) {
// Hint about the raw excel value
((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(data);
}
return performDateFormatting(DateUtil.getJavaDate(data, use1904windowing), dateFormat);
}
/**
* Returns the formatted value of an Excel number as a <tt>String</tt> based on the cell's <code>DataFormat</code>.
* Supported formats include currency, percents, decimals, phone number, SSN, etc.: "61.54%", "$100.00", "(800)
* 555-1234".
* <p>
* Format comes from either the highest priority conditional format rule with a specified format, or from the cell
* style.
*
* @param data
* to format
* @param dataFormat
* @param dataFormatString
* @return a formatted number string
*/
private String getFormattedNumberString(Double data, Integer dataFormat, String dataFormatString) {
Format numberFormat = getFormat(dataFormat, dataFormatString);
String formatted = numberFormat.format(data);
return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation
}
/**
* Format data.
*
* @param data
* @param dataFormat
* @param dataFormatString
* @return
*/
public String format(Double data, Integer dataFormat, String dataFormatString) {
if (DateUtils.isADateFormat(dataFormat, dataFormatString)) {
return getFormattedDateString(data, dataFormat, dataFormatString);
}
return getFormattedNumberString(data, dataFormat, dataFormatString);
}
/**
* <p>
* Sets a default number format to be used when the Excel format cannot be parsed successfully. <b>Note:</b> This is
* a fall back for when an error occurs while parsing an Excel number format pattern. This will not affect cells
* with the <em>General</em> format.
* </p>
* <p>
* The value that will be passed to the Format's format method (specified by <code>java.text.Format#format</code>)
* will be a double value from a numeric cell. Therefore the code in the format method should expect a
* <code>Number</code> value.
* </p>
*
* @param format
* A Format instance to be used as a default
* @see Format#format
*/
public void setDefaultNumberFormat(Format format) {
for (Map.Entry<String, Format> entry : formats.entrySet()) {
if (entry.getValue() == defaultNumFormat) {
entry.setValue(format);
}
}
defaultNumFormat = format;
}
/**
* Adds a new format to the available formats.
* <p>
* The value that will be passed to the Format's format method (specified by <code>java.text.Format#format</code>)
* will be a double value from a numeric cell. Therefore the code in the format method should expect a
* <code>Number</code> value.
* </p>
*
* @param excelFormatStr
* The data format string
* @param format
* A Format instance
*/
public void addFormat(String excelFormatStr, Format format) {
formats.put(excelFormatStr, format);
}
// Some custom formats
/**
* @return a <tt>DecimalFormat</tt> with parseIntegerOnly set <code>true</code>
*/
private static DecimalFormat createIntegerOnlyFormat(String fmt) {
DecimalFormatSymbols dsf = DecimalFormatSymbols.getInstance(Locale.ROOT);
DecimalFormat result = new DecimalFormat(fmt, dsf);
result.setParseIntegerOnly(true);
return result;
}
/**
* Enables excel style rounding mode (round half up) on the Decimal Format given.
*/
public static void setExcelStyleRoundingMode(DecimalFormat format) {
setExcelStyleRoundingMode(format, RoundingMode.HALF_UP);
}
/**
* Enables custom rounding mode on the given Decimal Format.
*
* @param format
* DecimalFormat
* @param roundingMode
* RoundingMode
*/
public static void setExcelStyleRoundingMode(DecimalFormat format, RoundingMode roundingMode) {
format.setRoundingMode(roundingMode);
}
/**
* Format class for Excel's SSN format. This class mimics Excel's built-in SSN formatting.
*
* @author James May
*/
@SuppressWarnings("serial")
private static final class SSNFormat extends Format {
private static final DecimalFormat df = createIntegerOnlyFormat("000000000");
private SSNFormat() {
// enforce singleton
}
/** Format a number as an SSN */
public static String format(Number num) {
String result = df.format(num);
return result.substring(0, 3) + '-' + result.substring(3, 5) + '-' + result.substring(5, 9);
}
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
return toAppendTo.append(format((Number)obj));
}
@Override
public Object parseObject(String source, ParsePosition pos) {
return df.parseObject(source, pos);
}
}
/**
* Format class for Excel Zip + 4 format. This class mimics Excel's built-in formatting for Zip + 4.
*
* @author James May
*/
@SuppressWarnings("serial")
private static final class ZipPlusFourFormat extends Format {
private static final DecimalFormat df = createIntegerOnlyFormat("000000000");
private ZipPlusFourFormat() {
// enforce singleton
}
/** Format a number as Zip + 4 */
public static String format(Number num) {
String result = df.format(num);
return result.substring(0, 5) + '-' + result.substring(5, 9);
}
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
return toAppendTo.append(format((Number)obj));
}
@Override
public Object parseObject(String source, ParsePosition pos) {
return df.parseObject(source, pos);
}
}
/**
* Format class for Excel phone number format. This class mimics Excel's built-in phone number formatting.
*
* @author James May
*/
@SuppressWarnings("serial")
private static final class PhoneFormat extends Format {
private static final DecimalFormat df = createIntegerOnlyFormat("##########");
private PhoneFormat() {
// enforce singleton
}
/** Format a number as a phone number */
public static String format(Number num) {
String result = df.format(num);
StringBuilder sb = new StringBuilder();
String seg1, seg2, seg3;
int len = result.length();
if (len <= 4) {
return result;
}
seg3 = result.substring(len - 4, len);
seg2 = result.substring(Math.max(0, len - 7), len - 4);
seg1 = result.substring(Math.max(0, len - 10), Math.max(0, len - 7));
if (seg1.trim().length() > 0) {
sb.append('(').append(seg1).append(") ");
}
if (seg2.trim().length() > 0) {
sb.append(seg2).append('-');
}
sb.append(seg3);
return sb.toString();
}
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
return toAppendTo.append(format((Number)obj));
}
@Override
public Object parseObject(String source, ParsePosition pos) {
return df.parseObject(source, pos);
}
}
}

15
src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java

@ -1,5 +1,7 @@
package com.alibaba.excel.metadata;
import java.util.Locale;
/**
* Global configuration
*
@ -18,6 +20,11 @@ public class GlobalConfiguration {
* @return
*/
private Boolean use1904windowing;
/**
* A <code>Locale</code> object represents a specific geographical, political, or cultural region. This parameter is
* used when formatting dates and numbers.
*/
private Locale locale;
public Boolean getUse1904windowing() {
return use1904windowing;
@ -34,4 +41,12 @@ public class GlobalConfiguration {
public void setAutoTrim(Boolean autoTrim) {
this.autoTrim = autoTrim;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
}

408
src/main/java/com/alibaba/excel/util/DateUtils.java

@ -1,24 +1,45 @@
package com.alibaba.excel.util;
import java.text.Format;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.ExcelNumberFormat;
import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Date utils
*
* @author Jiaju Zhuang
**/
public class DateUtils {
public class DateUtils implements ThreadLocalCachedUtils {
/**
* Is a cache of dates
*/
private static final ThreadLocal<Map<Integer, Boolean>> DATE_THREAD_LOCAL =
new ThreadLocal<Map<Integer, Boolean>>();
/**
* Is a cache of dates
*/
private static final ThreadLocal<Map<String, SimpleDateFormat>> DATE_FORMAT_THREAD_LOCAL =
new ThreadLocal<Map<String, SimpleDateFormat>>();
/**
* The following patterns are used in {@link #isADateFormat(Integer, String)}
*/
private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]");
private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+\\]");
private static final Pattern date_ptrn3a = Pattern.compile("[yYmMdDhHsS]");
// add "\u5e74 \u6708 \u65e5" for Chinese/Japanese date format:2017 \u5e74 2 \u6708 7 \u65e5
private static final Pattern date_ptrn3b =
Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0*[ampAMP/]*$");
// elapsed time patterns: [h],[m] and [s]
private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]");
// for format which start with "[DBNum1]" or "[DBNum2]" or "[DBNum3]" could be a Chinese date
private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]");
// for format which start with "年" or "月" or "日" or "时" or "分" or "秒" could be a Chinese date
private static final Pattern date_ptrn6 = Pattern.compile("(年|月|日|时|分|秒)+");
public static final String DATE_FORMAT_10 = "yyyy-MM-dd";
public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss";
@ -41,7 +62,7 @@ public class DateUtils {
if (StringUtils.isEmpty(dateFormat)) {
dateFormat = switchDateFormat(dateString);
}
return new SimpleDateFormat(dateFormat).parse(dateString);
return getCacheDateFormat(dateFormat).parse(dateString);
}
/**
@ -107,196 +128,183 @@ public class DateUtils {
if (StringUtils.isEmpty(dateFormat)) {
dateFormat = DATE_FORMAT_19;
}
return new SimpleDateFormat(dateFormat).format(date);
return getCacheDateFormat(dateFormat).format(date);
}
private static DateFormat getCacheDateFormat(String dateFormat) {
Map<String, SimpleDateFormat> dateFormatMap = DATE_FORMAT_THREAD_LOCAL.get();
if (dateFormatMap == null) {
dateFormatMap = new HashMap<String, SimpleDateFormat>();
DATE_FORMAT_THREAD_LOCAL.set(dateFormatMap);
} else {
SimpleDateFormat dateFormatCached = dateFormatMap.get(dateFormat);
if (dateFormatCached != null) {
return dateFormatCached;
}
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
dateFormatMap.put(dateFormat, simpleDateFormat);
return simpleDateFormat;
}
//
// /**
// * Determine if it is a date format.
// *
// * @param dataFormat
// * @param dataFormatString
// * @return
// */
// public static boolean isDateFormatted(Integer dataFormat, String dataFormatString) {
// if (cell == null) {
// return false;
// }
// boolean isDate = false;
//
// double d = cell.getNumericCellValue();
// if (DateUtil.isValidExcelDate(d)) {
// ExcelNumberFormat nf = ExcelNumberFormat.from(cell, cfEvaluator);
// if (nf == null) {
// return false;
// }
// bDate = isADateFormat(nf);
// }
// return bDate;
// }
//
// private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
// if (cell == null) {
// return null;
// }
// Format dateFormat = getFormat(cell, cfEvaluator);
// synchronized (dateFormat) {
// if(dateFormat instanceof ExcelStyleDateFormatter) {
// // Hint about the raw excel value
// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(
// cell.getNumericCellValue()
// );
// }
// Date d = cell.getDateCellValue();
// return performDateFormatting(d, dateFormat);
// }
// }
//
//
// public static boolean isADateFormat(int formatIndex, String formatString) {
// // First up, is this an internal date format?
// if (isInternalDateFormat(formatIndex)) {
// return true;
// }
// if (StringUtils.isEmpty(formatString)) {
// return false;
// }
//
// // check the cache first
// if (isCached(formatString, formatIndex)) {
// return lastCachedResult.get();
// }
//
// String fs = formatString;
// /*if (false) {
// // Normalize the format string. The code below is equivalent
// // to the following consecutive regexp replacements:
//
// // Translate \- into just -, before matching
// fs = fs.replaceAll("\\\\-","-");
// // And \, into ,
// fs = fs.replaceAll("\\\\,",",");
// // And \. into .
// fs = fs.replaceAll("\\\\\\.",".");
// // And '\ ' into ' '
// fs = fs.replaceAll("\\\\ "," ");
//
// // If it end in ;@, that's some crazy dd/mm vs mm/dd
// // switching stuff, which we can ignore
// fs = fs.replaceAll(";@", "");
//
// // The code above was reworked as suggested in bug 48425:
// // simple loop is more efficient than consecutive regexp replacements.
// }*/
// final int length = fs.length();
// StringBuilder sb = new StringBuilder(length);
// for (int i = 0; i < length; i++) {
// char c = fs.charAt(i);
// if (i < length - 1) {
// char nc = fs.charAt(i + 1);
// if (c == '\\') {
// switch (nc) {
// case '-':
// case ',':
// case '.':
// case ' ':
// case '\\':
// // skip current '\' and continue to the next char
// continue;
// }
// } else if (c == ';' && nc == '@') {
// i++;
// // skip ";@" duplets
// continue;
// }
// }
// sb.append(c);
// }
// fs = sb.toString();
//
// // short-circuit if it indicates elapsed time: [h], [m] or [s]
// if (date_ptrn4.matcher(fs).matches()) {
// cache(formatString, formatIndex, true);
// return true;
// }
// // If it starts with [DBNum1] or [DBNum2] or [DBNum3]
// // then it could be a Chinese date
// fs = date_ptrn5.matcher(fs).replaceAll("");
// // If it starts with [$-...], then could be a date, but
// // who knows what that starting bit is all about
// fs = date_ptrn1.matcher(fs).replaceAll("");
// // If it starts with something like [Black] or [Yellow],
// // then it could be a date
// fs = date_ptrn2.matcher(fs).replaceAll("");
// // You're allowed something like dd/mm/yy;[red]dd/mm/yy
// // which would place dates before 1900/1904 in red
// // For now, only consider the first one
// final int separatorIndex = fs.indexOf(';');
// if (0 < separatorIndex && separatorIndex < fs.length() - 1) {
// fs = fs.substring(0, separatorIndex);
// }
//
// // Ensure it has some date letters in it
// // (Avoids false positives on the rest of pattern 3)
// if (!date_ptrn3a.matcher(fs).find()) {
// return false;
// }
//
// // If we get here, check it's only made up, in any case, of:
// // y m d h s - \ / , . : [ ] T
// // optionally followed by AM/PM
//
// boolean result = date_ptrn3b.matcher(fs).matches();
// cache(formatString, formatIndex, result);
// return result;
// }
//
// /**
// * Given a format ID this will check whether the format represents an internal excel date format or not.
// *
// * @see #isADateFormat(int, java.lang.String)
// */
// public static boolean isInternalDateFormat(int format) {
// switch (format) {
// // Internal Date Formats as described on page 427 in
// // Microsoft Excel Dev's Kit...
// // 14-22
// case 0x0e:
// case 0x0f:
// case 0x10:
// case 0x11:
// case 0x12:
// case 0x13:
// case 0x14:
// case 0x15:
// case 0x16:
// // 27-36
// case 0x1b:
// case 0x1c:
// case 0x1d:
// case 0x1e:
// case 0x1f:
// case 0x20:
// case 0x21:
// case 0x22:
// case 0x23:
// case 0x24:
// // 45-47
// case 0x2d:
// case 0x2e:
// case 0x2f:
// // 50-58
// case 0x32:
// case 0x33:
// case 0x34:
// case 0x35:
// case 0x36:
// case 0x37:
// case 0x38:
// case 0x39:
// case 0x3a:
// return true;
// }
// return false;
// }
/**
* Determine if it is a date format.
*
* @param formatIndex
* @param formatString
* @return
*/
public static boolean isADateFormat(Integer formatIndex, String formatString) {
if (formatIndex == null) {
return false;
}
Map<Integer, Boolean> isDateCache = DATE_THREAD_LOCAL.get();
if (isDateCache == null) {
isDateCache = new HashMap<Integer, Boolean>();
DATE_THREAD_LOCAL.set(isDateCache);
} else {
Boolean isDateCachedData = isDateCache.get(formatIndex);
if (isDateCachedData != null) {
return isDateCachedData;
}
}
boolean isDate = isADateFormatUncached(formatIndex, formatString);
isDateCache.put(formatIndex, isDate);
return isDate;
}
/**
* Determine if it is a date format.
*
* @param formatIndex
* @param formatString
* @return
*/
public static boolean isADateFormatUncached(Integer formatIndex, String formatString) {
// First up, is this an internal date format?
if (isInternalDateFormat(formatIndex)) {
return true;
}
if (StringUtils.isEmpty(formatString)) {
return false;
}
String fs = formatString;
final int length = fs.length();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = fs.charAt(i);
if (i < length - 1) {
char nc = fs.charAt(i + 1);
if (c == '\\') {
switch (nc) {
case '-':
case ',':
case '.':
case ' ':
case '\\':
// skip current '\' and continue to the next char
continue;
}
} else if (c == ';' && nc == '@') {
i++;
// skip ";@" duplets
continue;
}
}
sb.append(c);
}
fs = sb.toString();
// short-circuit if it indicates elapsed time: [h], [m] or [s]
if (date_ptrn4.matcher(fs).matches()) {
return true;
}
// If it starts with [DBNum1] or [DBNum2] or [DBNum3]
// then it could be a Chinese date
fs = date_ptrn5.matcher(fs).replaceAll("");
// If it starts with [$-...], then could be a date, but
// who knows what that starting bit is all about
fs = date_ptrn1.matcher(fs).replaceAll("");
// If it starts with something like [Black] or [Yellow],
// then it could be a date
fs = date_ptrn2.matcher(fs).replaceAll("");
// You're allowed something like dd/mm/yy;[red]dd/mm/yy
// which would place dates before 1900/1904 in red
// For now, only consider the first one
final int separatorIndex = fs.indexOf(';');
if (0 < separatorIndex && separatorIndex < fs.length() - 1) {
fs = fs.substring(0, separatorIndex);
}
// Ensure it has some date letters in it
// (Avoids false positives on the rest of pattern 3)
if (!date_ptrn3a.matcher(fs).find()) {
return false;
}
// If we get here, check it's only made up, in any case, of:
// y m d h s - \ / , . : [ ] T
// optionally followed by AM/PM
boolean result = date_ptrn3b.matcher(fs).matches();
if (result) {
return true;
}
result = date_ptrn6.matcher(fs).find();
return result;
}
/**
* Given a format ID this will check whether the format represents an internal excel date format or not.
*
* @see #isADateFormat(Integer, String)
*/
public static boolean isInternalDateFormat(int format) {
switch (format) {
// Internal Date Formats as described on page 427 in
// Microsoft Excel Dev's Kit...
// 14-22
case 0x0e:
case 0x0f:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
// 27-36
case 0x1b:
case 0x1c:
case 0x1d:
case 0x1e:
case 0x1f:
case 0x20:
case 0x21:
case 0x22:
case 0x23:
case 0x24:
// 45-47
case 0x2d:
case 0x2e:
case 0x2f:
// 50-58
case 0x32:
case 0x33:
case 0x34:
case 0x35:
case 0x36:
case 0x37:
case 0x38:
case 0x39:
case 0x3a:
return true;
}
return false;
}
@Override
public void removeThreadLocalCache() {
DATE_THREAD_LOCAL.remove();
DATE_FORMAT_THREAD_LOCAL.remove();
}
}

201
src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java

@ -1,154 +1,47 @@
//package com.alibaba.excel.util;
//
//import java.text.Format;
//
//import org.apache.poi.ss.format.CellFormat;
//import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
//import org.apache.poi.ss.usermodel.Cell;
//import org.apache.poi.ss.usermodel.DataFormatter;
//import org.apache.poi.ss.usermodel.DateUtil;
//import org.apache.poi.ss.usermodel.ExcelNumberFormat;
//import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter;
//import org.apache.poi.util.POILogger;
//
///**
// * Convert number data, including date.
// *
// * @author Jiaju Zhuang
// **/
//public class NumberDataFormatterUtils {
//
// /**
// *
// * @param data
// * Not null.
// * @param dataFormatString
// * Not null.
// * @return
// */
// public String format(Double data, Integer dataFormat, String dataFormatString) {
//
// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) {
// return getFormattedDateString(cell, cfEvaluator);
// }
// return getFormattedNumberString(cell, cfEvaluator);
//
// }
//
// private String getFormattedDateString(Double data,String dataFormatString) {
//
//
// if (cell == null) {
// return null;
// }
// Format dateFormat = getFormat(cell, cfEvaluator);
// synchronized (dateFormat) {
// if (dateFormat instanceof ExcelStyleDateFormatter) {
// // Hint about the raw excel value
// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue());
// }
// Date d = cell.getDateCellValue();
// return performDateFormatting(d, dateFormat);
// }
// }
//
//
// /**
// * Return a Format for the given cell if one exists, otherwise try to
// * create one. This method will return <code>null</code> if the any of the
// * following is true:
// * <ul>
// * <li>the cell's style is null</li>
// * <li>the style's data format string is null or empty</li>
// * <li>the format string cannot be recognized as either a number or date</li>
// * </ul>
// *
// * @param cell The cell to retrieve a Format for
// * @return A Format for the format String
// */
// private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
// if (cell == null) return null;
//
// ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator);
//
// if ( numFmt == null) {
// return null;
// }
//
// int formatIndex = numFmt.getIdx();
// String formatStr = numFmt.getFormat();
// if(formatStr == null || formatStr.trim().length() == 0) {
// return null;
// }
// return getFormat(cell.getNumericCellValue(), formatIndex, formatStr, isDate1904(cell));
// }
//
// private boolean isDate1904(Cell cell) {
// if ( cell != null && cell.getSheet().getWorkbook() instanceof Date1904Support) {
// return ((Date1904Support)cell.getSheet().getWorkbook()).isDate1904();
//
// }
// return false;
// }
//
// private Format getFormat(double cellValue, int formatIndex, String formatStrIn, boolean use1904Windowing) {
// localeChangedObservable.checkForLocaleChange();
//
// // Might be better to separate out the n p and z formats, falling back to p when n and z are not set.
// // That however would require other code to be re factored.
// // String[] formatBits = formatStrIn.split(";");
// // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2;
// // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0];
//
// String formatStr = formatStrIn;
//
// // Excel supports 2+ part conditional data formats, eg positive/negative/zero,
// // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds
// // of different formats for different ranges, just +ve/-ve, we need to
// // handle these ourselves in a special way.
// // For now, if we detect 2+ parts, we call out to CellFormat to handle it
// // TODO Going forward, we should really merge the logic between the two classes
// if (formatStr.contains(";") &&
// (formatStr.indexOf(';') != formatStr.lastIndexOf(';')
// || rangeConditionalPattern.matcher(formatStr).matches()
// ) ) {
// try {
// // Ask CellFormat to get a formatter for it
// CellFormat cfmt = CellFormat.getInstance(locale, formatStr);
// // CellFormat requires callers to identify date vs not, so do so
// Object cellValueO = Double.valueOf(cellValue);
// if (DateUtil.isADateFormat(formatIndex, formatStr) &&
// // don't try to handle Date value 0, let a 3 or 4-part format take care of it
// ((Double)cellValueO).doubleValue() != 0.0) {
// cellValueO = DateUtil.getJavaDate(cellValue, use1904Windowing);
// }
// // Wrap and return (non-cachable - CellFormat does that)
// return new DataFormatter.CellFormatResultWrapper( cfmt.apply(cellValueO) );
// } catch (Exception e) {
// logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e);
// }
// }
//
// // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format.
// if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) {
// formatStr = formatStr.replaceAll("#", "");
// }
//
// // See if we already have it cached
// Format format = formats.get(formatStr);
// if (format != null) {
// return format;
// }
//
// // Is it one of the special built in types, General or @?
// if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
// return generalNumberFormat;
// }
//
// // Build a formatter, and cache it
// format = createFormat(cellValue, formatIndex, formatStr);
// formats.put(formatStr, format);
// return format;
// }
//
//}
package com.alibaba.excel.util;
import com.alibaba.excel.metadata.DataFormatter;
import com.alibaba.excel.metadata.GlobalConfiguration;
/**
* Convert number data, including date.
*
* @author Jiaju Zhuang
**/
public class NumberDataFormatterUtils implements ThreadLocalCachedUtils {
/**
* Cache DataFormatter.
*/
private static final ThreadLocal<DataFormatter> DATA_FORMATTER_THREAD_LOCAL = new ThreadLocal<DataFormatter>();
/**
* Format number data.
*
* @param data
* @param dataFormat
* Not null.
* @param dataFormatString
* @param globalConfiguration
* @return
*/
public static String format(Double data, Integer dataFormat, String dataFormatString,
GlobalConfiguration globalConfiguration) {
DataFormatter dataFormatter = DATA_FORMATTER_THREAD_LOCAL.get();
if (dataFormatter == null) {
if (globalConfiguration != null) {
dataFormatter =
new DataFormatter(globalConfiguration.getLocale(), globalConfiguration.getUse1904windowing());
} else {
dataFormatter = new DataFormatter();
}
DATA_FORMATTER_THREAD_LOCAL.set(dataFormatter);
}
return dataFormatter.format(data, dataFormat, dataFormatString);
}
@Override
public void removeThreadLocalCache() {
DATA_FORMATTER_THREAD_LOCAL.remove();
}
}

14
src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java

@ -0,0 +1,14 @@
package com.alibaba.excel.util;
/**
* Thread local cache in the current tool class.
*
* @author Jiaju Zhuang
**/
public interface ThreadLocalCachedUtils {
/**
* Remove remove thread local cached.
*/
void removeThreadLocalCache();
}

14
src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java

@ -0,0 +1,14 @@
package com.alibaba.easyexcel.test.core.dataformat;
import lombok.Data;
/**
* @author Jiaju Zhuang
*/
@Data
public class DateFormatData {
private String date;
private String dateString;
private String number;
private String numberString;
}

50
src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java

@ -0,0 +1,50 @@
package com.alibaba.easyexcel.test.core.dataformat;
import java.io.File;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
/**
*
* @author Jiaju Zhuang
*/
public class DateFormatTest {
private static final Logger LOGGER = LoggerFactory.getLogger(DateFormatTest.class);
private static File file07;
private static File file03;
@BeforeClass
public static void init() {
file07 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx");
file03 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xls");
}
@Test
public void t01Read07() {
read(file07);
}
@Test
public void t02Read03() {
read(file03);
}
private void read(File file) {
List<DateFormatData> list = EasyExcel.read(file, DateFormatData.class, null).sheet().doReadSync();
for (DateFormatData data : list) {
if (!data.getDate().equals(data.getDateString())) {
LOGGER.info("返回:{}", JSON.toJSONString(data));
}
}
}
}

63
src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java

@ -6,10 +6,14 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Ignore;
@ -17,7 +21,9 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.easyexcel.test.core.dataformat.DateFormatData;
import com.alibaba.easyexcel.test.temp.Lock2Test;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
@ -124,4 +130,61 @@ public class DataFormatTest {
System.out.println("end:" + (System.currentTimeMillis() - start));
}
@Test
public void test355() throws IOException, InvalidFormatException {
File file = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx");
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file);
Sheet xssfSheet = xssfWorkbook.getSheetAt(0);
DataFormatter d = new DataFormatter(Locale.CHINA);
for (int i = 0; i < xssfSheet.getLastRowNum(); i++) {
Row row = xssfSheet.getRow(i);
System.out.println(d.formatCellValue(row.getCell(0)));
}
}
@Test
public void test3556() throws IOException, InvalidFormatException {
String file = "D://test/dataformat.xlsx";
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file);
Sheet xssfSheet = xssfWorkbook.getSheetAt(0);
DataFormatter d = new DataFormatter(Locale.CHINA);
for (int i = 0; i < xssfSheet.getLastRowNum(); i++) {
Row row = xssfSheet.getRow(i);
System.out.println(d.formatCellValue(row.getCell(0)));
}
}
@Test
public void tests() throws IOException, InvalidFormatException {
SimpleDateFormat s1 = new SimpleDateFormat("yyyy\"5E74\"m\"6708\"d\"65E5\"");
System.out.println(s1.format(new Date()));
s1 = new SimpleDateFormat("yyyy年m月d日");
System.out.println(s1.format(new Date()));
}
@Test
public void tests1() throws IOException, InvalidFormatException {
String file = "D://test/dataformat1.xlsx";
List<DateFormatData> list = EasyExcel.read(file, DateFormatData.class, null).sheet().doReadSync();
for (DateFormatData data : list) {
LOGGER.info("返回:{}", JSON.toJSONString(data));
}
}
@Test
public void tests3() throws IOException, InvalidFormatException {
SimpleDateFormat s1 = new SimpleDateFormat("ah\"时\"mm\"分\"");
System.out.println(s1.format(new Date()));
}
private static final Pattern date_ptrn6 = Pattern.compile("^.*(年|月|日|时|分|秒)+.*$");
@Test
public void tests34() throws IOException, InvalidFormatException {
System.out.println(date_ptrn6.matcher("2017但是").matches());
}
}

61
src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java

@ -2,12 +2,8 @@ package com.alibaba.easyexcel.test.temp.poi;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.regex.Pattern;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
@ -17,11 +13,8 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.fastjson.JSON;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
/**
* 测试poi
*
@ -99,58 +92,4 @@ public class PoiWriteTest {
}
@Test
public void test() throws Exception {
Class<TestCell> clazz = TestCell.class;
Field field = clazz.getDeclaredField("c2");
// 通过getDeclaredField可以获得成员变量,但是对于Map来说,仅仅可以知道它是个Map,无法知道键值对各自的数据类型
Type gType = field.getGenericType();
// 获得field的泛型类型
// 如果gType是ParameterizedType对象(参数化)
if (gType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType)gType;
// 就把它转换成ParameterizedType对象
Type[] tArgs = pType.getActualTypeArguments();
// 获得泛型类型的泛型参数(实际类型参数)
ParameterizedTypeImpl c = (ParameterizedTypeImpl)pType.getActualTypeArguments()[0];
Class ttt = c.getRawType();
System.out.println(ttt);
} else {
System.out.println("出错!!!");
}
}
@Test
public void test2() throws Exception {
Class<TestCell> clazz = TestCell.class;
Field field = clazz.getDeclaredField("c2");
// 通过getDeclaredField可以获得成员变量,但是对于Map来说,仅仅可以知道它是个Map,无法知道键值对各自的数据类型
Type gType = field.getGenericType();
// 获得field的泛型类型
// 如果gType是ParameterizedType对象(参数化)
if (gType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType)gType;
// 就把它转换成ParameterizedType对象
Type[] tArgs = pType.getActualTypeArguments();
// 获得泛型类型的泛型参数(实际类型参数)
ParameterizedTypeImpl c = (ParameterizedTypeImpl)pType.getActualTypeArguments()[0];
Class ttt = c.getRawType();
System.out.println(ttt);
} else {
System.out.println("出错!!!");
}
}
}

BIN
src/test/resources/dataformat/dataformat.xlsx

Binary file not shown.
Loading…
Cancel
Save