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> <artifactId>poi-ooxml</artifactId>
<version>3.17</version> <version>3.17</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.17</version>
</dependency>
<dependency> <dependency>
<groupId>cglib</groupId> <groupId>cglib</groupId>
<artifactId>cglib</artifactId> <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 org.apache.poi.hssf.record.Record;
import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler; import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler;
import com.alibaba.excel.constant.BuiltinFormats;
import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.CellData;
/** /**
@ -32,8 +33,13 @@ public class NumberRecordHandler extends AbstractXlsRecordHandler {
this.row = numrec.getRow(); this.row = numrec.getRow();
this.column = numrec.getColumn(); this.column = numrec.getColumn();
this.cellData = new CellData(BigDecimal.valueOf(numrec.getValue())); this.cellData = new CellData(BigDecimal.valueOf(numrec.getValue()));
this.cellData.setDataFormat(formatListener.getFormatIndex(numrec)); int dataFormat = formatListener.getFormatIndex(numrec);
this.cellData.setDataFormatString(formatListener.getFormatString(numrec)); this.cellData.setDataFormat(dataFormat);
if (dataFormat <= BuiltinFormats.builtinFormats.length) {
this.cellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat));
} else {
this.cellData.setDataFormatString(formatListener.getFormatString(numrec));
}
} }
@Override @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.LinkedList;
import java.util.Map; import java.util.Map;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRichTextString; 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.XlsxCellHandler;
import com.alibaba.excel.analysis.v07.XlsxRowResultHolder; import com.alibaba.excel.analysis.v07.XlsxRowResultHolder;
import com.alibaba.excel.constant.BuiltinFormats;
import com.alibaba.excel.constant.ExcelXmlConstants; import com.alibaba.excel.constant.ExcelXmlConstants;
import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.CellDataTypeEnum;
@ -87,12 +87,11 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder
int dateFormatIndexInteger = Integer.parseInt(dateFormatIndex); int dateFormatIndexInteger = Integer.parseInt(dateFormatIndex);
XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger); XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger);
int dataFormat = xssfCellStyle.getDataFormat(); int dataFormat = xssfCellStyle.getDataFormat();
String dataFormatString = xssfCellStyle.getDataFormatString();
currentCellData.setDataFormat(dataFormat); currentCellData.setDataFormat(dataFormat);
if (dataFormatString == null) { if (dataFormat <= BuiltinFormats.builtinFormats.length) {
currentCellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat)); currentCellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat));
} else { } 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 // 13
"# ??/??", "# ??/??",
// 14 // 14
"m/d/yy", // The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d".
"yyyy/m/d",
// 15 // 15
"d-mmm-yy", "d-mmm-yy",
// 16 // 16
@ -63,7 +64,8 @@ public class BuiltinFormats {
// 21 // 21
"h:mm:ss", "h:mm:ss",
// 22 // 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-26 No specific correspondence found in the official documentation.
// 23 // 23
null, null,
@ -74,25 +76,25 @@ public class BuiltinFormats {
// 26 // 26
null, null,
// 27 // 27
"yyyy\"5E74\"m\"6708\"", "yyyy\"年\"m\"月\"",
// 28 // 28
"m\"6708\"d\"65E5\"", "m\"月\"d\"日\"",
// 29 // 29
"m\"6708\"d\"65E5\"", "m\"月\"d\"日\"",
// 30 // 30
"m-d-yy", "m-d-yy",
// 31 // 31
"yyyy\"5E74\"m\"6708\"d\"65E5\"", "yyyy\"年\"m\"月\"d\"日\"",
// 32 // 32
"h\"65F6\"mm\"5206\"", "h\"时\"mm\"分\"",
// 33 // 33
"h\"65F6\"mm\"5206\"ss\"79D2\"", "h\"时\"mm\"分\"ss\"秒\"",
// 34 // 34
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", "上午/下午h\"时\"mm\"分\"",
// 35 // 35
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", "上午/下午h\"时\"mm\"分\"ss\"秒\"",
// 36 // 36
"yyyy\"5E74\"m\"6708\"", "yyyy\"年\"m\"月\"",
// 37 // 37
"#,##0_);(#,##0)", "#,##0_);(#,##0)",
// 38 // 38
@ -120,23 +122,23 @@ public class BuiltinFormats {
// 49 // 49
"@", "@",
// 50 // 50
"yyyy\"5E74\"m\"6708\"", "yyyy\"年\"m\"月\"",
// 51 // 51
"m\"6708\"d\"65E5\"", "m\"月\"d\"日\"",
// 52 // 52
"yyyy\"5E74\"m\"6708\"", "yyyy\"年\"m\"月\"",
// 53 // 53
"m\"6708\"d\"65E5\"", "m\"月\"d\"日\"",
// 54 // 54
"m\"6708\"d\"65E5\"", "m\"月\"d\"日\"",
// 55 // 55
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", "上午/下午h\"时\"mm\"分\"",
// 56 // 56
"4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", "上午/下午h\"时\"mm\"分\"ss\"秒\"",
// 57 // 57
"yyyy\"5E74\"m\"6708\"", "yyyy\"年\"m\"月\"",
// 58 // 58
"m\"6708\"d\"65E5\"", "m\"月\"d\"日\"",
// 59 // 59
"t0", "t0",
// 60 // 60
@ -163,25 +165,25 @@ public class BuiltinFormats {
// 70 // 70
"t# ??/??", "t# ??/??",
// 71 // 71
"0E27/0E14/0E1B0E1B0E1B0E1B", "ว/ด/ปปปป",
// 72 // 72
"0E27-0E140E140E14-0E1B0E1B", "ว-ดดด-ปป",
// 73 // 73
"0E27-0E140E140E14", "ว-ดดด",
// 74 // 74
"0E140E140E14-0E1B0E1B", "ดดด-ปป",
// 75 // 75
"0E0A:0E190E19", "ช:นน",
// 76 // 76
"0E0A:0E190E19:0E170E17", "ช:นน:ทท",
// 77 // 77
"0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19", "ว/ด/ปปปป ช:นน",
// 78 // 78
"0E190E19:0E170E17", "นน:ทท",
// 79 // 79
"[0E0A]:0E190E19:0E170E17", "[ช]:นน:ทท",
// 80 // 80
"0E190E19:0E170E17.0", "นน:ทท.0",
// 81 // 81
"d/m/bb", "d/m/bb",
// end // 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.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.DateUtils; import com.alibaba.excel.util.DateUtils;
import com.alibaba.excel.util.NumberDataFormatterUtils;
import com.alibaba.excel.util.NumberUtils; import com.alibaba.excel.util.NumberUtils;
import com.alibaba.excel.util.StringUtils;
/** /**
* String and number converter * String and number converter
@ -44,13 +46,9 @@ public class StringNumberConverter implements Converter<String> {
return NumberUtils.format(cellData.getNumberValue(), contentProperty); return NumberUtils.format(cellData.getNumberValue(), contentProperty);
} }
// Excel defines formatting // Excel defines formatting
if (cellData.getDataFormat() != null) { if (cellData.getDataFormat() != null && !StringUtils.isEmpty(cellData.getDataFormatString())) {
if (DateUtil.isADateFormat(cellData.getDataFormat(), cellData.getDataFormatString())) { return NumberDataFormatterUtils.format(cellData.getNumberValue().doubleValue(), cellData.getDataFormat(),
return DateUtils.format(DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(), cellData.getDataFormatString(), globalConfiguration);
globalConfiguration.getUse1904windowing(), null));
} else {
return NumberUtils.format(cellData.getNumberValue(), contentProperty);
}
} }
// Default conversion number // Default conversion number
return NumberUtils.format(cellData.getNumberValue(), contentProperty); 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; package com.alibaba.excel.metadata;
import java.util.List; import java.util.List;
import java.util.Locale;
import com.alibaba.excel.converters.Converter; import com.alibaba.excel.converters.Converter;
@ -34,6 +35,11 @@ public class BasicParameter {
* @return * @return
*/ */
private Boolean use1904windowing; 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() { public List<List<String>> getHead() {
return head; return head;
@ -75,4 +81,11 @@ public class BasicParameter {
this.use1904windowing = use1904windowing; 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; package com.alibaba.excel.metadata;
import java.util.Locale;
/** /**
* Global configuration * Global configuration
* *
@ -18,6 +20,11 @@ public class GlobalConfiguration {
* @return * @return
*/ */
private Boolean use1904windowing; 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() { public Boolean getUse1904windowing() {
return use1904windowing; return use1904windowing;
@ -34,4 +41,12 @@ public class GlobalConfiguration {
public void setAutoTrim(Boolean autoTrim) { public void setAutoTrim(Boolean autoTrim) {
this.autoTrim = 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; package com.alibaba.excel.util;
import java.text.Format; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; import java.util.Map;
import org.apache.poi.ss.usermodel.Cell; import java.util.regex.Pattern;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.ExcelNumberFormat;
import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter;
/** /**
* Date utils * Date utils
* *
* @author Jiaju Zhuang * @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_10 = "yyyy-MM-dd";
public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss";
@ -41,7 +62,7 @@ public class DateUtils {
if (StringUtils.isEmpty(dateFormat)) { if (StringUtils.isEmpty(dateFormat)) {
dateFormat = switchDateFormat(dateString); 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)) { if (StringUtils.isEmpty(dateFormat)) {
dateFormat = DATE_FORMAT_19; 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.
// * Determine if it is a date format. *
// * * @param formatIndex
// * @param dataFormat * @param formatString
// * @param dataFormatString * @return
// * @return */
// */ public static boolean isADateFormat(Integer formatIndex, String formatString) {
// public static boolean isDateFormatted(Integer dataFormat, String dataFormatString) { if (formatIndex == null) {
// if (cell == null) { return false;
// return false; }
// } Map<Integer, Boolean> isDateCache = DATE_THREAD_LOCAL.get();
// boolean isDate = false; if (isDateCache == null) {
// isDateCache = new HashMap<Integer, Boolean>();
// double d = cell.getNumericCellValue(); DATE_THREAD_LOCAL.set(isDateCache);
// if (DateUtil.isValidExcelDate(d)) { } else {
// ExcelNumberFormat nf = ExcelNumberFormat.from(cell, cfEvaluator); Boolean isDateCachedData = isDateCache.get(formatIndex);
// if (nf == null) { if (isDateCachedData != null) {
// return false; return isDateCachedData;
// } }
// bDate = isADateFormat(nf); }
// } boolean isDate = isADateFormatUncached(formatIndex, formatString);
// return bDate; isDateCache.put(formatIndex, isDate);
// } return isDate;
// }
// private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
// if (cell == null) { /**
// return null; * Determine if it is a date format.
// } *
// Format dateFormat = getFormat(cell, cfEvaluator); * @param formatIndex
// synchronized (dateFormat) { * @param formatString
// if(dateFormat instanceof ExcelStyleDateFormatter) { * @return
// // Hint about the raw excel value */
// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted( public static boolean isADateFormatUncached(Integer formatIndex, String formatString) {
// cell.getNumericCellValue() // First up, is this an internal date format?
// ); if (isInternalDateFormat(formatIndex)) {
// } return true;
// Date d = cell.getDateCellValue(); }
// return performDateFormatting(d, dateFormat); if (StringUtils.isEmpty(formatString)) {
// } return false;
// } }
// String fs = formatString;
// final int length = fs.length();
// public static boolean isADateFormat(int formatIndex, String formatString) { StringBuilder sb = new StringBuilder(length);
// // First up, is this an internal date format? for (int i = 0; i < length; i++) {
// if (isInternalDateFormat(formatIndex)) { char c = fs.charAt(i);
// return true; if (i < length - 1) {
// } char nc = fs.charAt(i + 1);
// if (StringUtils.isEmpty(formatString)) { if (c == '\\') {
// return false; switch (nc) {
// } case '-':
// case ',':
// // check the cache first case '.':
// if (isCached(formatString, formatIndex)) { case ' ':
// return lastCachedResult.get(); case '\\':
// } // skip current '\' and continue to the next char
// continue;
// String fs = formatString; }
// /*if (false) { } else if (c == ';' && nc == '@') {
// // Normalize the format string. The code below is equivalent i++;
// // to the following consecutive regexp replacements: // skip ";@" duplets
// continue;
// // Translate \- into just -, before matching }
// fs = fs.replaceAll("\\\\-","-"); }
// // And \, into , sb.append(c);
// fs = fs.replaceAll("\\\\,",","); }
// // And \. into . fs = sb.toString();
// fs = fs.replaceAll("\\\\\\.",".");
// // And '\ ' into ' ' // short-circuit if it indicates elapsed time: [h], [m] or [s]
// fs = fs.replaceAll("\\\\ "," "); if (date_ptrn4.matcher(fs).matches()) {
// return true;
// // If it end in ;@, that's some crazy dd/mm vs mm/dd }
// // switching stuff, which we can ignore // If it starts with [DBNum1] or [DBNum2] or [DBNum3]
// fs = fs.replaceAll(";@", ""); // then it could be a Chinese date
// fs = date_ptrn5.matcher(fs).replaceAll("");
// // The code above was reworked as suggested in bug 48425: // If it starts with [$-...], then could be a date, but
// // simple loop is more efficient than consecutive regexp replacements. // who knows what that starting bit is all about
// }*/ fs = date_ptrn1.matcher(fs).replaceAll("");
// final int length = fs.length(); // If it starts with something like [Black] or [Yellow],
// StringBuilder sb = new StringBuilder(length); // then it could be a date
// for (int i = 0; i < length; i++) { fs = date_ptrn2.matcher(fs).replaceAll("");
// char c = fs.charAt(i); // You're allowed something like dd/mm/yy;[red]dd/mm/yy
// if (i < length - 1) { // which would place dates before 1900/1904 in red
// char nc = fs.charAt(i + 1); // For now, only consider the first one
// if (c == '\\') { final int separatorIndex = fs.indexOf(';');
// switch (nc) { if (0 < separatorIndex && separatorIndex < fs.length() - 1) {
// case '-': fs = fs.substring(0, separatorIndex);
// case ',': }
// case '.':
// case ' ': // Ensure it has some date letters in it
// case '\\': // (Avoids false positives on the rest of pattern 3)
// // skip current '\' and continue to the next char if (!date_ptrn3a.matcher(fs).find()) {
// continue; return false;
// } }
// } else if (c == ';' && nc == '@') {
// i++; // If we get here, check it's only made up, in any case, of:
// // skip ";@" duplets // y m d h s - \ / , . : [ ] T
// continue; // optionally followed by AM/PM
// } boolean result = date_ptrn3b.matcher(fs).matches();
// } if (result) {
// sb.append(c); return true;
// } }
// fs = sb.toString(); result = date_ptrn6.matcher(fs).find();
// return result;
// // short-circuit if it indicates elapsed time: [h], [m] or [s] }
// if (date_ptrn4.matcher(fs).matches()) {
// cache(formatString, formatIndex, true); /**
// return true; * Given a format ID this will check whether the format represents an internal excel date format or not.
// } *
// // If it starts with [DBNum1] or [DBNum2] or [DBNum3] * @see #isADateFormat(Integer, String)
// // then it could be a Chinese date */
// fs = date_ptrn5.matcher(fs).replaceAll(""); public static boolean isInternalDateFormat(int format) {
// // If it starts with [$-...], then could be a date, but switch (format) {
// // who knows what that starting bit is all about // Internal Date Formats as described on page 427 in
// fs = date_ptrn1.matcher(fs).replaceAll(""); // Microsoft Excel Dev's Kit...
// // If it starts with something like [Black] or [Yellow], // 14-22
// // then it could be a date case 0x0e:
// fs = date_ptrn2.matcher(fs).replaceAll(""); case 0x0f:
// // You're allowed something like dd/mm/yy;[red]dd/mm/yy case 0x10:
// // which would place dates before 1900/1904 in red case 0x11:
// // For now, only consider the first one case 0x12:
// final int separatorIndex = fs.indexOf(';'); case 0x13:
// if (0 < separatorIndex && separatorIndex < fs.length() - 1) { case 0x14:
// fs = fs.substring(0, separatorIndex); case 0x15:
// } case 0x16:
// // 27-36
// // Ensure it has some date letters in it case 0x1b:
// // (Avoids false positives on the rest of pattern 3) case 0x1c:
// if (!date_ptrn3a.matcher(fs).find()) { case 0x1d:
// return false; case 0x1e:
// } case 0x1f:
// case 0x20:
// // If we get here, check it's only made up, in any case, of: case 0x21:
// // y m d h s - \ / , . : [ ] T case 0x22:
// // optionally followed by AM/PM case 0x23:
// case 0x24:
// boolean result = date_ptrn3b.matcher(fs).matches(); // 45-47
// cache(formatString, formatIndex, result); case 0x2d:
// return result; case 0x2e:
// } case 0x2f:
// // 50-58
// /** case 0x32:
// * Given a format ID this will check whether the format represents an internal excel date format or not. case 0x33:
// * case 0x34:
// * @see #isADateFormat(int, java.lang.String) case 0x35:
// */ case 0x36:
// public static boolean isInternalDateFormat(int format) { case 0x37:
// switch (format) { case 0x38:
// // Internal Date Formats as described on page 427 in case 0x39:
// // Microsoft Excel Dev's Kit... case 0x3a:
// // 14-22 return true;
// case 0x0e: }
// case 0x0f: return false;
// case 0x10: }
// case 0x11:
// case 0x12: @Override
// case 0x13: public void removeThreadLocalCache() {
// case 0x14: DATE_THREAD_LOCAL.remove();
// case 0x15: DATE_FORMAT_THREAD_LOCAL.remove();
// 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;
// }
} }

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

@ -1,154 +1,47 @@
//package com.alibaba.excel.util; package com.alibaba.excel.util;
//
//import java.text.Format; import com.alibaba.excel.metadata.DataFormatter;
// import com.alibaba.excel.metadata.GlobalConfiguration;
//import org.apache.poi.ss.format.CellFormat;
//import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; /**
//import org.apache.poi.ss.usermodel.Cell; * Convert number data, including date.
//import org.apache.poi.ss.usermodel.DataFormatter; *
//import org.apache.poi.ss.usermodel.DateUtil; * @author Jiaju Zhuang
//import org.apache.poi.ss.usermodel.ExcelNumberFormat; **/
//import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; public class NumberDataFormatterUtils implements ThreadLocalCachedUtils {
//import org.apache.poi.util.POILogger; /**
// * Cache DataFormatter.
///** */
// * Convert number data, including date. private static final ThreadLocal<DataFormatter> DATA_FORMATTER_THREAD_LOCAL = new ThreadLocal<DataFormatter>();
// *
// * @author Jiaju Zhuang /**
// **/ * Format number data.
//public class NumberDataFormatterUtils { *
// * @param data
// /** * @param dataFormat
// * * Not null.
// * @param data * @param dataFormatString
// * Not null. * @param globalConfiguration
// * @param dataFormatString * @return
// * Not null. */
// * @return public static String format(Double data, Integer dataFormat, String dataFormatString,
// */ GlobalConfiguration globalConfiguration) {
// public String format(Double data, Integer dataFormat, String dataFormatString) { DataFormatter dataFormatter = DATA_FORMATTER_THREAD_LOCAL.get();
// if (dataFormatter == null) {
// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) { if (globalConfiguration != null) {
// return getFormattedDateString(cell, cfEvaluator); dataFormatter =
// } new DataFormatter(globalConfiguration.getLocale(), globalConfiguration.getUse1904windowing());
// return getFormattedNumberString(cell, cfEvaluator); } else {
// dataFormatter = new DataFormatter();
// } }
// DATA_FORMATTER_THREAD_LOCAL.set(dataFormatter);
// private String getFormattedDateString(Double data,String dataFormatString) { }
// return dataFormatter.format(data, dataFormat, dataFormatString);
//
// if (cell == null) { }
// return null;
// } @Override
// Format dateFormat = getFormat(cell, cfEvaluator); public void removeThreadLocalCache() {
// synchronized (dateFormat) { DATA_FORMATTER_THREAD_LOCAL.remove();
// 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;
// }
//
//}

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.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; 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.Cell;
import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil; 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.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Ignore; import org.junit.Ignore;
@ -17,7 +21,9 @@ import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.easyexcel.test.core.dataformat.DateFormatData;
import com.alibaba.easyexcel.test.temp.Lock2Test; import com.alibaba.easyexcel.test.temp.Lock2Test;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
@ -124,4 +130,61 @@ public class DataFormatTest {
System.out.println("end:" + (System.currentTimeMillis() - start)); 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.FileOutputStream;
import java.io.IOException; 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 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.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow; import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFSheet;
@ -17,11 +13,8 @@ import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
/** /**
* 测试poi * 测试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