From 9c7e06022b0e32d155e2a0fa8808b94791c15188 Mon Sep 17 00:00:00 2001
From: Jiaju Zhuang <zhuangjiaju@qq.com>
Date: Mon, 23 Dec 2019 19:10:58 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=A5=E6=9C=9F=E8=BD=AC?=
 =?UTF-8?q?=E6=8D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pom.xml                                       |   5 +
 .../v03/handlers/NumberRecordHandler.java     |  10 +-
 .../v07/handlers/DefaultCellHandler.java      |   7 +-
 .../excel/constant/BuiltinFormats.java        |  62 +-
 .../string/StringNumberConverter.java         |  12 +-
 .../excel/metadata/BasicParameter.java        |  13 +
 .../alibaba/excel/metadata/DataFormatter.java | 786 ++++++++++++++++++
 .../excel/metadata/GlobalConfiguration.java   |  15 +
 .../com/alibaba/excel/util/DateUtils.java     | 408 ++++-----
 .../excel/util/NumberDataFormatterUtils.java  | 201 ++---
 .../excel/util/ThreadLocalCachedUtils.java    |  14 +
 .../test/core/dataformat/DateFormatData.java  |  14 +
 .../test/core/dataformat/DateFormatTest.java  |  50 ++
 .../test/temp/dataformat/DataFormatTest.java  |  63 ++
 .../easyexcel/test/temp/poi/PoiWriteTest.java |  61 --
 src/test/resources/dataformat/dataformat.xlsx | Bin 0 -> 11489 bytes
 16 files changed, 1263 insertions(+), 458 deletions(-)
 create mode 100644 src/main/java/com/alibaba/excel/metadata/DataFormatter.java
 create mode 100644 src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java
 create mode 100644 src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java
 create mode 100644 src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java
 create mode 100644 src/test/resources/dataformat/dataformat.xlsx

diff --git a/pom.xml b/pom.xml
index e85d0449..7e2235cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,11 @@
             <artifactId>poi-ooxml</artifactId>
             <version>3.17</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml-schemas</artifactId>
+            <version>3.17</version>
+        </dependency>
         <dependency>
             <groupId>cglib</groupId>
             <artifactId>cglib</artifactId>
diff --git a/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java
index 75c3128a..d8f35240 100644
--- a/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java
+++ b/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java
@@ -7,6 +7,7 @@ import org.apache.poi.hssf.record.NumberRecord;
 import org.apache.poi.hssf.record.Record;
 
 import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler;
+import com.alibaba.excel.constant.BuiltinFormats;
 import com.alibaba.excel.metadata.CellData;
 
 /**
@@ -32,8 +33,13 @@ public class NumberRecordHandler extends AbstractXlsRecordHandler {
         this.row = numrec.getRow();
         this.column = numrec.getColumn();
         this.cellData = new CellData(BigDecimal.valueOf(numrec.getValue()));
-        this.cellData.setDataFormat(formatListener.getFormatIndex(numrec));
-        this.cellData.setDataFormatString(formatListener.getFormatString(numrec));
+        int dataFormat = formatListener.getFormatIndex(numrec);
+        this.cellData.setDataFormat(dataFormat);
+        if (dataFormat <= BuiltinFormats.builtinFormats.length) {
+            this.cellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat));
+        } else {
+            this.cellData.setDataFormatString(formatListener.getFormatString(numrec));
+        }
     }
 
     @Override
diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java
index e0b9c413..7312b2ca 100644
--- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java
+++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java
@@ -13,7 +13,6 @@ import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.Map;
 
-import org.apache.poi.ss.usermodel.BuiltinFormats;
 import org.apache.poi.xssf.model.StylesTable;
 import org.apache.poi.xssf.usermodel.XSSFCellStyle;
 import org.apache.poi.xssf.usermodel.XSSFRichTextString;
@@ -21,6 +20,7 @@ import org.xml.sax.Attributes;
 
 import com.alibaba.excel.analysis.v07.XlsxCellHandler;
 import com.alibaba.excel.analysis.v07.XlsxRowResultHolder;
+import com.alibaba.excel.constant.BuiltinFormats;
 import com.alibaba.excel.constant.ExcelXmlConstants;
 import com.alibaba.excel.context.AnalysisContext;
 import com.alibaba.excel.enums.CellDataTypeEnum;
@@ -87,12 +87,11 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder
                 int dateFormatIndexInteger = Integer.parseInt(dateFormatIndex);
                 XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger);
                 int dataFormat = xssfCellStyle.getDataFormat();
-                String dataFormatString = xssfCellStyle.getDataFormatString();
                 currentCellData.setDataFormat(dataFormat);
-                if (dataFormatString == null) {
+                if (dataFormat <= BuiltinFormats.builtinFormats.length) {
                     currentCellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat));
                 } else {
-                    currentCellData.setDataFormatString(dataFormatString);
+                    currentCellData.setDataFormatString(xssfCellStyle.getDataFormatString());
                 }
             }
         }
diff --git a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java
index 7b3c5bb8..5663054e 100644
--- a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java
+++ b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java
@@ -47,7 +47,8 @@ public class BuiltinFormats {
         // 13
         "# ??/??",
         // 14
-        "m/d/yy",
+        // The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d".
+        "yyyy/m/d",
         // 15
         "d-mmm-yy",
         // 16
@@ -63,7 +64,8 @@ public class BuiltinFormats {
         // 21
         "h:mm:ss",
         // 22
-        "m/d/yy h:mm",
+        // The official documentation shows "m/d/yy h:mm", but the actual test is "yyyy/m/d h:mm".
+        "yyyy/m/d h:mm",
         // 23-26 No specific correspondence found in the official documentation.
         // 23
         null,
@@ -74,25 +76,25 @@ public class BuiltinFormats {
         // 26
         null,
         // 27
-        "yyyy\"5E74\"m\"6708\"",
+        "yyyy\"年\"m\"月\"",
         // 28
-        "m\"6708\"d\"65E5\"",
+        "m\"月\"d\"日\"",
         // 29
-        "m\"6708\"d\"65E5\"",
+        "m\"月\"d\"日\"",
         // 30
         "m-d-yy",
         // 31
-        "yyyy\"5E74\"m\"6708\"d\"65E5\"",
+        "yyyy\"年\"m\"月\"d\"日\"",
         // 32
-        "h\"65F6\"mm\"5206\"",
+        "h\"时\"mm\"分\"",
         // 33
-        "h\"65F6\"mm\"5206\"ss\"79D2\"",
+        "h\"时\"mm\"分\"ss\"秒\"",
         // 34
-        "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"",
+        "上午/下午h\"时\"mm\"分\"",
         // 35
-        "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"",
+        "上午/下午h\"时\"mm\"分\"ss\"秒\"",
         // 36
-        "yyyy\"5E74\"m\"6708\"",
+        "yyyy\"年\"m\"月\"",
         // 37
         "#,##0_);(#,##0)",
         // 38
@@ -120,23 +122,23 @@ public class BuiltinFormats {
         // 49
         "@",
         // 50
-        "yyyy\"5E74\"m\"6708\"",
+        "yyyy\"年\"m\"月\"",
         // 51
-        "m\"6708\"d\"65E5\"",
+        "m\"月\"d\"日\"",
         // 52
-        "yyyy\"5E74\"m\"6708\"",
+        "yyyy\"年\"m\"月\"",
         // 53
-        "m\"6708\"d\"65E5\"",
+        "m\"月\"d\"日\"",
         // 54
-        "m\"6708\"d\"65E5\"",
+        "m\"月\"d\"日\"",
         // 55
-        "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"",
+        "上午/下午h\"时\"mm\"分\"",
         // 56
-        "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"",
+        "上午/下午h\"时\"mm\"分\"ss\"秒\"",
         // 57
-        "yyyy\"5E74\"m\"6708\"",
+        "yyyy\"年\"m\"月\"",
         // 58
-        "m\"6708\"d\"65E5\"",
+        "m\"月\"d\"日\"",
         // 59
         "t0",
         // 60
@@ -163,25 +165,25 @@ public class BuiltinFormats {
         // 70
         "t# ??/??",
         // 71
-        "0E27/0E14/0E1B0E1B0E1B0E1B",
+        "ว/ด/ปปปป",
         // 72
-        "0E27-0E140E140E14-0E1B0E1B",
+        "ว-ดดด-ปป",
         // 73
-        "0E27-0E140E140E14",
+        "ว-ดดด",
         // 74
-        "0E140E140E14-0E1B0E1B",
+        "ดดด-ปป",
         // 75
-        "0E0A:0E190E19",
+        "ช:นน",
         // 76
-        "0E0A:0E190E19:0E170E17",
+        "ช:นน:ทท",
         // 77
-        "0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19",
+        "ว/ด/ปปปป ช:นน",
         // 78
-        "0E190E19:0E170E17",
+        "นน:ทท",
         // 79
-        "[0E0A]:0E190E19:0E170E17",
+        "[ช]:นน:ทท",
         // 80
-        "0E190E19:0E170E17.0",
+        "นน:ทท.0",
         // 81
         "d/m/bb",
         // end
diff --git a/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java b/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java
index f536a088..b31bc706 100644
--- a/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java
+++ b/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java
@@ -10,7 +10,9 @@ import com.alibaba.excel.metadata.CellData;
 import com.alibaba.excel.metadata.GlobalConfiguration;
 import com.alibaba.excel.metadata.property.ExcelContentProperty;
 import com.alibaba.excel.util.DateUtils;
+import com.alibaba.excel.util.NumberDataFormatterUtils;
 import com.alibaba.excel.util.NumberUtils;
+import com.alibaba.excel.util.StringUtils;
 
 /**
  * String and number converter
@@ -44,13 +46,9 @@ public class StringNumberConverter implements Converter<String> {
             return NumberUtils.format(cellData.getNumberValue(), contentProperty);
         }
         // Excel defines formatting
-        if (cellData.getDataFormat() != null) {
-            if (DateUtil.isADateFormat(cellData.getDataFormat(), cellData.getDataFormatString())) {
-                return DateUtils.format(DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(),
-                    globalConfiguration.getUse1904windowing(), null));
-            } else {
-                return NumberUtils.format(cellData.getNumberValue(), contentProperty);
-            }
+        if (cellData.getDataFormat() != null && !StringUtils.isEmpty(cellData.getDataFormatString())) {
+            return NumberDataFormatterUtils.format(cellData.getNumberValue().doubleValue(), cellData.getDataFormat(),
+                cellData.getDataFormatString(), globalConfiguration);
         }
         // Default conversion number
         return NumberUtils.format(cellData.getNumberValue(), contentProperty);
diff --git a/src/main/java/com/alibaba/excel/metadata/BasicParameter.java b/src/main/java/com/alibaba/excel/metadata/BasicParameter.java
index 40bffe63..d00c93a8 100644
--- a/src/main/java/com/alibaba/excel/metadata/BasicParameter.java
+++ b/src/main/java/com/alibaba/excel/metadata/BasicParameter.java
@@ -1,6 +1,7 @@
 package com.alibaba.excel.metadata;
 
 import java.util.List;
+import java.util.Locale;
 
 import com.alibaba.excel.converters.Converter;
 
@@ -34,6 +35,11 @@ public class BasicParameter {
      * @return
      */
     private Boolean use1904windowing;
+    /**
+     * A <code>Locale</code> object represents a specific geographical, political, or cultural region. This parameter is
+     * used when formatting dates and numbers.
+     */
+    private Locale locale;
 
     public List<List<String>> getHead() {
         return head;
@@ -75,4 +81,11 @@ public class BasicParameter {
         this.use1904windowing = use1904windowing;
     }
 
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
 }
diff --git a/src/main/java/com/alibaba/excel/metadata/DataFormatter.java b/src/main/java/com/alibaba/excel/metadata/DataFormatter.java
new file mode 100644
index 00000000..13bd1628
--- /dev/null
+++ b/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);
+        }
+    }
+
+}
diff --git a/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java b/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java
index 921d70a8..12cc8d31 100644
--- a/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java
+++ b/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java
@@ -1,5 +1,7 @@
 package com.alibaba.excel.metadata;
 
+import java.util.Locale;
+
 /**
  * Global configuration
  *
@@ -18,6 +20,11 @@ public class GlobalConfiguration {
      * @return
      */
     private Boolean use1904windowing;
+    /**
+     * A <code>Locale</code> object represents a specific geographical, political, or cultural region. This parameter is
+     * used when formatting dates and numbers.
+     */
+    private Locale locale;
 
     public Boolean getUse1904windowing() {
         return use1904windowing;
@@ -34,4 +41,12 @@ public class GlobalConfiguration {
     public void setAutoTrim(Boolean autoTrim) {
         this.autoTrim = autoTrim;
     }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
 }
diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java
index dfc8d20a..b375733a 100644
--- a/src/main/java/com/alibaba/excel/util/DateUtils.java
+++ b/src/main/java/com/alibaba/excel/util/DateUtils.java
@@ -1,24 +1,45 @@
 package com.alibaba.excel.util;
 
-import java.text.Format;
+import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
-
-import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
-import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.DateUtil;
-import org.apache.poi.ss.usermodel.ExcelNumberFormat;
-import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
 
 /**
  * Date utils
  *
  * @author Jiaju Zhuang
  **/
-public class DateUtils {
-
+public class DateUtils implements ThreadLocalCachedUtils {
+    /**
+     * Is a cache of dates
+     */
+    private static final ThreadLocal<Map<Integer, Boolean>> DATE_THREAD_LOCAL =
+        new ThreadLocal<Map<Integer, Boolean>>();
+    /**
+     * Is a cache of dates
+     */
+    private static final ThreadLocal<Map<String, SimpleDateFormat>> DATE_FORMAT_THREAD_LOCAL =
+        new ThreadLocal<Map<String, SimpleDateFormat>>();
 
+    /**
+     * The following patterns are used in {@link #isADateFormat(Integer, String)}
+     */
+    private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]");
+    private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+\\]");
+    private static final Pattern date_ptrn3a = Pattern.compile("[yYmMdDhHsS]");
+    // add "\u5e74 \u6708 \u65e5" for Chinese/Japanese date format:2017 \u5e74 2 \u6708 7 \u65e5
+    private static final Pattern date_ptrn3b =
+        Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0*[ampAMP/]*$");
+    // elapsed time patterns: [h],[m] and [s]
+    private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]");
+    // for format which start with "[DBNum1]" or "[DBNum2]" or "[DBNum3]" could be a Chinese date
+    private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]");
+    // for format which start with "年" or "月" or "日" or "时" or "分" or "秒" could be a Chinese date
+    private static final Pattern date_ptrn6 = Pattern.compile("(年|月|日|时|分|秒)+");
 
     public static final String DATE_FORMAT_10 = "yyyy-MM-dd";
     public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss";
@@ -41,7 +62,7 @@ public class DateUtils {
         if (StringUtils.isEmpty(dateFormat)) {
             dateFormat = switchDateFormat(dateString);
         }
-        return new SimpleDateFormat(dateFormat).parse(dateString);
+        return getCacheDateFormat(dateFormat).parse(dateString);
     }
 
     /**
@@ -107,196 +128,183 @@ public class DateUtils {
         if (StringUtils.isEmpty(dateFormat)) {
             dateFormat = DATE_FORMAT_19;
         }
-        return new SimpleDateFormat(dateFormat).format(date);
+        return getCacheDateFormat(dateFormat).format(date);
+    }
+
+    private static DateFormat getCacheDateFormat(String dateFormat) {
+        Map<String, SimpleDateFormat> dateFormatMap = DATE_FORMAT_THREAD_LOCAL.get();
+        if (dateFormatMap == null) {
+            dateFormatMap = new HashMap<String, SimpleDateFormat>();
+            DATE_FORMAT_THREAD_LOCAL.set(dateFormatMap);
+        } else {
+            SimpleDateFormat dateFormatCached = dateFormatMap.get(dateFormat);
+            if (dateFormatCached != null) {
+                return dateFormatCached;
+            }
+        }
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
+        dateFormatMap.put(dateFormat, simpleDateFormat);
+        return simpleDateFormat;
     }
 
-//
-//    /**
-//     * Determine if it is a date format.
-//     *
-//     * @param dataFormat
-//     * @param dataFormatString
-//     * @return
-//     */
-//    public static boolean isDateFormatted(Integer dataFormat, String dataFormatString) {
-//        if (cell == null) {
-//            return false;
-//        }
-//        boolean isDate = false;
-//
-//        double d = cell.getNumericCellValue();
-//        if (DateUtil.isValidExcelDate(d)) {
-//            ExcelNumberFormat nf = ExcelNumberFormat.from(cell, cfEvaluator);
-//            if (nf == null) {
-//                return false;
-//            }
-//            bDate = isADateFormat(nf);
-//        }
-//        return bDate;
-//    }
-//
-//    private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
-//        if (cell == null) {
-//            return null;
-//        }
-//        Format dateFormat = getFormat(cell, cfEvaluator);
-//        synchronized (dateFormat) {
-//            if(dateFormat instanceof ExcelStyleDateFormatter) {
-//                // Hint about the raw excel value
-//                ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(
-//                    cell.getNumericCellValue()
-//                );
-//            }
-//            Date d = cell.getDateCellValue();
-//            return performDateFormatting(d, dateFormat);
-//        }
-//    }
-//
-//
-//    public static boolean isADateFormat(int formatIndex, String formatString) {
-//        // First up, is this an internal date format?
-//        if (isInternalDateFormat(formatIndex)) {
-//            return true;
-//        }
-//        if (StringUtils.isEmpty(formatString)) {
-//            return false;
-//        }
-//
-//        // check the cache first
-//        if (isCached(formatString, formatIndex)) {
-//            return lastCachedResult.get();
-//        }
-//
-//        String fs = formatString;
-//        /*if (false) {
-//            // Normalize the format string. The code below is equivalent
-//            // to the following consecutive regexp replacements:
-//
-//             // Translate \- into just -, before matching
-//             fs = fs.replaceAll("\\\\-","-");
-//             // And \, into ,
-//             fs = fs.replaceAll("\\\\,",",");
-//             // And \. into .
-//             fs = fs.replaceAll("\\\\\\.",".");
-//             // And '\ ' into ' '
-//             fs = fs.replaceAll("\\\\ "," ");
-//
-//             // If it end in ;@, that's some crazy dd/mm vs mm/dd
-//             //  switching stuff, which we can ignore
-//             fs = fs.replaceAll(";@", "");
-//
-//             // The code above was reworked as suggested in bug 48425:
-//             // simple loop is more efficient than consecutive regexp replacements.
-//        }*/
-//        final int length = fs.length();
-//        StringBuilder sb = new StringBuilder(length);
-//        for (int i = 0; i < length; i++) {
-//            char c = fs.charAt(i);
-//            if (i < length - 1) {
-//                char nc = fs.charAt(i + 1);
-//                if (c == '\\') {
-//                    switch (nc) {
-//                        case '-':
-//                        case ',':
-//                        case '.':
-//                        case ' ':
-//                        case '\\':
-//                            // skip current '\' and continue to the next char
-//                            continue;
-//                    }
-//                } else if (c == ';' && nc == '@') {
-//                    i++;
-//                    // skip ";@" duplets
-//                    continue;
-//                }
-//            }
-//            sb.append(c);
-//        }
-//        fs = sb.toString();
-//
-//        // short-circuit if it indicates elapsed time: [h], [m] or [s]
-//        if (date_ptrn4.matcher(fs).matches()) {
-//            cache(formatString, formatIndex, true);
-//            return true;
-//        }
-//        // If it starts with [DBNum1] or [DBNum2] or [DBNum3]
-//        // then it could be a Chinese date
-//        fs = date_ptrn5.matcher(fs).replaceAll("");
-//        // If it starts with [$-...], then could be a date, but
-//        // who knows what that starting bit is all about
-//        fs = date_ptrn1.matcher(fs).replaceAll("");
-//        // If it starts with something like [Black] or [Yellow],
-//        // then it could be a date
-//        fs = date_ptrn2.matcher(fs).replaceAll("");
-//        // You're allowed something like dd/mm/yy;[red]dd/mm/yy
-//        // which would place dates before 1900/1904 in red
-//        // For now, only consider the first one
-//        final int separatorIndex = fs.indexOf(';');
-//        if (0 < separatorIndex && separatorIndex < fs.length() - 1) {
-//            fs = fs.substring(0, separatorIndex);
-//        }
-//
-//        // Ensure it has some date letters in it
-//        // (Avoids false positives on the rest of pattern 3)
-//        if (!date_ptrn3a.matcher(fs).find()) {
-//            return false;
-//        }
-//
-//        // If we get here, check it's only made up, in any case, of:
-//        // y m d h s - \ / , . : [ ] T
-//        // optionally followed by AM/PM
-//
-//        boolean result = date_ptrn3b.matcher(fs).matches();
-//        cache(formatString, formatIndex, result);
-//        return result;
-//    }
-//
-//    /**
-//     * Given a format ID this will check whether the format represents an internal excel date format or not.
-//     *
-//     * @see #isADateFormat(int, java.lang.String)
-//     */
-//    public static boolean isInternalDateFormat(int format) {
-//        switch (format) {
-//            // Internal Date Formats as described on page 427 in
-//            // Microsoft Excel Dev's Kit...
-//            // 14-22
-//            case 0x0e:
-//            case 0x0f:
-//            case 0x10:
-//            case 0x11:
-//            case 0x12:
-//            case 0x13:
-//            case 0x14:
-//            case 0x15:
-//            case 0x16:
-//                // 27-36
-//            case 0x1b:
-//            case 0x1c:
-//            case 0x1d:
-//            case 0x1e:
-//            case 0x1f:
-//            case 0x20:
-//            case 0x21:
-//            case 0x22:
-//            case 0x23:
-//            case 0x24:
-//                // 45-47
-//            case 0x2d:
-//            case 0x2e:
-//            case 0x2f:
-//                // 50-58
-//            case 0x32:
-//            case 0x33:
-//            case 0x34:
-//            case 0x35:
-//            case 0x36:
-//            case 0x37:
-//            case 0x38:
-//            case 0x39:
-//            case 0x3a:
-//                return true;
-//        }
-//        return false;
-//    }
+    /**
+     * Determine if it is a date format.
+     *
+     * @param formatIndex
+     * @param formatString
+     * @return
+     */
+    public static boolean isADateFormat(Integer formatIndex, String formatString) {
+        if (formatIndex == null) {
+            return false;
+        }
+        Map<Integer, Boolean> isDateCache = DATE_THREAD_LOCAL.get();
+        if (isDateCache == null) {
+            isDateCache = new HashMap<Integer, Boolean>();
+            DATE_THREAD_LOCAL.set(isDateCache);
+        } else {
+            Boolean isDateCachedData = isDateCache.get(formatIndex);
+            if (isDateCachedData != null) {
+                return isDateCachedData;
+            }
+        }
+        boolean isDate = isADateFormatUncached(formatIndex, formatString);
+        isDateCache.put(formatIndex, isDate);
+        return isDate;
+    }
+
+    /**
+     * Determine if it is a date format.
+     *
+     * @param formatIndex
+     * @param formatString
+     * @return
+     */
+    public static boolean isADateFormatUncached(Integer formatIndex, String formatString) {
+        // First up, is this an internal date format?
+        if (isInternalDateFormat(formatIndex)) {
+            return true;
+        }
+        if (StringUtils.isEmpty(formatString)) {
+            return false;
+        }
+        String fs = formatString;
+        final int length = fs.length();
+        StringBuilder sb = new StringBuilder(length);
+        for (int i = 0; i < length; i++) {
+            char c = fs.charAt(i);
+            if (i < length - 1) {
+                char nc = fs.charAt(i + 1);
+                if (c == '\\') {
+                    switch (nc) {
+                        case '-':
+                        case ',':
+                        case '.':
+                        case ' ':
+                        case '\\':
+                            // skip current '\' and continue to the next char
+                            continue;
+                    }
+                } else if (c == ';' && nc == '@') {
+                    i++;
+                    // skip ";@" duplets
+                    continue;
+                }
+            }
+            sb.append(c);
+        }
+        fs = sb.toString();
+
+        // short-circuit if it indicates elapsed time: [h], [m] or [s]
+        if (date_ptrn4.matcher(fs).matches()) {
+            return true;
+        }
+        // If it starts with [DBNum1] or [DBNum2] or [DBNum3]
+        // then it could be a Chinese date
+        fs = date_ptrn5.matcher(fs).replaceAll("");
+        // If it starts with [$-...], then could be a date, but
+        // who knows what that starting bit is all about
+        fs = date_ptrn1.matcher(fs).replaceAll("");
+        // If it starts with something like [Black] or [Yellow],
+        // then it could be a date
+        fs = date_ptrn2.matcher(fs).replaceAll("");
+        // You're allowed something like dd/mm/yy;[red]dd/mm/yy
+        // which would place dates before 1900/1904 in red
+        // For now, only consider the first one
+        final int separatorIndex = fs.indexOf(';');
+        if (0 < separatorIndex && separatorIndex < fs.length() - 1) {
+            fs = fs.substring(0, separatorIndex);
+        }
+
+        // Ensure it has some date letters in it
+        // (Avoids false positives on the rest of pattern 3)
+        if (!date_ptrn3a.matcher(fs).find()) {
+            return false;
+        }
+
+        // If we get here, check it's only made up, in any case, of:
+        // y m d h s - \ / , . : [ ] T
+        // optionally followed by AM/PM
+        boolean result = date_ptrn3b.matcher(fs).matches();
+        if (result) {
+            return true;
+        }
+        result = date_ptrn6.matcher(fs).find();
+        return result;
+    }
+
+    /**
+     * Given a format ID this will check whether the format represents an internal excel date format or not.
+     *
+     * @see #isADateFormat(Integer, String)
+     */
+    public static boolean isInternalDateFormat(int format) {
+        switch (format) {
+            // Internal Date Formats as described on page 427 in
+            // Microsoft Excel Dev's Kit...
+            // 14-22
+            case 0x0e:
+            case 0x0f:
+            case 0x10:
+            case 0x11:
+            case 0x12:
+            case 0x13:
+            case 0x14:
+            case 0x15:
+            case 0x16:
+                // 27-36
+            case 0x1b:
+            case 0x1c:
+            case 0x1d:
+            case 0x1e:
+            case 0x1f:
+            case 0x20:
+            case 0x21:
+            case 0x22:
+            case 0x23:
+            case 0x24:
+                // 45-47
+            case 0x2d:
+            case 0x2e:
+            case 0x2f:
+                // 50-58
+            case 0x32:
+            case 0x33:
+            case 0x34:
+            case 0x35:
+            case 0x36:
+            case 0x37:
+            case 0x38:
+            case 0x39:
+            case 0x3a:
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void removeThreadLocalCache() {
+        DATE_THREAD_LOCAL.remove();
+        DATE_FORMAT_THREAD_LOCAL.remove();
+    }
 }
diff --git a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java
index 6a4b9c55..52ced2ac 100644
--- a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java
+++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java
@@ -1,154 +1,47 @@
-//package com.alibaba.excel.util;
-//
-//import java.text.Format;
-//
-//import org.apache.poi.ss.format.CellFormat;
-//import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
-//import org.apache.poi.ss.usermodel.Cell;
-//import org.apache.poi.ss.usermodel.DataFormatter;
-//import org.apache.poi.ss.usermodel.DateUtil;
-//import org.apache.poi.ss.usermodel.ExcelNumberFormat;
-//import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter;
-//import org.apache.poi.util.POILogger;
-//
-///**
-// * Convert number data, including date.
-// *
-// * @author Jiaju Zhuang
-// **/
-//public class NumberDataFormatterUtils {
-//
-//    /**
-//     *
-//     * @param data
-//     *            Not null.
-//     * @param dataFormatString
-//     *            Not null.
-//     * @return
-//     */
-//    public String format(Double data, Integer dataFormat, String dataFormatString) {
-//
-//        if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) {
-//            return getFormattedDateString(cell, cfEvaluator);
-//        }
-//        return getFormattedNumberString(cell, cfEvaluator);
-//
-//    }
-//
-//    private String getFormattedDateString(Double data,String dataFormatString) {
-//
-//
-//        if (cell == null) {
-//            return null;
-//        }
-//        Format dateFormat = getFormat(cell, cfEvaluator);
-//        synchronized (dateFormat) {
-//            if (dateFormat instanceof ExcelStyleDateFormatter) {
-//                // Hint about the raw excel value
-//                ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue());
-//            }
-//            Date d = cell.getDateCellValue();
-//            return performDateFormatting(d, dateFormat);
-//        }
-//    }
-//
-//
-//    /**
-//     * Return a Format for the given cell if one exists, otherwise try to
-//     * create one. This method will return <code>null</code> if the any of the
-//     * following is true:
-//     * <ul>
-//     * <li>the cell's style is null</li>
-//     * <li>the style's data format string is null or empty</li>
-//     * <li>the format string cannot be recognized as either a number or date</li>
-//     * </ul>
-//     *
-//     * @param cell The cell to retrieve a Format for
-//     * @return A Format for the format String
-//     */
-//    private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) {
-//        if (cell == null) return null;
-//
-//        ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator);
-//
-//        if ( numFmt == null) {
-//            return null;
-//        }
-//
-//        int formatIndex = numFmt.getIdx();
-//        String formatStr = numFmt.getFormat();
-//        if(formatStr == null || formatStr.trim().length() == 0) {
-//            return null;
-//        }
-//        return getFormat(cell.getNumericCellValue(), formatIndex, formatStr, isDate1904(cell));
-//    }
-//
-//    private boolean isDate1904(Cell cell) {
-//        if ( cell != null && cell.getSheet().getWorkbook() instanceof Date1904Support) {
-//            return ((Date1904Support)cell.getSheet().getWorkbook()).isDate1904();
-//
-//        }
-//        return false;
-//    }
-//
-//    private Format getFormat(double cellValue, int formatIndex, String formatStrIn, boolean use1904Windowing) {
-//        localeChangedObservable.checkForLocaleChange();
-//
-//        // Might be better to separate out the n p and z formats, falling back to p when n and z are not set.
-//        // That however would require other code to be re factored.
-//        // String[] formatBits = formatStrIn.split(";");
-//        // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2;
-//        // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0];
-//
-//        String formatStr = formatStrIn;
-//
-//        // Excel supports 2+ part conditional data formats, eg positive/negative/zero,
-//        //  or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds
-//        //  of different formats for different ranges, just +ve/-ve, we need to
-//        //  handle these ourselves in a special way.
-//        // For now, if we detect 2+ parts, we call out to CellFormat to handle it
-//        // TODO Going forward, we should really merge the logic between the two classes
-//        if (formatStr.contains(";") &&
-//            (formatStr.indexOf(';') != formatStr.lastIndexOf(';')
-//                || rangeConditionalPattern.matcher(formatStr).matches()
-//            ) ) {
-//            try {
-//                // Ask CellFormat to get a formatter for it
-//                CellFormat cfmt = CellFormat.getInstance(locale, formatStr);
-//                // CellFormat requires callers to identify date vs not, so do so
-//                Object cellValueO = Double.valueOf(cellValue);
-//                if (DateUtil.isADateFormat(formatIndex, formatStr) &&
-//                    // don't try to handle Date value 0, let a 3 or 4-part format take care of it
-//                    ((Double)cellValueO).doubleValue() != 0.0) {
-//                    cellValueO = DateUtil.getJavaDate(cellValue, use1904Windowing);
-//                }
-//                // Wrap and return (non-cachable - CellFormat does that)
-//                return new DataFormatter.CellFormatResultWrapper( cfmt.apply(cellValueO) );
-//            } catch (Exception e) {
-//                logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e);
-//            }
-//        }
-//
-//        // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format.
-//        if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) {
-//            formatStr = formatStr.replaceAll("#", "");
-//        }
-//
-//        // See if we already have it cached
-//        Format format = formats.get(formatStr);
-//        if (format != null) {
-//            return format;
-//        }
-//
-//        // Is it one of the special built in types, General or @?
-//        if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
-//            return generalNumberFormat;
-//        }
-//
-//        // Build a formatter, and cache it
-//        format = createFormat(cellValue, formatIndex, formatStr);
-//        formats.put(formatStr, format);
-//        return format;
-//    }
-//
-//}
+package com.alibaba.excel.util;
+
+import com.alibaba.excel.metadata.DataFormatter;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+
+/**
+ * Convert number data, including date.
+ *
+ * @author Jiaju Zhuang
+ **/
+public class NumberDataFormatterUtils implements ThreadLocalCachedUtils {
+    /**
+     * Cache DataFormatter.
+     */
+    private static final ThreadLocal<DataFormatter> DATA_FORMATTER_THREAD_LOCAL = new ThreadLocal<DataFormatter>();
+
+    /**
+     * Format number data.
+     *
+     * @param data
+     * @param dataFormat
+     *            Not null.
+     * @param dataFormatString
+     * @param globalConfiguration
+     * @return
+     */
+    public static String format(Double data, Integer dataFormat, String dataFormatString,
+        GlobalConfiguration globalConfiguration) {
+        DataFormatter dataFormatter = DATA_FORMATTER_THREAD_LOCAL.get();
+        if (dataFormatter == null) {
+            if (globalConfiguration != null) {
+                dataFormatter =
+                    new DataFormatter(globalConfiguration.getLocale(), globalConfiguration.getUse1904windowing());
+            } else {
+                dataFormatter = new DataFormatter();
+            }
+            DATA_FORMATTER_THREAD_LOCAL.set(dataFormatter);
+        }
+        return dataFormatter.format(data, dataFormat, dataFormatString);
+
+    }
+
+    @Override
+    public void removeThreadLocalCache() {
+        DATA_FORMATTER_THREAD_LOCAL.remove();
+    }
+}
diff --git a/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java b/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java
new file mode 100644
index 00000000..d1367038
--- /dev/null
+++ b/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();
+}
diff --git a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java
new file mode 100644
index 00000000..d627e46c
--- /dev/null
+++ b/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;
+}
diff --git a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java
new file mode 100644
index 00000000..69b67098
--- /dev/null
+++ b/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));
+            }
+        }
+    }
+
+}
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
index d9c7bb23..d3db5e06 100644
--- a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java
+++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java
@@ -6,10 +6,14 @@ import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
 
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.DataFormatter;
 import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.junit.Ignore;
@@ -17,7 +21,9 @@ import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.alibaba.easyexcel.test.core.dataformat.DateFormatData;
 import com.alibaba.easyexcel.test.temp.Lock2Test;
+import com.alibaba.easyexcel.test.util.TestFileUtil;
 import com.alibaba.excel.EasyExcel;
 import com.alibaba.fastjson.JSON;
 
@@ -124,4 +130,61 @@ public class DataFormatTest {
         System.out.println("end:" + (System.currentTimeMillis() - start));
     }
 
+    @Test
+    public void test355() throws IOException, InvalidFormatException {
+        File file = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx");
+        XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file);
+        Sheet xssfSheet = xssfWorkbook.getSheetAt(0);
+        DataFormatter d = new DataFormatter(Locale.CHINA);
+
+        for (int i = 0; i < xssfSheet.getLastRowNum(); i++) {
+            Row row = xssfSheet.getRow(i);
+            System.out.println(d.formatCellValue(row.getCell(0)));
+        }
+
+    }
+
+    @Test
+    public void test3556() throws IOException, InvalidFormatException {
+        String file = "D://test/dataformat.xlsx";
+        XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file);
+        Sheet xssfSheet = xssfWorkbook.getSheetAt(0);
+        DataFormatter d = new DataFormatter(Locale.CHINA);
+
+        for (int i = 0; i < xssfSheet.getLastRowNum(); i++) {
+            Row row = xssfSheet.getRow(i);
+            System.out.println(d.formatCellValue(row.getCell(0)));
+        }
+
+    }
+
+    @Test
+    public void tests() throws IOException, InvalidFormatException {
+        SimpleDateFormat s1 = new SimpleDateFormat("yyyy\"5E74\"m\"6708\"d\"65E5\"");
+        System.out.println(s1.format(new Date()));
+        s1 = new SimpleDateFormat("yyyy年m月d日");
+        System.out.println(s1.format(new Date()));
+    }
+
+    @Test
+    public void tests1() throws IOException, InvalidFormatException {
+        String file = "D://test/dataformat1.xlsx";
+        List<DateFormatData> list = EasyExcel.read(file, DateFormatData.class, null).sheet().doReadSync();
+        for (DateFormatData data : list) {
+            LOGGER.info("返回:{}", JSON.toJSONString(data));
+        }
+    }
+
+    @Test
+    public void tests3() throws IOException, InvalidFormatException {
+        SimpleDateFormat s1 = new SimpleDateFormat("ah\"时\"mm\"分\"");
+        System.out.println(s1.format(new Date()));
+    }
+
+    private static final Pattern date_ptrn6 = Pattern.compile("^.*(年|月|日|时|分|秒)+.*$");
+
+    @Test
+    public void tests34() throws IOException, InvalidFormatException {
+        System.out.println(date_ptrn6.matcher("2017但是").matches());
+    }
 }
diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java
index 3a70d0d4..68fff703 100644
--- a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java
+++ b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java
@@ -2,12 +2,8 @@ package com.alibaba.easyexcel.test.temp.poi;
 
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
 import java.util.regex.Pattern;
 
-import org.apache.poi.ss.formula.functions.T;
 import org.apache.poi.xssf.streaming.SXSSFCell;
 import org.apache.poi.xssf.streaming.SXSSFRow;
 import org.apache.poi.xssf.streaming.SXSSFSheet;
@@ -17,11 +13,8 @@ import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.alibaba.excel.metadata.CellData;
 import com.alibaba.fastjson.JSON;
 
-import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
-
 /**
  * 测试poi
  *
@@ -99,58 +92,4 @@ public class PoiWriteTest {
 
     }
 
-    @Test
-    public void test() throws Exception {
-        Class<TestCell> clazz = TestCell.class;
-
-        Field field = clazz.getDeclaredField("c2");
-        // 通过getDeclaredField可以获得成员变量,但是对于Map来说,仅仅可以知道它是个Map,无法知道键值对各自的数据类型
-
-        Type gType = field.getGenericType();
-        // 获得field的泛型类型
-
-        // 如果gType是ParameterizedType对象(参数化)
-        if (gType instanceof ParameterizedType) {
-
-            ParameterizedType pType = (ParameterizedType)gType;
-            // 就把它转换成ParameterizedType对象
-
-            Type[] tArgs = pType.getActualTypeArguments();
-            // 获得泛型类型的泛型参数(实际类型参数)
-            ParameterizedTypeImpl c = (ParameterizedTypeImpl)pType.getActualTypeArguments()[0];
-            Class ttt = c.getRawType();
-            System.out.println(ttt);
-        } else {
-            System.out.println("出错!!!");
-        }
-
-    }
-
-    @Test
-    public void test2() throws Exception {
-        Class<TestCell> clazz = TestCell.class;
-
-        Field field = clazz.getDeclaredField("c2");
-        // 通过getDeclaredField可以获得成员变量,但是对于Map来说,仅仅可以知道它是个Map,无法知道键值对各自的数据类型
-
-        Type gType = field.getGenericType();
-        // 获得field的泛型类型
-
-        // 如果gType是ParameterizedType对象(参数化)
-        if (gType instanceof ParameterizedType) {
-
-            ParameterizedType pType = (ParameterizedType)gType;
-            // 就把它转换成ParameterizedType对象
-
-            Type[] tArgs = pType.getActualTypeArguments();
-            // 获得泛型类型的泛型参数(实际类型参数)
-            ParameterizedTypeImpl c = (ParameterizedTypeImpl)pType.getActualTypeArguments()[0];
-            Class ttt = c.getRawType();
-            System.out.println(ttt);
-        } else {
-            System.out.println("出错!!!");
-        }
-
-    }
-
 }
diff --git a/src/test/resources/dataformat/dataformat.xlsx b/src/test/resources/dataformat/dataformat.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..370d983a931862faf90274a954ed71e38cb92ac5
GIT binary patch
literal 11489
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

literal 0
HcmV?d00001