Excel 日期时间格式讲解

1.Excel中日期时间

1.1 存储格式

  • Excel中日期时间格式单元格类型为Numeric,表示Excel日期时间内部以double数值存储
  • Excel中日期时间以浮点值存储,以1900-01-01 00:00:00为起点
  • 浮点值整数部分代表日期,表示自1900-01-01起的天数
  • 浮点值小数部分代表时间
  • 浮点值1.0表示1900-01-01 00:00:00
  • Excel提供了将浮点值d转换为日期时间date的方法
    a. d在区间(-~, 0) 表示无效日期时间
    b. d在区间[0, 1) 转换为 日期时间区间[1899-12-31 00:00:00, 1900-01-01 00:00:00)
    c. d在区间[1, +~) 转换为 日期时间区间[1900-01-01 00:00:00, yyyy-MM-dd HH:mm:ss)

  • Excel提供了将日期时间date转换为浮点值d的方法
    a. date在区间(-~, 1900-01-01 00:00:00) 转换为 d = -1,表示无效日期时间
    b. date = 1900-01-01 00:00:00 转换为 d = 1.0
    c. date在区间(1900-01-01 00:00:00, +~) 转换为 d > 1.0
    这里写图片描述

1.2 日期时间转double方法

org.apache.poi.ss.usermodel.DateUtil

/**
 * 将一个给的的日期转换为一个double类型的数值,代表日期在Excel内部的表现形式
 * double的整数部分表示自1900年1月1日以来的天数,小数表示时分秒
 * @param date : date
 * @param use1904windowing : true-使用1904年的日期窗口,false-使用1900年的日期窗口
 * @return 返回日期的Excel表示(如果错误 -检查小于0.1, 返回-1)
 * */
public static double getExcelDate(Date date) // 默认使用1900窗口
public static double getExcelDate(Date date, boolean use1904windowing)

1.3 日期时间转double实例

package org.apache.poi.util;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.poi.ss.usermodel.DateUtil;

public class Date2DoubleTest {

    public static void main(String[] args) throws Exception {
        System.out.println("小于1900-01-01 00:00:00的日期返回-1");
        System.out.println("日期1800-01-01 00:00:00 转换为double值为: " + formatDouble("1800-01-01 00:00:00"));
        System.out.println("日期1899-01-01 00:00:00 转换为double值为: " + formatDouble("1899-01-01 00:00:00"));
        System.out.println("日期1899-12-31 23:24:25 转换为double值为: " + formatDouble("1899-12-31 23:24:25"));
        System.out.println("日期1900-01-01 00:00:00 转换为double值为: " + formatDouble("1900-01-01 00:00:00"));
        System.out.println("日期1900-01-01 11:11:11 转换为double值为: " + formatDouble("1900-01-01 11:11:11"));
        System.out.println("日期1900-01-01 22:22:22 转换为double值为: " + formatDouble("1900-01-01 22:22:22"));
        System.out.println("日期1900-01-01 23:22:22 转换为double值为: " + formatDouble("1900-01-01 23:22:22"));
        System.out.println("日期1900-01-02 00:00:00 转换为double值为: " + formatDouble("1900-01-02 00:00:00"));
        System.out.println("日期1900-01-03 00:00:00 转换为double值为: " + formatDouble("1900-01-03 00:00:00"));
        System.out.println("日期2017-11-12 13:14:15 转换为double值为: " + formatDouble("2017-11-12 13:14:15"));
    }

    public static double formatDouble (String dateStr) throws Exception {
        SimpleDateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        Date date = dateFormat2.parse(dateStr); 
        return formatDouble(date);
    }

    /**
     * 将java.util.Date转换为double值
     * */
    public static double formatDouble (Date date) {
        double excelDate = DateUtil.getExcelDate(date);

        return excelDate;
    }
}

执行结果:

小于1900-01-01 00:00:00的日期返回-1
日期1800-01-01 00:00:00 转换为double值为: -1.0
日期1899-01-01 00:00:00 转换为double值为: -1.0
日期1899-12-31 23:24:25 转换为double值为: -1.0
日期1900-01-01 00:00:00 转换为double值为: 1.0
日期1900-01-01 11:11:11 转换为double值为: 1.466099537037037
日期1900-01-01 22:22:22 转换为double值为: 1.9321990740740742
日期1900-01-01 23:22:22 转换为double值为: 1.9738657407407407
日期1900-01-02 00:00:00 转换为double值为: 2.0
日期1900-01-03 00:00:00 转换为double值为: 3.0
日期2017-11-12 13:14:15 转换为double值为: 43051.5515625

这里写图片描述

1.4 double转日期时间方法

org.apache.poi.ss.usermodel.DateUtil

/**
 * 将一个double类型的数值根据Excel内部日期格式转换为一个java.util.Date
 * 
 * 注意:
 * Excel日期和时间存储没有任何时区(TimeZone)信息
 * 如果默认的TimeZone使用了夏令时(Daylight Saving Time),则转换回Excel日期可能不会产生相同的值
 * 
 * @param date Excel内部日期值,一个double类型的数值
 * @param use1904windowing true-使用1904年的日期窗口,false-使用1900年的日期窗口
 * @param tz 对应的时区,如果为null,使用系统默认时区
 * @param roundSeconds 是否round最接近的秒,默认false
 * @return 日期的Java表示形式,如果日期不是有效的Excel日期,则为null
 * */
public static Date getJavaDate(double date)
public static Date getJavaDate(double date, boolean use1904windowing)
public static Date getJavaDate(double date, TimeZone tz) // 使用时区tz去格式化日期
public static Date getJavaDate(double date, boolean use1904windowing, TimeZone tz)
public static Date getJavaDate(double date, boolean use1904windowing, TimeZone tz, boolean roundSeconds)

1.5 double转日期时间实例

package org.apache.poi.util;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.poi.ss.usermodel.DateUtil;

public class Double2DateTest {
    public static void main(String[] args) {
        /**
         * double转换为java.util.Date
         * */
        System.out.println("double :    -1.0                转换为日期时间为:  " + formatDate(-1));
        System.out.println("double :    0.00000000000001    转换为日期时间为:  " + formatDate(0.00000000000001));
        System.out.println("double :    0.55555555555555    转换为日期时间为:  " + formatDate(0.55555555555555));
        System.out.println("double :    0.77777777777777    转换为日期时间为:  " + formatDate(0.77777777777777));
        System.out.println("double :    1.0                 转换为日期时间为:  " + formatDate(1.0));
        System.out.println("double :    1.466099537037037   转换为日期时间为:  " + formatDate(1.466099537037037));
        System.out.println("double :    1.9321990740740742  转换为日期时间为:  " + formatDate(1.9321990740740742));
        System.out.println("double :    1.9738657407407407  转换为日期时间为:  " + formatDate(1.9738657407407407));
        System.out.println("double :    2.0                 转换为日期时间为:  " + formatDate(2.0));
        System.out.println("double :    3.0                 转换为日期时间为:  " + formatDate(3.0));
        System.out.println("double :    43051.5515625       转换为日期时间为:  " + formatDate(43051.5515625));
    }

    /**
     * 将double值转换为java.util.Date
     * 1. 负数               -> 小于0,表示无效日期
     * 2. 1                -> 1900-1-1 00:00:00
     * 3. 43081.5091898148 -> 2017-12-12 12:13:14
     * @param value 
     * */
    public static String formatDate(double value) {
        if (!DateUtil.isValidExcelDate(value)) {
            // value<0表示是无效日期
            return "小于0,表示无效日期";
        }
        // HH表示24进制,hh是12进制
        return formatDate(value, "yyyy-MM-dd HH:mm:ss");
    }

    public static String formatDate(double value, String formatString) {
        SimpleDateFormat sdf = new SimpleDateFormat(formatString);
//      Date date = DateUtil.getJavaDate(value); 
        // 以1900-1-1 00:00::00为起点
        Date date = DateUtil.getJavaDate(value, false);

        // 以1900-1-1 00:00::00为起点
//      Date date = DateUtil.getJavaDate(value, true);

        return sdf.format(date);
    }
}

执行结果:
以1900-1-1 00:00:00为起点

double :    -1.0                转换为日期时间为:  小于0,表示无效日期
double :    0.00000000000001    转换为日期时间为:  1899-12-31 00:00:00
double :    0.55555555555555    转换为日期时间为:  1899-12-31 13:20:00
double :    0.77777777777777    转换为日期时间为:  1899-12-31 18:40:00
double :    1.0                 转换为日期时间为:  1900-01-01 00:00:00
double :    1.466099537037037   转换为日期时间为:  1900-01-01 11:11:11
double :    1.9321990740740742  转换为日期时间为:  1900-01-01 22:22:22
double :    1.9738657407407407  转换为日期时间为:  1900-01-01 23:22:22
double :    2.0                 转换为日期时间为:  1900-01-02 00:00:00
double :    3.0                 转换为日期时间为:  1900-01-03 00:00:00
double :    43051.5515625       转换为日期时间为:  2017-11-12 13:14:15

这里写图片描述

2.Excel日期单元格样式

  • Excel支持日期单元格
  • 日期值对应的是Excel内部日期double值的证书部分,表示自1900-1-1以来的天数

2.1 Excel日期样式

使用Excel或WPS设置单元格样式,会看到已经定义了日期样式

2.1.1 对话框-已定义日期样式

这里写图片描述

2.1.2 对话框-自定义日期样式

这里写图片描述

2.2 Excel中已定义日期样式

[$-804]表示国家或地区(语言)编号,一般可以不填。
如804 汉语 (中国)
809 英语(英国)
409 英语(美国)
如果电脑上没有装相应的语言(可以设),把804改掉都不会影响显示结果。

[$-F800]相对于是 yyyy-m-d"
[$-F400]相对于是 h:mm:ss

[dbnum1]是将阿拉伯数字转换为汉字,如:123转换为一二三
[dbbun2]是转换成大写汉字,如:123转换为壹贰叁
[dbnum3]是转换为全角数字,如:123转换为 123
d代表日期之中的日,m日期中的月,y日期中的年,h日期中的小时,m日期中的分,s是日期中的秒

[$-F800]dddd, mmmm dd, yyyy
[DBNum1][$-804]yyyy"年"m"月"d"日";@
[DBNum1][$-804]yyyy"年"m"月";@
[DBNum1][$-804]m"月"d"日";@
yyyy"年"m"月"d"日";@
yyyy"年"m"月";@
m"月"d"日";@
[$-804]aaaa;@
[$-804]aaa;@
yyyy/m/d;@
[$-409]yyyy/m/d h:mm AM/PM;@
yyyy/m/d h:mm;@
yy/m/d;@
m/d;@
m/d/yy;@
mm/dd/yy;@
[$-409]d-mmm;@
[$-409]d-mmm-yy;@
[$-409]dd-mmm-yy;@
[$-409]mmm-yy;@
[$-409]mmmm-yy;@
[$-409]mmmm-yy;@
[$-409]mmmmm;@
[$-409]mmmmm-yy;@

2.3 Excel 2007中日期样式存储格式

2.3.1 当前Sheet日期单元格

这里写图片描述

2.3.2 当前Sheet存储XML

这里写图片描述

2.3.3 当前Sheet单元格样式存储XML

这里写图片描述

2.4 验证日期样式字符串

org.apache.poi.ss.usermodel.DateUtil提供方法验证

package org.apache.poi.util;

import org.apache.poi.ss.usermodel.DateUtil;

/**
 * 验证Excel中已定义的日期格式
 * org.apache.poi.ss.usermodel.DateUtil
 * 
 * [$-804]表示国家或地区(语言)编号,一般可以不填。
 * 如804 汉语 (中国)
 *  809 英语(英国)
 *  409 英语(美国)
 * 如果电脑上没有装相应的语言(可以设),把804改掉都不会影响显示结果。
 * 
 * [$-F800]相对于是 yyyy-m-d"
 * [$-F400]相对于是 h:mm:ss
 * 
 * [dbnum1]是将阿拉伯数字转换为汉字,如:123转换为一二三
 * [dbbun2]是转换成大写汉字,如:123转换为壹贰叁
 * [dbnum3]是转换为全角数字,如:123转换为 123
 * d代表日期之中的日,m日期中的月,y日期中的年,h日期中的小时,m日期中的分,s是日期中的秒
 * */
public class DateFormatTest {
    public static void main(String[] args) {
        System.out.println("字符串 - [$-F800]dddd\\,\\ mmmm\\ dd\\,\\ yyyy  是否是日期格式: " + DateUtil.isADateFormat(176, "[$-F800]dddd\\,\\ mmmm\\ dd\\,\\ yyyy"));
        System.out.println("字符串 - [DBNum1][$-804]yyyy\"年\"m\"月\"d\"日 	 是否是日期格式: " + DateUtil.isADateFormat(177, "[DBNum1][$-804]yyyy\"年\"m\"月\"d\"日"));
        System.out.println("字符串 - [DBNum1][$-804]yyyy\"年\"m\"月\";@  	是否是日期格式: " + DateUtil.isADateFormat(178, "[DBNum1][$-804]yyyy\"年\"m\"月\";@"));
        System.out.println("字符串 - [DBNum1][$-804]m\"月\"d\"日\";@  	是否是日期格式: " + DateUtil.isADateFormat(179, "[DBNum1][$-804]m\"月\"d\"日\";@"));
        System.out.println("字符串 - yyyy\"年\"m\"月\"d\"日\";@       是否是日期格式: " + DateUtil.isADateFormat(180, "yyyy\"年\"m\"月\"d\"日\";@"));
        System.out.println("字符串 - yyyy\"年\"m\"月\";@             是否是日期格式: " + DateUtil.isADateFormat(181, "yyyy\"年\"m\"月\";@"));
        System.out.println("字符串 - m\"月\"d\"日\";@            是否是日期格式: " + DateUtil.isADateFormat(182, "m\"月\"d\"日\";@"));
        System.out.println("字符串 - [$-804]aaaa;@  			是否是日期格式: " + DateUtil.isADateFormat(183, "[$-804]aaaa;@"));
        System.out.println("字符串 - [$-804]aaa;@  			是否是日期格式: " + DateUtil.isADateFormat(184, "[$-804]aaa;@"));
        System.out.println("字符串 - yyyy/m/d;@            是否是日期格式: " + DateUtil.isADateFormat(185, "yyyy/m/d;@"));
        System.out.println("字符串 - [$-409]yyyy/m/d\\ h:mm\\ AM/PM;@    是否是日期格式: " + DateUtil.isADateFormat(186, "[$-409]yyyy/m/d\\ h:mm\\ AM/PM;@"));
        System.out.println("字符串 - yyyy/m/d\\ h:mm;@         是否是日期格式: " + DateUtil.isADateFormat(187, "yyyy/m/d\\ h:mm;@"));
        System.out.println("字符串 - yy/m/d;@              是否是日期格式: " + DateUtil.isADateFormat(188, "yy/m/d;@"));
        System.out.println("字符串 - m/d;@                 是否是日期格式: " + DateUtil.isADateFormat(189, "m/d;@"));
        System.out.println("字符串 - m/d/yy;@              是否是日期格式: " + DateUtil.isADateFormat(190, "m/d/yy;@"));
        System.out.println("字符串 - mm/dd/yy;@            是否是日期格式: " + DateUtil.isADateFormat(191, "mm/dd/yy;@"));
        System.out.println("字符串 - [$-409]d\\-mmm;@  			是否是日期格式: " + DateUtil.isADateFormat(192, "[$-409]d\\-mmm;@"));
        System.out.println("字符串 - [$-409]d\\-mmm\\-yy;@  		是否是日期格式: " + DateUtil.isADateFormat(193, "[$-409]d\\-mmm\\-yy;@"));
        System.out.println("字符串 - [$-409]dd\\-mmm\\-yy;@  		是否是日期格式: " + DateUtil.isADateFormat(194, "[$-409]dd\\-mmm\\-yy;@"));
        System.out.println("字符串 - [$-409]mmm\\-yy;@  		是否是日期格式: " + DateUtil.isADateFormat(195, "[$-409]mmm\\-yy;@"));
        System.out.println("字符串 - [$-409]mmmm\\-yy;@  		是否是日期格式: " + DateUtil.isADateFormat(196, "[$-409]mmmm\\-yy;@"));
        System.out.println("字符串 - [$-409]mmmmm;@  			是否是日期格式: " + DateUtil.isADateFormat(197, "[$-409]mmmmm;@"));
        System.out.println("字符串 - [$-409]mmmmm\\-yy;@  		是否是日期格式: " + DateUtil.isADateFormat(198, "[$-409]mmmmm\\-yy;@"));
    }
}

执行结果:

字符串 - [$-F800]dddd\,\ mmmm\ dd\,\ yyyy  是否是日期格式: true
字符串 - [DBNum1][$-804]yyyy"年"m"月"d"日     是否是日期格式: true
字符串 - [DBNum1][$-804]yyyy"年"m"月";@     是否是日期格式: true
字符串 - [DBNum1][$-804]m"月"d"日";@    是否是日期格式: true
字符串 - yyyy"年"m"月"d"日";@         是否是日期格式: true
字符串 - yyyy"年"m"月";@             是否是日期格式: true
字符串 - m"月"d"日";@            是否是日期格式: true
字符串 - [$-804]aaaa;@            是否是日期格式: false
字符串 - [$-804]aaa;@             是否是日期格式: false
字符串 - yyyy/m/d;@            是否是日期格式: true
字符串 - [$-409]yyyy/m/d\ h:mm\ AM/PM;@    是否是日期格式: true
字符串 - yyyy/m/d\ h:mm;@          是否是日期格式: true
字符串 - yy/m/d;@              是否是日期格式: true
字符串 - m/d;@                 是否是日期格式: true
字符串 - m/d/yy;@              是否是日期格式: true
字符串 - mm/dd/yy;@            是否是日期格式: true
字符串 - [$-409]d\-mmm;@              是否是日期格式: true
字符串 - [$-409]d\-mmm\-yy;@          是否是日期格式: true
字符串 - [$-409]dd\-mmm\-yy;@         是否是日期格式: true
字符串 - [$-409]mmm\-yy;@         是否是日期格式: true
字符串 - [$-409]mmmm\-yy;@        是否是日期格式: true
字符串 - [$-409]mmmmm;@           是否是日期格式: true
字符串 - [$-409]mmmmm\-yy;@       是否是日期格式: true

这里写图片描述

3. Excel 时间单元格样式

  • Excel单元格也支持时间单元格
  • 时间值对应的是Excel内部时间double值的小数部分

3.1 Excel时间样式

使用Excel或WPS设置单元格样式,会看到已经定义了时间样式,我们主要讲解的就是这些时间样式:

3.1.1 对话框-已定义时间样式

这里写图片描述

3.1.2 对话框-自定义时间样式

这里写图片描述

3.2 Excel中已定义时间样式

两种设置时间样式一共有19种已经定义的时间样式,内置的时间样式索引小于50,自定义样式索引从164开始:

 - 索引         时间样式字符串
 - 18,       "h:mm AM/PM"  
 - 19,       "h:mm:ss AM/PM"  
 - 20,       "h:mm"  
 - 21,       "h:mm:ss"  
 - 22,       "m/d/yy h:mm"  
 - 45,       "mm:ss"  
 - 46,       "[h]:mm:ss"  
 - 47,       "mm:ss.0" 
 - 
 - 176    [DBNum1][$-804]上午/下午h"时"mm"分";@
 - 177    [DBNum1][$-804]h"时"mm"分";@
 - 178    [$-409]h:mm:ss\ AM/PM;@
 - 179    [$-409]h:mm\ AM/PM;@
 - 180    [$-F400]h:mm:ss\ AM/PM
 - 181    h:mm;@
 - 182    h:mm:ss;@
 - 183    h"时"mm"分";@
 - 184    h"时"mm"分"ss"秒";@
 - 185    上午/下午h"时"mm"分";@
 - 186    上午/下午h"时"mm"分"ss"秒";@ 

3.3 Excel 2007中时间样式存储格式

3.3.1 当前Sheet时间单元格

这里写图片描述

3.3.2 当前Sheet存储XML

这里写图片描述

3.3.3 当前Sheet单元格样式存储XML

这里写图片描述

3.4 验证时间样式字符串

提供方法实现验证一个单元格是否为一个时间单元格:
- 时间单元格原始值为double
- 时间单元格样式为时间字符串
这里提供方法解析单元格样式字符串,验证其是否为时间样式字符串,工具类:

package org.apache.poi.util;

import java.util.regex.Pattern;

/**
 * 主要用于处理导入excel中日期时间格式, 
 * 参考org.apache.poi.ss.usermodel.DateUtil
 * */
public class DateTimeUtil {

    protected DateTimeUtil() {
        // no instances of this class
    }

    // 描述无效的日期
//  private static final int BAD_DATE = -1;

    public static final int SECONDS_PER_MINUTE = 60;
    public static final int MINUTES_PER_HOUR = 60;
    public static final int HOURS_PER_DAY = 24;
    // 一天等于86400秒
    public static final int SECONDS_PER_DAY = (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE);

    // 一天等于86400 * 1000毫秒
    public static final long DAY_MILLISECONDS = SECONDS_PER_DAY * 1000L;

//  private static final Pattern TIME_SEPARATOR_PATTERN = Pattern.compile(":");

    /**
     * 符号. : 匹配除换行符 \n 之外的任何单字符
     * 符号* : 匹配前面的子表达式零次或多次
     * 符号+ : 匹配前面的子表达式一次或多次
     * 符号? : 匹配前面的子表达式零次或一次
     * 
     * *、 +限定符都是贪婪的,因为它们会尽可能多的匹配文字
     * 通过在 *、 + 或 ? 限定符之后放置 ?(*?、 +?、 ??),该表达式从"贪心"表达式转换为"非贪心"表达式或者最小匹配
     * */

    // ^[\$\-.*?] : 匹配以[$-开头,中间为除换行符 \n 之外的任何单字符,结尾为]的字符串,非贪心匹配
    private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]");

    /**
     * 红色 : \u7ea2\u8272
     * 黑色 :\u9ed1\u8272
     * 黄色 :  \u9ec4\u8272
     * 绿色 : \u7eff\u8272
     * 白色 : \u9ed1\u8272
     * 蓝色 : \u84dd\u8272
     * 青色 : \u9752\u8272
     * 洋红 : \u6d0b\u7ea2
     * */
    // ^[[a-zA-Z]+] : 匹配以[开头, ]结尾, 中间至少一个字母的字符串,如颜色
    private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z\u7ea2\u9ed1\u9ec4\u7eff\u84dd\u9752\u6d0b\u7ea2\u8272]+\\]");

    // [yYmMdDhHsS] : 匹配中括号里任一字母, 匹配一个
    private static final Pattern date_ptrn3a = Pattern.compile("[mhHsS]");

    /**
     * 为中文/日文日期格式添加Unicode编码:如2017 \u5e74 2 \u6708 7 \u65e5
     * Unicode编码  : 中文
     *    \u5e74 : 年
     *    \u6708 : 月
     *    \u65e5 : 日
     *    \u4e0a : 上
     *    \u4e0b : 下
     *    \u5348 : 午
     *    
     * ^[\[\]mhHsS\-T/时分秒(上午/下午),. :"\\]+0*[ampAMP/]*$
     * */
    private static final Pattern date_ptrn3b = Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u65f6\u5206\u79d2(\u4e0a\u5348/\u4e0b\u5348),. :\"\\\\]+0*[ampAMP/]*$");

    // ^\[([hH]+|[mM]+|[sS]+)] : 匹配以[开头, 以]结尾, 中间为时、分或秒的时间字符
    private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]");

    // 匹配以[DBNum1]、[DBNum2]或[DBNum3]开头的中文日期
    private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]");

    /**
     * 性能优化的变量:
     * 如果一个相同的日期格式字符串被多次传递,避免重复检查DataUtil.isADateFormat(int, String)
     * https://issues.apache.org/bugzilla/show_bug.cgi?id=55611
     * */
    private static ThreadLocal<Integer> lastFormatIndex = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return -1;
        }
    };
    private static ThreadLocal<String> lastFormatString = new ThreadLocal<String>();
    private static ThreadLocal<Boolean> lastCachedResult = new ThreadLocal<Boolean>();

    /**
     * 当前格式是否缓存过
     * @param formatString 格式化字符串
     * @param formatIndex 格式化索引
     * */
    private static boolean isCached(String formatString, int formatIndex) {
        String cachedFormatString = lastFormatString.get();
        return cachedFormatString != null && 
                     formatIndex == lastFormatIndex.get() && 
                     formatString.equals(cachedFormatString);
    }

    /**
     * 缓存当前格式
     * @param formatString 格式化字符串
     * @param formatIndex 格式化索引
     * @param cached 是否已经缓存过
     * */
    private static void cache(String formatString, int formatIndex, boolean cached) {
        lastFormatIndex.set(formatIndex);
        lastFormatString.set(formatString);
        lastCachedResult.set(Boolean.valueOf(cached));
    }

    /**
     * 检查给定格式ID及其格式String是否表示时间格式。
     * 1. 调用此方法之前org.apache.poi.ss.usermodel.DateUtil.isADateFormat(formatIndex, formatString)
     *      首先确保是一个日期格式
     * 2. 再检查是否为时间格式
     *
     * @param formatIndex 格式索引
     * @param formatString 格式字符串
     */
    public static boolean isADateTimeFormat(int formatIndex, String formatString) {
        if(isInternalDateTimeFormat(formatIndex)) {
            cache(formatString, formatIndex, true);
            return true;
        }

        if(formatString == null || formatString.length() == 0) {
            return false;
        }

        // 检查是否缓存过,提高性能
        if (isCached(formatString, formatIndex)) {
            return lastCachedResult.get();
        }

        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++;
                    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;
        }

        // 处理以[DBNum1]、 [DBNum2]或[DBNum3]开头的时间格式:[DBNum1][$-804]h"时"mm"分";@
        fs = date_ptrn5.matcher(fs).replaceAll("");

        // 处理匹配[$-...]的时间格式:[$-F400]h:mm:ss\ AM/PM
        fs = date_ptrn1.matcher(fs).replaceAll("");

        // 匹配以[开头, ]结尾, 中间至少一个字母的字符串,如带颜色的时间格式:[Yellow]h:mm:ss AM/PM
        fs = date_ptrn2.matcher(fs).replaceAll("");

        // 日期格式为dd / mm / yy; [red] dd / mm / yy,只处理第一个
        final int separatorIndex = fs.indexOf(';');
        if(0 < separatorIndex && separatorIndex < fs.length()-1) {
            fs = fs.substring(0, separatorIndex);
        }

        // 处理时间格式
        if (! date_ptrn3a.matcher(fs).find()) {
            return false;
        }

        /**
         * 到了这里,检查它只是由以下组成:  m h s - \ /,。 :[] T
         * 可选地跟随AM / PM
         * */
        boolean result = date_ptrn3b.matcher(fs).matches();
        cache(formatString, formatIndex, result);
        return result;
    }

    /**
     * 给定的日期格式索引是否为Excel内置日期时间格式
     * @param format 日期格式索引
     */
    public static boolean isInternalDateTimeFormat(int format) {
        switch(format) {
            // Excel内部时间格式
            //          0x12,       "h:mm AM/PM"  
            //          0x13,       "h:mm:ss AM/PM"  
            //          0x14,       "h:mm"  
            //          0x15,       "h:mm:ss"  
            //          0x16,       "m/d/yy h:mm"  
            //          0x2d,       "mm:ss"  
            //          0x2e,       "[h]:mm:ss"  
            //          0x2f,       "mm:ss.0"  
            case 0x12:
            case 0x13:
            case 0x14:
            case 0x15:
            case 0x16:
            case 0x2d:
            case 0x2e:
            case 0x2f:
                return true;
        }
        return false;
    }

    /**
     * 检查给定的double数值是否是有效的Excel日期
     * value>=0,表示是有效日期
     */
    public static boolean isValidExcelDate(double value) {
        return (value > -Double.MIN_VALUE);
    }

    /************************ 以下是org.apache.poi.ss.usermodel.DateUtil常用方法   ******************************/

    /**
     * 将一个给的的日期转换为一个double类型的数值,代表日期在Excel内部的表现形式
     * double的整数部分表示自1900年1月1日以来的天数,小数表示时分秒
     * @param date : date
     * @param use1904windowing : true-使用1904年的日期窗口,false-使用1900年的日期窗口
     * @return 返回日期的Excel表示(如果错误 -检查小于0.1, 返回-1)
     * */
//  public static double getExcelDate(Date date) // 默认使用1900窗口
//  public static double getExcelDate(Date date, boolean use1904windowing)

    /**
     * 将一个double类型的数值根据Excel内部日期格式转换为一个java.util.Date
     * 
     * 注意:
     * Excel日期和时间存储没有任何时区(TimeZone)信息
     * 如果默认的TimeZone使用了夏令时(Daylight Saving Time),则转换回Excel日期可能不会产生相同的值
     * 
     * @param date Excel内部日期值,一个double类型的数值
     * @param use1904windowing true-使用1904年的日期窗口,false-使用1900年的日期窗口
     * @param tz 对应的时区,如果为null,使用系统默认时区
     * @param roundSeconds 是否round最接近的秒,默认false
     * @return 日期的Java表示形式,如果日期不是有效的Excel日期,则为null
     * */
//  public static Date getJavaDate(double date)
//  public static Date getJavaDate(double date, boolean use1904windowing)
//  public static Date getJavaDate(double date, TimeZone tz) // 使用时区tz去格式化日期
//  public static Date getJavaDate(double date, boolean use1904windowing, TimeZone tz)
//  public static Date getJavaDate(double date, boolean use1904windowing, TimeZone tz, boolean roundSeconds)

    /**
     * 将一个格式为HH:MM或HH:MM:SS的时间字符串转换为Excel日期格式的double数值,一个小数
     * @param timeStr 如12:12、 12:12:12,timeStr长度小于4或大于8都会抛出异常
     * @return 返回一个0~1之间的数
     */
//  public static double convertTime(String timeStr)
//  private static double convertTimeInternal(String timeStr)
}

测试类:

package org.apache.poi.util;

/**
 * 
 * 索引         时间样式字符串
 * 18,       "h:mm AM/PM"  
 * 19,       "h:mm:ss AM/PM"  
 * 20,       "h:mm"  
 * 21,       "h:mm:ss"  
 * 22,       "m/d/yy h:mm"  
 * 45,       "mm:ss"  
 * 46,       "[h]:mm:ss"  
 * 47,       "mm:ss.0" 
 * 
 * 176    [DBNum1][$-804]上午/下午h"时"mm"分";@
 * 177    [DBNum1][$-804]h"时"mm"分";@
 * 178    [$-409]h:mm:ss\ AM/PM;@
 * 179    [$-409]h:mm\ AM/PM;@
 * 180    [$-F400]h:mm:ss\ AM/PM
 * 181    h:mm;@
 * 182    h:mm:ss;@
 * 183    h"时"mm"分";@
 * 184    h"时"mm"分"ss"秒";@
 * 185    上午/下午h"时"mm"分";@
 * 186    上午/下午h"时"mm"分"ss"秒";@ 
 * */
public class TimeFormatTest {
    public static void main(String[] args) {
        System.out.println("Excel已定义时间格式字符串: ");
        System.out.println("字符串 - h:mm AM/PM                             是否是时间格式: " + DateTimeUtil.isADateTimeFormat(18, "h:mm AM/PM"));
        System.out.println("字符串 - h:mm:ss AM/PM                          是否是时间格式: " + DateTimeUtil.isADateTimeFormat(19, "h:mm:ss AM/PM"));
        System.out.println("字符串 - h:mm                                   是否是时间格式: " + DateTimeUtil.isADateTimeFormat(20, "h:mm"));
        System.out.println("字符串 - h:mm:ss                                是否是时间格式: " + DateTimeUtil.isADateTimeFormat(21, "h:mm:ss"));
        System.out.println("字符串 - m/d/yy h:mm                            是否是时间格式: " + DateTimeUtil.isADateTimeFormat(22, "m/d/yy h:mm"));
        System.out.println("字符串 - mm:ss                                  是否是时间格式: " + DateTimeUtil.isADateTimeFormat(45, "mm:ss"));
        System.out.println("字符串 - [h]:mm:ss                              是否是时间格式: " + DateTimeUtil.isADateTimeFormat(46, "[h]:mm:ss"));
        System.out.println("字符串 - mm:ss.0                                                                                           是否是时间格式: " + DateTimeUtil.isADateTimeFormat(47, "mm:ss.0"));
        System.out.println("字符串 - [DBNum1][$-804]上午/下午h\"时\"mm\"分\";@  是否是时间格式: " + DateTimeUtil.isADateTimeFormat(176, "[DBNum1][$-804]上午/下午h\"时\"mm\"分\";@"));
        System.out.println("字符串 - [DBNum1][$-804]h\"时\"mm\"分\";@          是否是时间格式: " + DateTimeUtil.isADateTimeFormat(177, "[DBNum1][$-804]h\"时\"mm\"分\";@"));
        System.out.println("字符串 - [$-409]h:mm:ss\\ AM/PM;@               是否是时间格式: " + DateTimeUtil.isADateTimeFormat(178, "[$-409]h:mm:ss\\ AM/PM;@"));
        System.out.println("字符串 - [$-409]h:mm\\ AM/PM;@                  是否是时间格式: " + DateTimeUtil.isADateTimeFormat(179, "[$-409]h:mm\\ AM/PM;@"));
        System.out.println("字符串 - [$-F400]h:mm:ss\\ AM/PM                是否是时间格式: " + DateTimeUtil.isADateTimeFormat(180, "[$-F400]h:mm:ss\\ AM/PM"));
        System.out.println("字符串 - h:mm;@                                 是否是时间格式: " + DateTimeUtil.isADateTimeFormat(181, "h:mm;@"));
        System.out.println("字符串 - h:mm:ss;@                              是否是时间格式: " + DateTimeUtil.isADateTimeFormat(182, "h:mm:ss;@"));
        System.out.println("字符串 - h\"时\"mm\"分\";@                        是否是时间格式: " + DateTimeUtil.isADateTimeFormat(183, "h\"时\"mm\"分\";@"));
        System.out.println("字符串 - h\"时\"mm\"分\"ss\"秒\";@                是否是时间格式: " + DateTimeUtil.isADateTimeFormat(184, "h\"时\"mm\"分\"ss\"秒\";@"));
        System.out.println("字符串 - 上午/下午h\"时\"mm\"分\";@                 是否是时间格式: " + DateTimeUtil.isADateTimeFormat(185, "上午/下午h\"时\"mm\"分\";@"));
        System.out.println("字符串 - 上午/下午h\"时\"mm\"分\"ss\"秒\";@          是否是时间格式: " + DateTimeUtil.isADateTimeFormat(186, "上午/下午h\"时\"mm\"分\"ss\"秒\";@"));

        System.out.println();
        System.out.println("Excel自定义时间格式字符串:");
        System.out.println("字符串 - [红色]上午/下午h\"时\"mm\"分\"ss\"秒\";@          是否是时间格式: " + DateTimeUtil.isADateTimeFormat(187, "[红色]上午/下午h\"时\"mm\"分\"ss\"秒\";@"));
        System.out.println("字符串 - [绿色]yyyy/mm/dd hh:mm          是否是时间格式: " + DateTimeUtil.isADateTimeFormat(188, "[绿色]yyyy/mm/dd hh:mm"));
    }
}

执行结果:

Excel已定义时间格式字符串: 
字符串 - h:mm AM/PM                            是否是时间格式: true
字符串 - h:mm:ss AM/PM                         是否是时间格式: true
字符串 - h:mm                                  是否是时间格式: true
字符串 - h:mm:ss                               是否是时间格式: true
字符串 - m/d/yy h:mm                           是否是时间格式: true
字符串 - mm:ss                                 是否是时间格式: true
字符串 - [h]:mm:ss                             是否是时间格式: true
字符串 - mm:ss.0                                                                                           是否是时间格式: true
字符串 - [DBNum1][$-804]上午/下午h"时"mm"分";@  是否是时间格式: true
字符串 - [DBNum1][$-804]h"时"mm"分";@          是否是时间格式: true
字符串 - [$-409]h:mm:ss\ AM/PM;@              是否是时间格式: true
字符串 - [$-409]h:mm\ AM/PM;@                 是否是时间格式: true
字符串 - [$-F400]h:mm:ss\ AM/PM               是否是时间格式: true
字符串 - h:mm;@                               是否是时间格式: true
字符串 - h:mm:ss;@                            是否是时间格式: true
字符串 - h"时"mm"分";@                         是否是时间格式: true
字符串 - h"时"mm"分"ss"秒";@                   是否是时间格式: true
字符串 - 上午/下午h"时"mm"分";@                 是否是时间格式: true
字符串 - 上午/下午h"时"mm"分"ss"秒";@           是否是时间格式: true

Excel自定义时间格式字符串:
字符串 - [红色]上午/下午h"时"mm"分"ss"秒";@      是否是时间格式: true
字符串 - [绿色]yyyy/mm/dd hh:mm                是否是时间格式: true

这里写图片描述

4.注意

4.1 只有时间

  • 在Excel一个Sheet单元格中输入:11:12:13
  • 设置单元格样式为时间:h:mm:ss;@
  • 11:12:13单元格为Numeric类型,以double值存储,不是一个字符串单元格
    因为只有时间,而没有日期,所以double只有小数,整部部分为0,存储的值是一个小于1的数。
    当前Sheet的时间单元格:
    这里写图片描述
    存储:
    这里写图片描述
    这里写图片描述

4.2 日期小于1900-1-1

  • 一个单元格为:1899/12/31,一个为:1900/1/1
  • 设置单元格样式为日期:yyyy”年”m”月”d”日”;@
  • 1899/12/31会以字符串存储,日期样式不起效
  • 1900/1/1会以double值存储,日期样式起效,显示为1900年1月1日
    这里写图片描述
  • sheet1.xml存储Sheet单元格信息
    这里写图片描述
  • SharedStrings.xml存储Excel中所有Sheet字符型单元格的文本值
    这里写图片描述
  • styles.xml存储Excel中所有Sheet的所有单元格样式信息
    这里写图片描述
  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Excel操作练习题的讲解可以从以下几个方面展开: 1. 标题和副标题:在Excel的工作表中,应该为每一列的数据设置一个有意义的标题,以便于理解和查找。同时,可以增加副标题,对整个工作表进行简单的介绍。 2. 数据输入:在练习题中,我们需要将一些数据输入到Excel中。可以通过直接输入或者复制粘贴的方式将数据输入到工作表的特定位置。 3. 数据格式化:在Excel中,我们可以对数据进行格式化,使其更符合我们的需求。例如,可以将某一列的数据格式设置为货币格式,或者将日期格式设置成年/月/日的形式。 4. 公式的应用:在Excel中,我们可以通过使用公式来进行简单的计算。例如,可以使用SUM函数求和、AVERAGE函数求平均值,或者使用IF函数进行逻辑判断。 5. 数据筛选和排序:在Excel中,我们可以通过使用筛选和排序功能来对数据进行筛选和排列。例如,可以根据某个条件进行筛选,或者按照某一列的数值大小进行升序或降序排列。 6. 图表的制作:Excel可以将数据以图表的形式展示,使得数据更直观和易于理解。可以选择合适的图表类型,如柱状图、折线图等,并对其进行适当的调整和美化。 7. 打印设置:在Excel中,我们可以对打印设置进行调整,使得输出的纸质文档更符合我们的需求。可以设置打印区域、页面方向、页边距等。 通过以上几个方面的讲解,可以帮助学习者更好地理解Excel的操作技巧,并能够熟练地完成相关的练习题目。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值