本文的重点是解决两个问题:
1、POI识别Excel的时间包含中文,例如:2021年1月22日
2、POI识别Excel中单元格为日期类型或时间类型的时间数据
前言:
本文主要以示例解决以上两个问题,为了突出主要解决问题的代码,所以就以xlsx类型表格的识别方式为例。
前言:
主要以 xxxx年xx月xx日 类型时间为例进行演示
背景:
自POI 3.X版本开始,poi便无法识别Excel中的中文时间了。然而,当我们的Excel中出现中文的时候,我们仍然是要对其进行识别的。
分析:
poi中对于数据类型的识别,主要用到的是org.apache.poi.ss.usermodel.DateUtil包中的DateUtil.isCellDateFormatted(Cell cell) 方法。通过查看方法isCellDateFormatted的源码,我们发现,其中关于对时间数据进行分析的方法 isADateFormat 仅对符号进行了分析,没有对相对应的中文数据进行分析,例如‘年’、‘月’、‘日’等关键的中文字符。
//以下 isADateFormat 方法中对于时间数据的分析源码
if (separatorIndex < length - 1) {
char nc = fs.charAt(separatorIndex + 1);
if (c == '\\') {
switch(nc) {
case ' ':
case ',':
case '-':
case '.':
case '\\':
continue;
}
} else if (c == ';' && nc == '@') {
++separatorIndex;
continue;
}
}
解决方案:
为了实现POI中的 isCellDateFormatted 能够对中文时间进行解析,我们需要重写一下该方法,下列是修改后的方法,可直接复制使用
//直接创建一个DateFormatUtil的工具类,当我们需要对时间类型进行判断时,直接使用DateFormatUtil.isCellDateFormatted (Cell cell)方法即可
public class DateFormatUtil{
public static boolean isCellDateFormatted(Cell cell)
{
if (cell == null) {
return false;
}
boolean bDate = false;
double d = cell.getNumericCellValue();
if (isValidExcelDate(d)) {
CellStyle style = cell.getCellStyle();
if (style == null) {
return false;
}
int i = style.getDataFormat();
String f = style.getDataFormatString();
bDate = isADateFormat(i, f);
}
return bDate;
}
public static boolean isADateFormat(int formatIndex, String formatString)
{
if (isInternalDateFormat(formatIndex)) {
return true;
}
if ((formatString == null) || (formatString.length() == 0)) {
return false;
}
String fs = formatString;
//下面这一行是自己手动添加的 以支持汉字格式wingzing
fs = fs.replaceAll("[\"|\']","").replaceAll("[年|月|日|时|分|秒|毫秒|微秒]", "");
fs = fs.replaceAll("\\\\-", "-");
fs = fs.replaceAll("\\\\,", ",");
fs = fs.replaceAll("\\\\.", ".");
fs = fs.replaceAll("\\\\ ", " ");
fs = fs.replaceAll(";@", "");
fs = fs.replaceAll("^\\[\\$\\-.*?\\]", "");
fs = fs.replaceAll("^\\[[a-zA-Z]+\\]", "");
return (fs.matches("^[yYmMdDhHsS\\-/,. :]+[ampAMP/]*$"));
}
public static boolean isInternalDateFormat(int format)
{
switch (format) { case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
case 20:
case 21:
case 22:
case 45:
case 46:
case 47:
case 57:
case 58:
return true;
case 23:
case 24:
case 25:
case 26:
case 27:
case 28:
case 29:
case 30:
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
case 38:
case 39:
case 40:
case 41:
case 42:
case 43:
case 44: } return false;
}
public static boolean isValidExcelDate(double value)
{
return (value > -4.940656458412465E-324D);
}
}
讲解POI识别Excel表格中时间数据的问题前,我们先要了解一下,就是Excel表格中,关于时间数据的单元格共有几种定义方式。
废话不多说,直接上图:
如图所示,在Excel表格中共有三种表示时间的单元格类型,分别为日期类型、时间类型和自定义类型。
在上面三种单元格类型中,自定义类型单元格存放的时间数据,可以直接通过getDataFormat()值来判断,常用的时间类型及其对应的format值如下:
时间类型 | format值 |
---|---|
yyyy-MM-dd | 14 |
yyyy年m月d日 | 31 |
yyyy年m月 | 57 |
m月d日 | 58 |
HH:mm | 20 |
h时mm分 | 32 |
下面是我对几种常见时间类型进行处理的代码
//先获取对应的format值
short format = row.getCell(j).getCellStyle().getDataFormat();
SimpleDateFormat sdf = null;
//根据format值,将其转换成对应的时间样式
if (format == 20 || format == 32){
sdf = new SimpleDateFormat("HH:mm");
}else if (format == 14 || format == 31 || format == 57 || format == 58){
sdf = new SimpleDateFormat("yyyy-MM-dd");
}else {
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
分析:
与自定义类型单元格的时间有所区别,日期类型和时间类型单元格的时间数据,我们无法通过format值对其进行判断。所以,在如上的代码中,会出现将“yyyy-MM-dd”和“HH:mm”类型的时间,直接转换成"yyyy-MM-dd HH:mm:ss"格式,既不美观也不符合我们对其格式的需求。
解决方案:(以下的解决方案仅为我个人的处理方法,可能不严谨,仅供参考)
步骤:
直接上代码:
//先获取对应的format值
short format = row.getCell(j).getCellStyle().getDataFormat();
SimpleDateFormat sdf = null;
//根据format值,将其转换成对应的时间样式
if (format == 20 || format == 32){
sdf = new SimpleDateFormat("HH:mm");
}else if (format == 14 || format == 31 || format == 57 || format == 58){
sdf = new SimpleDateFormat("yyyy-MM-dd");
}else {
/**
* 上面处理的时间,单元格格式都是自定义格式中时间格式,而下面解决的是单元格格式为日期格式的问题
* 单元格格式为日期格式时,没办法通过format进行判断,所以进行如下操作:
* 1、先将数据转换成 "yyyy-MM-dd HH:mm:ss"的格式
* 2、将SimpleDateFormat类型的时间转换成String类型,并去除其中的空格、:和-三个符号,存进String类型数组中
* 3、通过数组进行中时分秒的数据进行判断最终要展示的时间样式
* 判断小时为0时,则不显示时分秒;判断秒钟为0时,则不显示年月日
*/
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// split方法去除多个符号,使用 | 分隔符进行简单的配置
// 转化为String类型数组后,我们数组中存储的数据依次为0-年、1-月、2-日、3-时、4-分、5-秒(数组下标-存放的数据)
String[] str = sdf.format(DateUtil.getJavaDate(row.getCell(j).getNumericCellValue())).toString().split("-|:| ");
if (Integer.parseInt(str[3]) == 0 && Integer.parseInt(str[4]) == 0 && Integer.parseInt(str[5]) == 0){
//根据表格,当时分秒都为0时,说明此单元格存储的数据为“yyyy/MM/dd”,此处只显示年月日
sdf = new SimpleDateFormat("yyyy-MM-dd");
}else if (Integer.parseInt(str[3]) != 0 && Integer.parseInt(str[4]) != 0 && Integer.parseInt(str[5]) == 0){
//根据表格,当小时和分钟不为0的情况,说明此单元格存储的数据为“HH:mm”,年月日是不需要的
sdf = new SimpleDateFormat("HH:mm");
}else{
break;
}
}
该解释都在代码中解释了,代码中例子的范围有限,仅供参考,实际还是需要根据自己的数据进行分析。
完整代码:
最后附上我识别xlsx类型表格的完整代码
// 1.根据传入的文件路径获取工作簿
XSSFWorkbook xssfworkbook = new XSSFWorkbook(path);
// 2.根据脚标获取工作表,index从0开始
XSSFSheet sheet = xssfworkbook.getSheetAt(1);
int lastRowNum = sheet.getLastRowNum();
for (int i = 2;i <= lastRowNum; i++) {
XSSFRow row = sheet.getRow(i);
if (row != null){
List list =new ArrayList();
for (int j = 0; j < 13;j++){
if (row.getCell(j) != null || !row.getCell(j).toString().trim().equals("")) {
String value = null;
//判断获取到的数据是否为数字类型数据
if (row.getCell(j).getCellType() == CellType.NUMERIC){
//是数字类型数据,进行类型转换
//获取日期类型
short format = row.getCell(j).getCellStyle().getDataFormat();
//此处运用了重写的isCellDateFormatted方法,在DateFormatUtil工具类中
if (DateFormatUtil.isCellDateFormatted(row.getCell(j))){
SimpleDateFormat sdf =null;
if (format == 20 || format == 32){
sdf = new SimpleDateFormat("HH:mm");
}else if (format == 14 || format == 31 || format == 57 || format == 58){
sdf = new SimpleDateFormat("yyyy-MM-dd");
}else {
/**
* 上面处理的时间,单元格格式都是自定义格式中时间格式,而下面解决的是单元格格式为日期格式的问题
* 单元格格式为日期格式时,没办法通过format进行判断,所以进行如下操作:
* 1、现将数据转换成 "yyyy-MM-dd HH:mm:ss"的格式
* 2、将SimpleDateFormat类型的时间转换成String类型,并去除其中的空格、:和-三个符号,存进String类型数组中
* 3、通过数组进行中时分秒的数据进行判断最终要展示的时间样式
* 判断小时为0时,则不显示时分秒;判断秒钟为0时,则不显示年月日
*/
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String[] str = sdf.format(DateUtil.getJavaDate(row.getCell(j).getNumericCellValue())).toString().split("-|:| ");
if (Integer.parseInt(str[3]) == 0 && Integer.parseInt(str[4]) == 0 && Integer.parseInt(str[5]) == 0){
//根据表格,当时分秒都为0时,说明此单元格为生产日期,此处只显示年月日
sdf = new SimpleDateFormat("yyyy-MM-dd");
}else if (Integer.parseInt(str[3]) != 0 && Integer.parseInt(str[4]) != 0 && Integer.parseInt(str[5]) == 0){
//根据表格,当小时和分钟不为0的情况,说明此单元格为初凝和终凝,年月日是不需要的
sdf = new SimpleDateFormat("HH:mm");
}else{
break;
}
}
double value1 = row.getCell(j).getNumericCellValue();
Date date = DateUtil.getJavaDate(value1);
value = sdf.format(date);
}else{
value = String.valueOf(row.getCell(j));
}
}else {
value = String.valueOf(row.getCell(j));
}
list.add(value);
}
}
//使用for循环list中的数据存储到对应的bean对象中
}
}
//最后根据自己方法体中需要返回的数据进行封装并返回即可