DateUtils.java 9.8 KB
Newer Older
Z
zhuangjiaju 已提交
1 2
package com.alibaba.excel.util;

庄家钜's avatar
庄家钜 已提交
3
import java.text.DateFormat;
Z
zhuangjiaju 已提交
4 5 6
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
庄家钜's avatar
庄家钜 已提交
7 8 9
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
Z
zhuangjiaju 已提交
10 11 12 13

/**
 * Date utils
 *
Z
zhuangjiaju 已提交
14
 * @author Jiaju Zhuang
Z
zhuangjiaju 已提交
15
 **/
16
public class DateUtils {
庄家钜's avatar
庄家钜 已提交
17 18 19 20 21 22 23 24 25 26
    /**
     * 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>>();
庄家钜's avatar
庄家钜 已提交
27

庄家钜's avatar
庄家钜 已提交
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
    /**
     * 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("(年|月|日|时|分|秒)+");
庄家钜's avatar
庄家钜 已提交
43

44
    public static final String DATE_FORMAT_10 = "yyyy-MM-dd";
Z
zhuangjiaju 已提交
45 46 47 48 49 50 51 52 53 54
    public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss";
    public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss";
    public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss";
    public static final String DATE_FORMAT_19_FORWARD_SLASH = "yyyy/MM/dd HH:mm:ss";
    private static final String MINUS = "-";

    private DateUtils() {}

    /**
     * convert string to date
Z
zhuangjiaju 已提交
55
     *
Z
zhuangjiaju 已提交
56 57 58 59 60 61 62 63 64
     * @param dateString
     * @param dateFormat
     * @return
     * @throws ParseException
     */
    public static Date parseDate(String dateString, String dateFormat) throws ParseException {
        if (StringUtils.isEmpty(dateFormat)) {
            dateFormat = switchDateFormat(dateString);
        }
庄家钜's avatar
庄家钜 已提交
65
        return getCacheDateFormat(dateFormat).parse(dateString);
Z
zhuangjiaju 已提交
66 67 68 69 70 71 72 73 74 75
    }

    /**
     * convert string to date
     *
     * @param dateString
     * @return
     * @throws ParseException
     */
    public static Date parseDate(String dateString) throws ParseException {
Z
zhuangjiaju 已提交
76
        return parseDate(dateString, switchDateFormat(dateString));
Z
zhuangjiaju 已提交
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    }

    /**
     * switch date format
     *
     * @param dateString
     * @return
     */
    private static String switchDateFormat(String dateString) {
        int length = dateString.length();
        switch (length) {
            case 19:
                if (dateString.contains(MINUS)) {
                    return DATE_FORMAT_19;
                } else {
                    return DATE_FORMAT_19_FORWARD_SLASH;
                }
            case 17:
                return DATE_FORMAT_17;
            case 14:
                return DATE_FORMAT_14;
98 99
            case 10:
                return DATE_FORMAT_10;
Z
zhuangjiaju 已提交
100
            default:
庄家钜's avatar
庄家钜 已提交
101
                throw new IllegalArgumentException("can not find date format for:" + dateString);
Z
zhuangjiaju 已提交
102 103 104 105 106 107 108
        }
    }

    /**
     * Format date
     * <p>
     * yyyy-MM-dd HH:mm:ss
Z
zhuangjiaju 已提交
109
     *
Z
zhuangjiaju 已提交
110 111 112 113 114 115 116 117 118
     * @param date
     * @return
     */
    public static String format(Date date) {
        return format(date, null);
    }

    /**
     * Format date
Z
zhuangjiaju 已提交
119
     *
Z
zhuangjiaju 已提交
120 121 122 123 124 125 126 127 128 129 130
     * @param date
     * @param dateFormat
     * @return
     */
    public static String format(Date date, String dateFormat) {
        if (date == null) {
            return "";
        }
        if (StringUtils.isEmpty(dateFormat)) {
            dateFormat = DATE_FORMAT_19;
        }
庄家钜's avatar
庄家钜 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
        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;
Z
zhuangjiaju 已提交
148
    }
庄家钜's avatar
庄家钜 已提交
149

庄家钜's avatar
庄家钜 已提交
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
    /**
     * 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;
    }

305
    public static void removeThreadLocalCache() {
庄家钜's avatar
庄家钜 已提交
306 307 308
        DATE_THREAD_LOCAL.remove();
        DATE_FORMAT_THREAD_LOCAL.remove();
    }
Z
zhuangjiaju 已提交
309
}