diff --git a/README.md b/README.md
index 1251ebd2..72cc0164 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja
/**
* 最简单的读
*
1. 创建excel对应的实体对象 参照{@link DemoData}
- *
2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
+ *
2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
*
3. 直接读即可
*/
@Test
@@ -81,7 +81,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja
/**
* 文件上传
*
1. 创建excel对应的实体对象 参照{@link UploadData}
- *
2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
+ *
2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
*
3. 直接读即可
*/
@PostMapping("upload")
diff --git a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java
new file mode 100644
index 00000000..7b3c5bb8
--- /dev/null
+++ b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java
@@ -0,0 +1,204 @@
+package com.alibaba.excel.constant;
+
+import com.alibaba.excel.util.StringUtils;
+
+/**
+ * Excel's built-in format conversion.Currently only supports Chinese.
+ *
+ *
+ * If it is not Chinese, it is recommended to directly modify the builtinFormats, which will better support
+ * internationalization in the future.
+ *
+ *
+ * Specific correspondence please see:
+ * https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.numberingformat?view=openxml-2.8.1
+ *
+ * @author Jiaju Zhuang
+ **/
+public class BuiltinFormats {
+
+ public static String[] builtinFormats = {
+ // 0
+ "General",
+ // 1
+ "0",
+ // 2
+ "0.00",
+ // 3
+ "#,##0",
+ // 4
+ "#,##0.00",
+ // 5
+ "\"$\"#,##0_);(\"$\"#,##0)",
+ // 6
+ "\"$\"#,##0_);[Red](\"$\"#,##0)",
+ // 7
+ "\"$\"#,##0.00_);(\"$\"#,##0.00)",
+ // 8
+ "\"$\"#,##0.00_);[Red](\"$\"#,##0.00)",
+ // 9
+ "0%",
+ // 10
+ "0.00%",
+ // 11
+ "0.00E+00",
+ // 12
+ "# ?/?",
+ // 13
+ "# ??/??",
+ // 14
+ "m/d/yy",
+ // 15
+ "d-mmm-yy",
+ // 16
+ "d-mmm",
+ // 17
+ "mmm-yy",
+ // 18
+ "h:mm AM/PM",
+ // 19
+ "h:mm:ss AM/PM",
+ // 20
+ "h:mm",
+ // 21
+ "h:mm:ss",
+ // 22
+ "m/d/yy h:mm",
+ // 23-26 No specific correspondence found in the official documentation.
+ // 23
+ null,
+ // 24
+ null,
+ // 25
+ null,
+ // 26
+ null,
+ // 27
+ "yyyy\"5E74\"m\"6708\"",
+ // 28
+ "m\"6708\"d\"65E5\"",
+ // 29
+ "m\"6708\"d\"65E5\"",
+ // 30
+ "m-d-yy",
+ // 31
+ "yyyy\"5E74\"m\"6708\"d\"65E5\"",
+ // 32
+ "h\"65F6\"mm\"5206\"",
+ // 33
+ "h\"65F6\"mm\"5206\"ss\"79D2\"",
+ // 34
+ "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"",
+ // 35
+ "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"",
+ // 36
+ "yyyy\"5E74\"m\"6708\"",
+ // 37
+ "#,##0_);(#,##0)",
+ // 38
+ "#,##0_);[Red](#,##0)",
+ // 39
+ "#,##0.00_);(#,##0.00)",
+ // 40
+ "#,##0.00_);[Red](#,##0.00)",
+ // 41
+ "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)",
+ // 42
+ "_(\"$\"* #,##0_);_(\"$\"* (#,##0);_(\"$\"* \"-\"_);_(@_)",
+ // 43
+ "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)",
+ // 44
+ "_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)",
+ // 45
+ "mm:ss",
+ // 46
+ "[h]:mm:ss",
+ // 47
+ "mm:ss.0",
+ // 48
+ "##0.0E+0",
+ // 49
+ "@",
+ // 50
+ "yyyy\"5E74\"m\"6708\"",
+ // 51
+ "m\"6708\"d\"65E5\"",
+ // 52
+ "yyyy\"5E74\"m\"6708\"",
+ // 53
+ "m\"6708\"d\"65E5\"",
+ // 54
+ "m\"6708\"d\"65E5\"",
+ // 55
+ "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"",
+ // 56
+ "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"",
+ // 57
+ "yyyy\"5E74\"m\"6708\"",
+ // 58
+ "m\"6708\"d\"65E5\"",
+ // 59
+ "t0",
+ // 60
+ "t0.00",
+ // 61
+ "t#,##0",
+ // 62
+ "t#,##0.00",
+ // 63-66 No specific correspondence found in the official documentation.
+ // 63
+ null,
+ // 64
+ null,
+ // 65
+ null,
+ // 66
+ null,
+ // 67
+ "t0%",
+ // 68
+ "t0.00%",
+ // 69
+ "t# ?/?",
+ // 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",
+ // 81
+ "d/m/bb",
+ // end
+ };
+
+ public static String getBuiltinFormat(Integer index) {
+ if (index == null || index < 0 || index >= builtinFormats.length) {
+ return null;
+ }
+ return builtinFormats[index];
+ }
+
+ public static String getFormat(Integer index, String format) {
+ if (!StringUtils.isEmpty(format)) {
+ return format;
+ }
+ return getBuiltinFormat(index);
+ }
+
+}
diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java
index a581a955..dfc8d20a 100644
--- a/src/main/java/com/alibaba/excel/util/DateUtils.java
+++ b/src/main/java/com/alibaba/excel/util/DateUtils.java
@@ -1,10 +1,15 @@
package com.alibaba.excel.util;
+import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
-import com.alibaba.excel.exception.ExcelDataConvertException;
+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;
/**
* Date utils
@@ -12,6 +17,9 @@ import com.alibaba.excel.exception.ExcelDataConvertException;
* @author Jiaju Zhuang
**/
public class DateUtils {
+
+
+
public static final String DATE_FORMAT_10 = "yyyy-MM-dd";
public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss";
public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss";
@@ -101,4 +109,194 @@ public class DateUtils {
}
return new SimpleDateFormat(dateFormat).format(date);
}
+
+//
+// /**
+// * 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;
+// }
}
diff --git a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java
new file mode 100644
index 00000000..6a4b9c55
--- /dev/null
+++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java
@@ -0,0 +1,154 @@
+//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 null
if the any of the
+// * following is true:
+// *
+// * the cell's style is null
+// * the style's data format string is null or empty
+// * the format string cannot be recognized as either a number or date
+// *
+// *
+// * @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;
+// }
+//
+//}
diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
index 0dc22aa7..c3f4ada5 100644
--- a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
+++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
@@ -33,7 +33,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象 参照{@link DemoData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
*
* 3. 直接读即可
*/
@@ -60,7 +60,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
*
* 3. 直接读即可
*/
@@ -76,7 +76,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象 参照{@link DemoData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
*
* 3. 直接读即可
*/
@@ -108,7 +108,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener}
*
* 3. 直接读即可
*/
@@ -130,7 +130,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象 参照{@link DemoData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
*
* 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,
* 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。
@@ -150,7 +150,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象 参照{@link DemoData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
*
* 3. 直接读即可
*/
@@ -167,7 +167,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象 参照{@link DemoData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
*
* 3. 直接读即可
*/
@@ -184,7 +184,7 @@ public class ReadTest {
*
* 1. 创建excel对应的实体对象 参照{@link ExceptionDemoData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener}
*
* 3. 直接读即可
*/
diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
index 98f400a4..b80022d9 100644
--- a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
+++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
@@ -85,7 +85,7 @@ public class WebTest {
*
* 1. 创建excel对应的实体对象 参照{@link UploadData}
*
- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
+ * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
*
* 3. 直接读即可
*/
diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java
index 052b60b6..1682aa17 100644
--- a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java
+++ b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java
@@ -29,7 +29,7 @@ public class Lock2Test {
@Test
public void test() throws Exception {
- File file = new File("D:\\test\\000001.xlsx");
+ File file = new File("D:\\test\\headt1.xlsx");
List list = EasyExcel.read(file).sheet().headRowNumber(0).doReadSync();
LOGGER.info("数据:{}", list.size());
diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java
new file mode 100644
index 00000000..d057d8e7
--- /dev/null
+++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java
@@ -0,0 +1,16 @@
+package com.alibaba.easyexcel.test.temp.dataformat;
+
+import com.alibaba.excel.metadata.CellData;
+
+import lombok.Data;
+
+/**
+ * TODO
+ *
+ * @author 罗成
+ **/
+@Data
+public class DataFormatData {
+ private CellData date;
+ private CellData num;
+}
diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java
new file mode 100644
index 00000000..d9c7bb23
--- /dev/null
+++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java
@@ -0,0 +1,127 @@
+package com.alibaba.easyexcel.test.temp.dataformat;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+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.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.easyexcel.test.temp.Lock2Test;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.fastjson.JSON;
+
+/**
+ * 格式测试
+ *
+ * @author Jiaju Zhuang
+ **/
+@Ignore
+public class DataFormatTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Lock2Test.class);
+
+ @Test
+ public void test() throws Exception {
+ File file = new File("D:\\test\\dataformat.xlsx");
+
+ List list =
+ EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync();
+ LOGGER.info("数据:{}", list.size());
+ for (DataFormatData data : list) {
+ Integer dataFormat = data.getDate().getDataFormat();
+
+ String dataFormatString = data.getDate().getDataFormatString();
+
+ if (dataFormat == null || dataFormatString == null) {
+
+ } else {
+ LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString,
+ DateUtil.isADateFormat(dataFormat, dataFormatString));
+ }
+
+ LOGGER.info("返回数据:{}", JSON.toJSONString(data));
+ }
+ }
+
+ @Test
+ public void testxls() throws Exception {
+ File file = new File("D:\\test\\dataformat.xls");
+
+ List list =
+ EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync();
+ LOGGER.info("数据:{}", list.size());
+ for (DataFormatData data : list) {
+ Integer dataFormat = data.getDate().getDataFormat();
+
+ String dataFormatString = data.getDate().getDataFormatString();
+
+ if (dataFormat == null || dataFormatString == null) {
+
+ } else {
+ LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString,
+ DateUtil.isADateFormat(dataFormat, dataFormatString));
+ }
+
+ LOGGER.info("返回数据:{}", JSON.toJSONString(data));
+ }
+ }
+
+ @Test
+ public void test3() throws IOException {
+ String file = "D:\\test\\dataformat1.xlsx";
+ XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file);
+ Sheet xssfSheet = xssfWorkbook.getSheetAt(0);
+ Cell cell = xssfSheet.getRow(0).getCell(0);
+ DataFormatter d = new DataFormatter();
+ System.out.println(d.formatCellValue(cell));
+ }
+
+ @Test
+ public void test31() throws IOException {
+ System.out.println(DateUtil.isADateFormat(181, "[DBNum1][$-404]m\"\u6708\"d\"\u65e5\";@"));
+ }
+
+ @Test
+ public void test43() throws IOException {
+ SimpleDateFormat s = new SimpleDateFormat("yyyy'年'm'月'd'日' h'点'mm'哈哈哈m'");
+ System.out.println(s.format(new Date()));
+ }
+
+ @Test
+ public void test463() throws IOException {
+ SimpleDateFormat s = new SimpleDateFormat("[$-804]yyyy年m月");
+ System.out.println(s.format(new Date()));
+ }
+
+ @Test
+ public void test1() throws Exception {
+ System.out.println(DateUtil.isADateFormat(181, "yyyy\"年啊\"m\"月\"d\"日\"\\ h"));
+ System.out.println(DateUtil.isADateFormat(180, "yyyy\"年\"m\"月\"d\"日\"\\ h\"点\""));
+ }
+
+ @Test
+ public void test2() throws Exception {
+ List list1 = new ArrayList(3000);
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < 10000; i++) {
+ list1.clear();
+ }
+ System.out.println("end:" + (System.currentTimeMillis() - start));
+ start = System.currentTimeMillis();
+ for (int i = 0; i < 10000; i++) {
+ list1 = new ArrayList(3000);
+ }
+ System.out.println("end:" + (System.currentTimeMillis() - start));
+ }
+
+}
diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java
new file mode 100644
index 00000000..0239ed5b
--- /dev/null
+++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java
@@ -0,0 +1,1292 @@
+/*
+ * ==================================================================== 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.easyexcel.test.temp.dataformat;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DateFormat;
+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.text.SimpleDateFormat;
+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.Observable;
+import java.util.Observer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.poi.ss.format.CellFormat;
+import org.apache.poi.ss.format.CellFormatResult;
+import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.ExcelGeneralNumberFormat;
+import org.apache.poi.ss.usermodel.ExcelNumberFormat;
+import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter;
+import org.apache.poi.ss.usermodel.FormulaError;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.FractionFormat;
+import org.apache.poi.ss.util.DateFormatConverter;
+import org.apache.poi.ss.util.NumberToTextConverter;
+import org.apache.poi.util.LocaleUtil;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * DataFormatter contains methods for formatting the value stored in an Cell. This can be useful for reports and GUI
+ * presentations when you need to display data exactly as it appears in Excel. Supported formats include currency, SSN,
+ * percentages, decimals, dates, phone numbers, zip codes, etc.
+ *
+ * Internally, formats will be implemented using subclasses of {@link Format} such as {@link DecimalFormat} and
+ * {@link java.text.SimpleDateFormat}. Therefore the formats used by this class must obey the same pattern rules as
+ * these Format subclasses. This means that only legal number pattern characters ("0", "#", ".", "," etc.) may appear in
+ * number formats. Other characters can be inserted before or after the number pattern to form a
+ * prefix or suffix.
+ *
+ *
+ * For example the Excel pattern "$#,##0.00 "USD"_);($#,##0.00 "USD")"
+ *
will be correctly formatted as "$1,000.00 USD" or "($1,000.00 USD)". However the pattern
+ * "00-00-00"
is incorrectly formatted by DecimalFormat as "000000--". For Excel formats that are not
+ * compatible with DecimalFormat, you can provide your own custom {@link Format} implementation via
+ * DataFormatter.addFormat(String,Format)
. The following custom formats are already provided by this class:
+ *
+ *
+ *
+ * SSN "000-00-0000"
+ * Phone Number "(###) ###-####"
+ * Zip plus 4 "00000-0000"
+ *
+ *
+ *
+ * If the Excel format pattern cannot be parsed successfully, then a default format will be used. The default number
+ * format will mimic the Excel General format: "#" for whole numbers and "#.##########" for decimal numbers. You can
+ * override the default format pattern with
+ * DataFormatter.setDefaultNumberFormat(Format)
. Note: the default format will only be used when a Format
+ * cannot be created from the cell's data format string.
+ *
+ *
+ * Note that by default formatted numeric values are trimmed. Excel formats can contain spacers and padding and the
+ * default behavior is to strip them off.
+ *
+ *
+ * Example:
+ *
+ *
+ * Consider a numeric cell with a value 12.343
and format "##.##_ "
. The trailing underscore
+ * and space ("_ ") in the format adds a space to the end and Excel formats this cell as "12.34 "
, but
+ * DataFormatter
trims the formatted value and returns "12.34"
.
+ *
+ * You can enable spaces by passing the emulateCSV=true
flag in the DateFormatter
cosntructor.
+ * If set to true, then the output tries to conform to what you get when you take an xls or xlsx in Excel and Save As
+ * CSV file:
+ *
+ * returned values are not trimmed
+ * Invalid dates are formatted as 255 pound signs ("#")
+ * simulate Excel's handling of a format string of all # when the value is 0. Excel will output "",
+ * DataFormatter
will output "0".
+ *
+ *
+ * Some formats are automatically "localized" by Excel, eg show as mm/dd/yyyy when loaded in Excel in some Locales but
+ * as dd/mm/yyyy in others. These are always returned in the "default" (US) format, as stored in the file. Some format
+ * strings request an alternate locale, eg [$-809]d/m/yy h:mm AM/PM
which explicitly requests UK locale.
+ * These locale directives are (currently) ignored. You can use {@link DateFormatConverter} to do some of this
+ * localisation if you need it.
+ */
+public class DataFormatter1 implements Observer {
+ 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("((A|P)[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\\])", 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 date format, if no date format was given
+ */
+ private DateFormat defaultDateformat;
+
+ /** General format for numbers. */
+ private Format generalNumberFormat;
+
+ /** A default format to use when a number pattern cannot be parsed. */
+ private Format defaultNumFormat;
+
+ /**
+ * A map to cache formats. Map formats
+ */
+ private final Map formats = new HashMap();
+
+ private final boolean emulateCSV;
+
+ /** stores the locale valid it the last formatting call */
+ private Locale locale;
+
+ /** stores if the locale should change according to {@link LocaleUtil#getUserLocale()} */
+ private boolean localeIsAdapting;
+
+ private class LocaleChangeObservable extends Observable {
+ void checkForLocaleChange() {
+ checkForLocaleChange(LocaleUtil.getUserLocale());
+ }
+
+ void checkForLocaleChange(Locale newLocale) {
+ if (!localeIsAdapting)
+ return;
+ if (newLocale.equals(locale))
+ return;
+ super.setChanged();
+ notifyObservers(newLocale);
+ }
+ }
+
+ /** the Observable to notify, when the locale has been changed */
+ private final LocaleChangeObservable localeChangedObservable = new LocaleChangeObservable();
+
+ /** For logging any problems we find */
+ private static POILogger logger = POILogFactory.getLogger(DataFormatter.class);
+
+ /**
+ * Creates a formatter using the {@link Locale#getDefault() default locale}.
+ */
+ public DataFormatter1() {
+ this(false);
+ }
+
+ /**
+ * Creates a formatter using the {@link Locale#getDefault() default locale}.
+ *
+ * @param emulateCSV
+ * whether to emulate CSV output.
+ */
+ public DataFormatter1(boolean emulateCSV) {
+ this(LocaleUtil.getUserLocale(), true, emulateCSV);
+ }
+
+ /**
+ * Creates a formatter using the given locale.
+ */
+ public DataFormatter1(Locale locale) {
+ this(locale, false);
+ }
+
+ /**
+ * Creates a formatter using the given locale.
+ *
+ * @param emulateCSV
+ * whether to emulate CSV output.
+ */
+ public DataFormatter1(Locale locale, boolean emulateCSV) {
+ this(locale, false, emulateCSV);
+ }
+
+ /**
+ * Creates a formatter using the given locale.
+ *
+ * @param localeIsAdapting
+ * (true only if locale is not user-specified)
+ * @param emulateCSV
+ * whether to emulate CSV output.
+ */
+ private DataFormatter1(Locale locale, boolean localeIsAdapting, boolean emulateCSV) {
+ this.localeIsAdapting = true;
+ localeChangedObservable.addObserver(this);
+ // localeIsAdapting must be true prior to this first checkForLocaleChange call.
+ localeChangedObservable.checkForLocaleChange(locale);
+ // set localeIsAdapting so subsequent checks perform correctly
+ // (whether a specific locale was provided to this DataFormatter or DataFormatter should
+ // adapt to the current user locale as the locale changes)
+ this.localeIsAdapting = localeIsAdapting;
+ this.emulateCSV = emulateCSV;
+ }
+
+ /**
+ * Return a Format for the given cell if one exists, otherwise try to create one. This method will return
+ * null
if the any of the following is true:
+ *
+ * the cell's style is null
+ * the style's data format string is null or empty
+ * the format string cannot be recognized as either a number or date
+ *
+ *
+ * @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);
+ }
+
+ private Format getFormat(double cellValue, int formatIndex, String formatStrIn) {
+ 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);
+ }
+ // Wrap and return (non-cachable - CellFormat does that)
+ return new 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;
+ }
+
+ /**
+ * Create and return a Format based on the format string from a cell's style. If the pattern cannot be parsed,
+ * return a default pattern.
+ *
+ * @param cell
+ * The Excel cell
+ * @return A Format representing the excel format. May return null.
+ */
+ public Format createFormat(Cell cell) {
+
+ int formatIndex = cell.getCellStyle().getDataFormat();
+ String formatStr = cell.getCellStyle().getDataFormatString();
+ return createFormat(cell.getNumericCellValue(), formatIndex, formatStr);
+ }
+
+ private Format createFormat(double cellValue, int formatIndex, String sFormat) {
+ localeChangedObservable.checkForLocaleChange();
+
+ String formatStr = sFormat;
+
+ // 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('$'), symbol.length());
+ }
+ formatStr = m.replaceAll(symbol);
+ m = localePatternGroup.matcher(formatStr);
+ }
+
+ // Check for special cases
+ if (formatStr == null || formatStr.trim().length() == 0) {
+ return getDefaultFormat(cellValue);
+ }
+
+ if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
+ return generalNumberFormat;
+ }
+
+ if ("".equals("")||(DateUtil.isADateFormat(formatIndex, formatStr) && DateUtil.isValidExcelDate(cellValue))) {
+ return createDateFormat(formatStr, cellValue);
+ }
+ // 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("\\?", "#");
+ // System.out.println("formatStr: "+strippedFormatStr);
+ return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat);
+ }
+
+ if (numPattern.matcher(formatStr).find()) {
+ return createNumberFormat(formatStr, cellValue);
+ }
+
+ if (emulateCSV) {
+ return new ConstantStringFormat(cleanFormatForNumber(formatStr));
+ }
+ // TODO - when does this occur?
+ return null;
+ }
+
+ private Format createDateFormat(String pFormatStr, double cellValue) {
+ 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
+
+ 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 ms = new ArrayList();
+ 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.log(POILogger.DEBUG, "Formatting failed for format " + formatStr + ", falling back", iae);
+ // the pattern could not be parsed correctly,
+ // so fall back to the default number format
+ return getDefaultFormat(cellValue);
+ }
+
+ }
+
+ private String cleanFormatForNumber(String formatStr) {
+ StringBuilder sb = new StringBuilder(formatStr);
+
+ if (emulateCSV) {
+ // Requested spacers with "_" are replaced by a single space.
+ // Full-column-width padding "*" are removed.
+ // Not processing fractions at this time. Replace ? with space.
+ // This matches CSV output.
+ for (int i = 0; i < sb.length(); i++) {
+ char c = sb.charAt(i);
+ if (c == '_' || c == '*' || c == '?') {
+ if (i > 0 && sb.charAt((i - 1)) == '\\') {
+ // It's escaped, don't worry
+ continue;
+ }
+ if (c == '?') {
+ sb.setCharAt(i, ' ');
+ } else if (i < sb.length() - 1) {
+ // Remove the character we're supposed
+ // to match the space of / pad to the
+ // column width with
+ if (c == '_') {
+ sb.setCharAt(i + 1, ' ');
+ } else {
+ sb.deleteCharAt(i + 1);
+ }
+ // Remove the character too
+ sb.deleteCharAt(i);
+ i--;
+ }
+ }
+ }
+ } else {
+ // 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 final 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, double cellValue) {
+ 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.log(POILogger.DEBUG, "Formatting failed for format " + formatStr + ", falling back", iae);
+ // the pattern could not be parsed correctly,
+ // so fall back to the default number format
+ return getDefaultFormat(cellValue);
+ }
+ }
+
+ /**
+ * Returns a default format for a cell.
+ *
+ * @param cell
+ * The cell
+ * @return a default format
+ */
+ public Format getDefaultFormat(Cell cell) {
+ return getDefaultFormat(cell.getNumericCellValue());
+ }
+
+ private Format getDefaultFormat(double cellValue) {
+ localeChangedObservable.checkForLocaleChange();
+
+ // for numeric cells try user supplied default
+ if (defaultNumFormat != null) {
+ return defaultNumFormat;
+
+ // otherwise use general format
+ }
+ return generalNumberFormat;
+ }
+
+ /**
+ * Performs Excel-style date formatting, using the supplied Date and format
+ */
+ private String performDateFormatting(Date d, Format dateFormat) {
+ return (dateFormat != null ? dateFormat : defaultDateformat).format(d);
+ }
+
+ /**
+ * Returns the formatted value of an Excel date as a String based on the cell's DataFormat
.
+ * i.e. "Thursday, January 02, 2003" , "01/02/2003" , "02-Jan" , etc.
+ *
+ * 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 cell
+ * to format
+ * @param cfEvaluator
+ * ConditionalFormattingEvaluator (if available)
+ * @return Formatted value
+ */
+ private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
+ Format dateFormat = getFormat(cell, cfEvaluator);
+ if (dateFormat instanceof ExcelStyleDateFormatter) {
+ // Hint about the raw excel value
+ ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue());
+ }
+ Date d = cell.getDateCellValue();
+ return performDateFormatting(d, dateFormat);
+ }
+
+ /**
+ * Returns the formatted value of an Excel number as a String based on the cell's DataFormat
.
+ * Supported formats include currency, percents, decimals, phone number, SSN, etc.: "61.54%", "$100.00", "(800)
+ * 555-1234".
+ *
+ * Format comes from either the highest priority conditional format rule with a specified format, or from the cell
+ * style.
+ *
+ * @param cell
+ * The cell
+ * @param cfEvaluator
+ * if available, or null
+ * @return a formatted number string
+ */
+ private String getFormattedNumberString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
+
+ Format numberFormat = getFormat(cell, cfEvaluator);
+ double d = cell.getNumericCellValue();
+ if (numberFormat == null) {
+ return String.valueOf(d);
+ }
+ String formatted = numberFormat.format(new Double(d));
+ return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation
+ }
+
+ /**
+ * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules.
+ *
+ * @see #formatCellValue(Cell)
+ */
+ public String formatRawCellContents(double value, int formatIndex, String formatString) {
+ return formatRawCellContents(value, formatIndex, formatString, false);
+ }
+
+ /**
+ * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules.
+ *
+ * @see #formatCellValue(Cell)
+ */
+ public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) {
+ localeChangedObservable.checkForLocaleChange();
+
+ // Is it a date?
+ if (DateUtil.isADateFormat(formatIndex, formatString)) {
+ if (DateUtil.isValidExcelDate(value)) {
+ Format dateFormat = getFormat(value, formatIndex, formatString);
+ if (dateFormat instanceof ExcelStyleDateFormatter) {
+ // Hint about the raw excel value
+ ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(value);
+ }
+ Date d = DateUtil.getJavaDate(value, use1904Windowing);
+ return performDateFormatting(d, dateFormat);
+ }
+ // RK: Invalid dates are 255 #s.
+ if (emulateCSV) {
+ return invalidDateTimeString;
+ }
+ }
+
+ // else Number
+ Format numberFormat = getFormat(value, formatIndex, formatString);
+ if (numberFormat == null) {
+ return String.valueOf(value);
+ }
+
+ // When formatting 'value', double to text to BigDecimal produces more
+ // accurate results than double to Double in JDK8 (as compared to
+ // previous versions). However, if the value contains E notation, this
+ // would expand the values, which we do not want, so revert to
+ // original method.
+ String result;
+ final String textValue = NumberToTextConverter.toText(value);
+ if (textValue.indexOf('E') > -1) {
+ result = numberFormat.format(new Double(value));
+ } else {
+ result = numberFormat.format(new BigDecimal(textValue));
+ }
+ // Complete scientific notation by adding the missing +.
+ if (result.indexOf('E') > -1 && !result.contains("E-")) {
+ result = result.replaceFirst("E", "E+");
+ }
+ return result;
+ }
+
+ /**
+ *
+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel format
+ * pattern cannot be parsed then the cell value will be formatted using a default format.
+ *
+ *
+ * When passed a null or blank cell, this method will return an empty String (""). Formulas in formula type cells
+ * will not be evaluated.
+ *
+ *
+ * @param cell
+ * The cell
+ * @return the formatted cell value as a String
+ */
+ public String formatCellValue(Cell cell) {
+ return formatCellValue(cell, null);
+ }
+
+ /**
+ *
+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number
+ * format pattern cannot be parsed then the cell value will be formatted using a default format.
+ *
+ *
+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated
+ * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula
+ * String will be returned. The caller is responsible for setting the currentRow on the evaluator
+ *
+ *
+ * @param cell
+ * The cell (can be null)
+ * @param evaluator
+ * The FormulaEvaluator (can be null)
+ * @return a string value of the cell
+ */
+ public String formatCellValue(Cell cell, FormulaEvaluator evaluator) {
+ return formatCellValue(cell, evaluator, null);
+ }
+
+ /**
+ *
+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number
+ * format pattern cannot be parsed then the cell value will be formatted using a default format.
+ *
+ *
+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated
+ * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula
+ * String will be returned. The caller is responsible for setting the currentRow on the evaluator
+ *
+ *
+ * When a ConditionalFormattingEvaluator is present, it is checked first to see if there is a number format to
+ * apply. If multiple rules apply, the last one is used. If no ConditionalFormattingEvaluator is present, no rules
+ * apply, or the applied rules do not define a format, the cell's style format is used.
+ *
+ *
+ * The two evaluators should be from the same context, to avoid inconsistencies in cached values.
+ *
+ *
+ * @param cell
+ * The cell (can be null)
+ * @param evaluator
+ * The FormulaEvaluator (can be null)
+ * @param cfEvaluator
+ * ConditionalFormattingEvaluator (can be null)
+ * @return a string value of the cell
+ */
+ public String formatCellValue(Cell cell, FormulaEvaluator evaluator, ConditionalFormattingEvaluator cfEvaluator) {
+ localeChangedObservable.checkForLocaleChange();
+
+ if (cell == null) {
+ return "";
+ }
+
+ CellType cellType = cell.getCellTypeEnum();
+ if (cellType == CellType.FORMULA) {
+ if (evaluator == null) {
+ return cell.getCellFormula();
+ }
+ cellType = evaluator.evaluateFormulaCellEnum(cell);
+ }
+ switch (cellType) {
+ case NUMERIC:
+
+// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) {
+ return getFormattedDateString(cell, cfEvaluator);
+// }
+// return getFormattedNumberString(cell, cfEvaluator);
+
+ case STRING:
+ return cell.getRichStringCellValue().getString();
+
+ case BOOLEAN:
+ return cell.getBooleanCellValue() ? "TRUE" : "FALSE";
+ case BLANK:
+ return "";
+ case ERROR:
+ return FormulaError.forInt(cell.getErrorCellValue()).getString();
+ default:
+ throw new RuntimeException("Unexpected celltype (" + cellType + ")");
+ }
+ }
+
+ /**
+ *
+ * Sets a default number format to be used when the Excel format cannot be parsed successfully. Note: This is
+ * a fall back for when an error occurs while parsing an Excel number format pattern. This will not affect cells
+ * with the General format.
+ *
+ *
+ * The value that will be passed to the Format's format method (specified by java.text.Format#format
)
+ * will be a double value from a numeric cell. Therefore the code in the format method should expect a
+ * Number
value.
+ *
+ *
+ * @param format
+ * A Format instance to be used as a default
+ * @see java.text.Format#format
+ */
+ public void setDefaultNumberFormat(Format format) {
+ for (Map.Entry entry : formats.entrySet()) {
+ if (entry.getValue() == generalNumberFormat) {
+ entry.setValue(format);
+ }
+ }
+ defaultNumFormat = format;
+ }
+
+ /**
+ * Adds a new format to the available formats.
+ *
+ * The value that will be passed to the Format's format method (specified by java.text.Format#format
)
+ * will be a double value from a numeric cell. Therefore the code in the format method should expect a
+ * Number
value.
+ *
+ *
+ * @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 DecimalFormat with parseIntegerOnly set true
+ */
+ 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);
+ }
+
+ /**
+ * If the Locale has been changed via {@link LocaleUtil#setUserLocale(Locale)} the stored formats need to be
+ * refreshed. All formats which aren't originated from DataFormatter itself, i.e. all Formats added via
+ * {@link DataFormatter#addFormat(String, Format)} and {@link DataFormatter#setDefaultNumberFormat(Format)}, need to
+ * be added again. To notify callers, the returned {@link Observable} should be used. The Object in
+ * {@link Observer#update(Observable, Object)} is the new Locale.
+ *
+ * @return the listener object, where callers can register themselves
+ */
+ public Observable getLocaleChangedObservable() {
+ return localeChangedObservable;
+ }
+
+ /**
+ * Update formats when locale has been changed
+ *
+ * @param observable
+ * usually this is our own Observable instance
+ * @param localeObj
+ * only reacts on Locale objects
+ */
+ public void update(Observable observable, Object localeObj) {
+ if (!(localeObj instanceof Locale))
+ return;
+ Locale newLocale = (Locale)localeObj;
+ if (!localeIsAdapting || newLocale.equals(locale))
+ return;
+
+ locale = newLocale;
+
+ dateSymbols = DateFormatSymbols.getInstance(locale);
+ decimalSymbols = DecimalFormatSymbols.getInstance(locale);
+ generalNumberFormat = new ExcelGeneralNumberFormat(locale);
+
+ // taken from Date.toString()
+ defaultDateformat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", dateSymbols);
+ defaultDateformat.setTimeZone(LocaleUtil.getUserTimeZone());
+
+ // init built-in formats
+
+ formats.clear();
+ Format zipFormat = ZipPlusFourFormat.instance;
+ addFormat("00000\\-0000", zipFormat);
+ addFormat("00000-0000", zipFormat);
+
+ Format phoneFormat = PhoneFormat.instance;
+ // allow for format string variations
+ addFormat("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####", phoneFormat);
+ addFormat("[<=9999999]###-####;(###) ###-####", phoneFormat);
+ addFormat("###\\-####;\\(###\\)\\ ###\\-####", phoneFormat);
+ addFormat("###-####;(###) ###-####", phoneFormat);
+
+ Format ssnFormat = SSNFormat.instance;
+ addFormat("000\\-00\\-0000", ssnFormat);
+ addFormat("000-00-0000", ssnFormat);
+ }
+
+ /**
+ * 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 {
+ public static final Format instance = new SSNFormat();
+ 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 {
+ public static final Format instance = new ZipPlusFourFormat();
+ 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 {
+ public static final Format instance = new PhoneFormat();
+ 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);
+ }
+ }
+
+ /**
+ * Format class that does nothing and always returns a constant string.
+ *
+ * This format is used to simulate Excel's handling of a format string of all # when the value is 0. Excel will
+ * output "", Java will output "0".
+ *
+ */
+ @SuppressWarnings("serial")
+ private static final class ConstantStringFormat extends Format {
+ private static final DecimalFormat df = createIntegerOnlyFormat("##########");
+ private final String str;
+
+ public ConstantStringFormat(String s) {
+ str = s;
+ }
+
+ @Override
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ return toAppendTo.append(str);
+ }
+
+ @Override
+ public Object parseObject(String source, ParsePosition pos) {
+ return df.parseObject(source, pos);
+ }
+ }
+
+ /**
+ * Workaround until we merge {@link DataFormatter} with {@link CellFormat}. Constant, non-cachable wrapper around a
+ * {@link CellFormatResult}
+ */
+ @SuppressWarnings("serial")
+ private final class CellFormatResultWrapper extends Format {
+ private final CellFormatResult result;
+
+ private CellFormatResultWrapper(CellFormatResult result) {
+ this.result = result;
+ }
+
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ if (emulateCSV) {
+ return toAppendTo.append(result.text);
+ } else {
+ return toAppendTo.append(result.text.trim());
+ }
+ }
+
+ public Object parseObject(String source, ParsePosition pos) {
+ return null; // Not supported
+ }
+ }
+}
diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java
index b19ac81a..44bc943f 100644
--- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java
+++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java
@@ -64,7 +64,7 @@ public class Wirte {
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
- data.setString("字符串" + i);
+ data.setString("640121807369666560" + i);
data.setDate(new Date());
data.setDoubleData(null);
list.add(data);