爲easyexcel設置TimeZone

寫在前面

導出Excel是系統中常常用到的功能。實現的方案也不少,能夠本身去封裝Apache Poi,也能夠直接使用別人已經封裝好的類庫。若是需求簡單的話,本身作實現也是能夠的,全部的bug和feature都將是可控的。使用第三方的類庫主要是方便,避免重複造輪子,但很差地方在於若是發現bug或者feature不知足時,會嚴重受限於類庫版本的迭代。 html

在導出數據中常常會含有時間,在時間格式化時,若是不指定時區,則會使用服務器的時區進行格式化,這樣可能致使導出的時間不是但願的時間。於是指定時區是一個很重要的功能。因爲EasyExcel沒有提供指定時區的功能,於是須要本身進行解決。前端

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.2</version>
</dependency>

實現方案

EasyExcel提供的可拓展功能爲用戶提供了很大的方便,特別是容許自定義轉化器和監聽器。這裏將使用自定義轉化器的功能進行解決。由於一個表中的時間通常都是在同一個時區,因此應該實現全局時區,同時應該支持動態配置,而不是硬編碼一個時區到代碼中。此外,這裏還提供了一個設置類Date類型屬性的時區的方法。 java

以下爲最終的效果:git

// io.gitlab.donespeak.tutorial.excel.easyexcel.timezone.DateTimeZoneConverterTest.TheDate
@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
public static class TheDate {
    @DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS")
    @ExcelProperty(index = 0)
    private Date date;

    @DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS")
    @DateTimeZone("Asia/Tokyo")
    @ExcelProperty(index = 1)
    private Date jpDate;
}

這裏推薦使用registerConverter方法直接替代ExcelWriterBuilderExcelReaderBuilder中的默認的類型轉化器。雖然也能夠經過指定ExcelProperty.converter的方法進行配置,但仍是會稍顯麻煩。github

// 用 US/Central 去寫入Excel中的時間
EasyExcel.write(file, TheDate.class)
    .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL))
    .sheet("theDate").doWrite(listOriginal);

// 用 US/Central 去讀取Excel中的時間
List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file)
    .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL))
    .head(TheDate.class).sheet().doReadSync();

定義 @DateTimeZone 註解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DateTimeZone {

    /**
     * Specific value reference {@link TimeZone#getAvailableIDs()}
     */
    String value() default "";
}

該註解指定一個Date屬性的時區。apache

實現時區轉化器:Date <-> String

import com.alibaba.excel.converters.date.DateStringConverter;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

import java.text.ParseException;
import java.util.Date;

public class DateTimeZoneStringConverter extends DateStringConverter {

    private final String globalTimeZoneId;

    public DateTimeZoneStringConverter() {
        super();
        globalTimeZoneId = null;
    }

    public DateTimeZoneStringConverter(String timeZoneId) {
        super();
        globalTimeZoneId = timeZoneId;
    }

    @Override
    public Date convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) throws ParseException {

        String timeZoneId = getTimeZoneId(contentProperty);
        String timeFormat = getTimeFormat(contentProperty);

        // System.out.println(String.format("%s: %s: %s", cellData.getStringValue(), timeFormat, timeZoneId));
        Date date = DateUtils.parseDate(cellData.getStringValue(), timeFormat , timeZoneId);
        return date;
    }

    @Override
    public CellData convertToExcelData(Date value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {

        String timeZoneId = getTimeZoneId(contentProperty);
        String timeFormat = getTimeFormat(contentProperty);

        // System.out.println(String.format("%s: %s: %s", value, timeFormat, timeZoneId));
        String excelValue = DateUtils.format(value, timeFormat, timeZoneId);
        return new CellData(excelValue);
    }

    private String getTimeZoneId(ExcelContentProperty contentProperty) {
        if (contentProperty == null) {
            return null;
        }
        return DateTimeZoneUtil.getTimeZone(contentProperty.getField(), globalTimeZoneId);
    }

    private String getTimeFormat(ExcelContentProperty contentProperty) {
        if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
            return null;
        }
        return contentProperty.getDateTimeFormatProperty().getFormat();
    }
}

com.alibaba.excel.converters.date.DateStringConverter 是EasyExcel定義的用於將Date導出爲String的轉化器。此外還有將Date轉化爲Number的轉化器com.alibaba.excel.converters.date.DateNumberConverter服務器

爲了方便,DateTimeZoneStringConverter直接繼承了DateStringConverter,並覆蓋用於轉化的兩個方法convertToJavaData()convertToExcelData()。看起來修改了不少,實際上沒有太大的改動,就增長了一個獲取時區的方法和在SimpleDateFormat中增長了TimeZonexss

這裏的DateUtils是重寫的DateUtils,EasyExcel中的com.alibaba.excel.util.DateUtils的實現沒有支持TimeZone。ide

import com.alibaba.excel.util.StringUtils;

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

public class DateUtils {

    public static final String DATE_FORMAT_10 = "yyyy-MM-dd";
    public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss";
    public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss";
    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() {
        throw new AssertionError("DateUtils can't be instantiated.");
    }

    /**
     * convert string to date
     */
    public static Date parseDate(String dateString, String dateFormat, String timeZone) throws ParseException {
        if (StringUtils.isEmpty(dateFormat)) {
            dateFormat = switchDateFormat(dateString);
        }
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        if(!StringUtils.isEmpty(timeZone)) {
            sdf.setTimeZone(TimeZone.getTimeZone(timeZone));
        }
        return sdf.parse(dateString);
    }

    /**
     * convert string to date
     */
    public static Date parseDate(String dateString) throws ParseException {
        return parseDate(dateString, switchDateFormat(dateString), null);
    }

    /**
     * switch date format
     */
    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;
            case 10:
                return DATE_FORMAT_10;
            default:
                throw new IllegalArgumentException("can not find date format for:" + dateString);
        }
    }

    /**
     * Format date
     * <p>
     * yyyy-MM-dd HH:mm:ss
     */
    public static String format(Date date, String timeZone) {
        return format(date, null, timeZone);
    }

    /**
     * Format date
     *
     * 當dateFormat爲空時,默認使用 yyyy-MM-dd HH:mm:ss
     */
    public static String format(Date date, String dateFormat, String timeZone) {
        if (date == null) {
            return "";
        }
        if (StringUtils.isEmpty(dateFormat)) {
            dateFormat = DATE_FORMAT_19;
        }
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        if(!StringUtils.isEmpty(timeZone)) {
            sdf.setTimeZone(TimeZone.getTimeZone(timeZone));
        }
        return sdf.format(date);
    }
}

單獨封裝了TimeZone獲取的方法。gitlab

import com.alibaba.excel.util.StringUtils;
import java.lang.reflect.Field;

public class DateTimeZoneUtil {

    public static String getTimeZone(Field field, String defaultTimeZoneId) {
        DateTimeZone dateTimeZone = field.getAnnotation(DateTimeZone.class);
        if (dateTimeZone == null) {
            // 若是Field沒有DateTimeZone註解,則使用全局的
            return defaultTimeZoneId;
        }
        String timeZoneId = dateTimeZone.value();
        if (StringUtils.isEmpty(timeZoneId)) {
            // 若是Field的DateTimeZone註解的值爲空,則使用全局的
            return defaultTimeZoneId;
        }
        return timeZoneId;
    }
}

實現時區轉化器:Date <-> Number

import com.alibaba.excel.converters.date.DateNumberConverter;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.DateUtil;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

@Slf4j
public class DateTimeZoneNumberConverter extends DateNumberConverter {

    private final String globalTimeZoneId;

    public DateTimeZoneNumberConverter() {
        this(null);
    }

    public DateTimeZoneNumberConverter(String timeZoneId) {
        super();
        this.globalTimeZoneId = timeZoneId;
    }

    @Override
    public Date convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {

        TimeZone timeZone = getTimeZone(contentProperty);
        boolean use1904windowing = getUse1904windowing(contentProperty, globalConfiguration);

        return DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(), use1904windowing, timeZone);
    }

    @Override
    public CellData convertToExcelData(Date value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {

        TimeZone timeZone = getTimeZone(contentProperty);
        Calendar calendar = getCalendar(value, timeZone);

        boolean use1904windowing = getUse1904windowing(contentProperty, globalConfiguration);

        CellData cellData = new CellData(BigDecimal.valueOf(DateUtil.getExcelDate(calendar, use1904windowing)));

        return cellData;
    }

    private TimeZone getTimeZone(ExcelContentProperty contentProperty) {
        if(contentProperty == null) {
            return null;
        }
        String timeZoneId = DateTimeZoneUtil.getTimeZone(contentProperty.getField(), globalTimeZoneId);
        return TimeZone.getTimeZone(timeZoneId);
    }

    private Calendar getCalendar(Date date, TimeZone timeZone) {
        Calendar calStart = Calendar.getInstance();
        calStart.setTime(date);
        if(timeZone != null) {
            calStart.setTimeZone(timeZone);
        }

        return calStart;
    }

    private boolean getUse1904windowing(ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
            return contentProperty.getDateTimeFormatProperty().getUse1904windowing();
        } else {
            return globalConfiguration.getUse1904windowing();
        }
    }
}

相似DateTimeZoneStringConverterDateTimeZoneNumberConverter繼承了DateNumberConverter,提供了一個DateNumbe之間轉化的轉化器。

測試

以下的單元測試,對@DateTimeZoneDateTimeZoneStringConverterDateTimeZoneNumberConverter均進行了測試。同時這也是一個完整的使用案例。

package io.gitlab.donespeak.tutorial.excel.easyexcel.timezone;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;

/**
 * @author DoneSpeak
 * @date 2019/11/21 22:01
 */
public class DateTimeZoneConverterTest {
    @Getter
    @Setter
    @ToString
    @EqualsAndHashCode
    @NoArgsConstructor
    public static class TheDate {
        @DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS")
        @ExcelProperty(index = 0)
        private Date date;

        @DateTimeFormat("yyyy-MM-dd hh:mm:ss:SSS")
        @DateTimeZone("Asia/Tokyo")
        @ExcelProperty(index = 1)
        private Date jpDate;
    }

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    /**
     * https://www.zeitverschiebung.net/cn/all-time-zones.html
     */
    private static final String TIME_ZONE_ID_US_CENTRAL = "US/Central";
    private static final String TIME_ZONE_ID_ETC_UTC = "Etc/UTC";
    private static final String TIME_ZONE_ID_JP = "Asia/Tokyo"; // UTC+9

    public File getTestDirectory() {
        // return new File(""); // 使用本地路徑,方便生成的文件
        return temporaryFolder.getRoot();
    }

    @Test
    public void testDateTimeZoneStringConverter() {
        File file = new File(getTestDirectory(), "easyexcel-test-dateTimeZoneStringConverter.xlsx");

        if(file.exists()) {
            file.delete();
        }
        List<TheDate> listOriginal = data();

        // 用 US/Central 去寫入Excel中的時間
        EasyExcel.write(file, TheDate.class)
            .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL))
            .sheet("theDate").doWrite(listOriginal);

        // 用 US/Central 去讀取Excel中的時間
        List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file)
            .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL))
            .head(TheDate.class).sheet().doReadSync();

        assertListEquals(listOriginal, listUsCentralWriteUsCentralRead);

        // 用 UTC 時區去讀取Excel中的時間
        List<TheDate> listUsCentralWriteEtcUtcRead = EasyExcel.read(file)
            .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_ETC_UTC))
            .head(TheDate.class).sheet().doReadSync();

        System.out.println(listUsCentralWriteEtcUtcRead);

        assertTimeSpan(collectDate(listOriginal, d -> d.getDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getDate()),
            TIME_ZONE_ID_US_CENTRAL, TIME_ZONE_ID_ETC_UTC);
        assertTimeSpan(collectDate(listOriginal, d -> d.getJpDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getJpDate()),
            TIME_ZONE_ID_JP, TIME_ZONE_ID_JP);
    }

    @Test
    public void testDateTimeZoneNumberConverter() {
        File file = new File(getTestDirectory(), "easyexcel-test-dateTimeZoneNumberConverter.xlsx");

        if(file.exists()) {
            file.delete();
        }
        List<TheDate> listOriginal = data();

        // 用 US/Central 去寫入Excel中的時間
        EasyExcel.write(file, TheDate.class)
            .registerConverter(new DateTimeZoneNumberConverter(TIME_ZONE_ID_US_CENTRAL))
            .sheet("theDate").doWrite(listOriginal);

        // 用 US/Central 去讀取Excel中的時間
        List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file)
            .registerConverter(new DateTimeZoneNumberConverter(TIME_ZONE_ID_US_CENTRAL))
            .head(TheDate.class).sheet().doReadSync();

        assertListEquals(listOriginal, listUsCentralWriteUsCentralRead);

        // 用 UTC 時區去讀取Excel中的時間
        List<TheDate> listUsCentralWriteEtcUtcRead = EasyExcel.read(file)
            .registerConverter(new DateTimeZoneNumberConverter(TIME_ZONE_ID_ETC_UTC))
            .head(TheDate.class).sheet().doReadSync();

        assertTimeSpan(collectDate(listOriginal, d -> d.getDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getDate()),
            TIME_ZONE_ID_US_CENTRAL, TIME_ZONE_ID_ETC_UTC);
        assertTimeSpan(collectDate(listOriginal, d -> d.getJpDate()), collectDate(listUsCentralWriteEtcUtcRead, d -> d.getJpDate()),
            TIME_ZONE_ID_JP, TIME_ZONE_ID_JP);
    }

    private List<TheDate> data() {
        Date now = getTime();

        List<TheDate> datas = new ArrayList<>();

        TheDate thd = new TheDate();
        thd.setDate(now);
        thd.setJpDate(now);

        datas.add(thd);
        return datas;
    }

    private Date getTime() {
        // 這裏的時間保留保留位數應該和@DateTimeFormat一致,不然值比較時將會不相等
        return new Date();
    }

    private long getTimeSpan(Date from, Date to) {
        return from.getTime() - to.getTime();
    }
    private long getTimeZoneTimeSpan(String timeZoneIdfrom, String timeZoneIdTo) {
        return TimeZone.getTimeZone(timeZoneIdfrom).getRawOffset()
            - TimeZone.getTimeZone(timeZoneIdTo).getRawOffset();
    }

    private void assertListEquals(List<TheDate> listOriginal, List<TheDate> listUsCentral) {
        assertEquals(listOriginal.size(), listUsCentral.size());
        for(int i = 0; i < listOriginal.size(); i ++) {
            TheDate original = listOriginal.get(i);
            TheDate usCentral = listUsCentral.get(i);
            assertEquals(original, usCentral);
        }
    }

    private void assertTimeSpan(List<Date> dateOriginal, List<Date> dateOperated, String timeZoneWrite,
        String timeZoneRead) {

        long timeZoneSpanFromUsCentralToEtcUtc = getTimeZoneTimeSpan(timeZoneWrite, timeZoneRead);

        for(int i = 0; i < dateOriginal.size(); i ++) {
            // 對於同一個時間字符串,A時區 - B時區 = B時區解釋 - A時區解釋
            long span = getTimeSpan(dateOperated.get(i), dateOriginal.get(i));
            assertEquals(timeZoneSpanFromUsCentralToEtcUtc, span);
        }
    }

    private List<Date> collectDate( final List<TheDate> list, Function<TheDate, Date> function) {
        return list.stream().map(function).collect(Collectors.toList());
    }
}

拓展 - 聊聊EasyExcel的轉化器

寫過程

EasyExcel.write(file, TheDate.class)
    .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL))
    .sheet("theDate").doWrite(listOriginal);
  • EasyExcel.write(file, TheDate.class): 會建立一個 ExcelWriterBuilder,目前也僅僅是設置了文件輸出路徑和表頭格式。
  • registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)): 爲 ExcelWriterBuilder.writeWorkbook添加自定義轉化器。
  • sheet("theDate"): 建立ExcelWriterSheetBuilder,並配置ExcelWriter的上下文,也就是轉化器等信息。
  • .doWrite(listOriginal): ExcelWriter將列表生成excel文件。

轉化器的配置就發生在sheet("theDate")中。按照:

ExcelWriterSheetBuilder.sheet() -> ExcelWriterSheetBuilder.build()
    -> new ExcelWriter(writeWorkbook) -> new ExcelBuilderImpl(writeWorkbook)
    -> new WriteContextImpl(writeWorkbook) -> WirteContextImpl.initCurrentSheetHolder(writeSheet)
    -> new WriteSheetHolder(writeSheet, writeWorkbookHolder) -> new AbstractWriteHolder()

到這裏就能夠找到配置Converter的代碼了:

// 配置默認Converter
if (parentAbstractWriteHolder == null) {
    setConverterMap(DefaultConverterLoader.loadDefaultWriteConverter());
} else {
    setConverterMap(new HashMap<String, Converter>(parentAbstractWriteHolder.getConverterMap()));
}
// 配置自定義Conveter
if (writeBasicParameter.getCustomConverterList() != null
    && !writeBasicParameter.getCustomConverterList().isEmpty()) {
    for (Converter converter : writeBasicParameter.getCustomConverterList()) {
        getConverterMap().put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter);
    }
}

com.alibaba.excel.converters包下有EasyExcel提供的默認的Converter。在配置默認Converter的流程中,DefaultConverterLoader.loadDefaultWriteConverter()將默認的轉化器進行加載。返回一個以converter.supportJavaTypeKey()構成的key,converter做爲value的Map,加載完成以後會有以下的列表(映射關係中會將基本類型轉化爲封裝類型):

BigDecimal.class:   BigDecimalNumberConverter
Boolean.class:      BooleanBooleanConverter
Byte.class:         ByteNumberConverter
Date.class:         DateStringConverter
Double.class:       DoubleNumberConverter
Float.class:        FloatNumberConverter
Integer.class:      IntegerNumberConverter
Long.class:         LongNumberConverter
Short.class:        ShortNumberConverter
String.class:       StringStringConverter
File.class:         FileImageConverter
InpurtStream.class: InputStreamImageConverter
byte[].class:       ByteArrayImageConverter
Byte[].class:       BoxingByteArrayImageConverter
URL.class:          UrlImageConverter

若是有自定義的Converter,則會使用自動定義的Conveter,則會根據supportJavaTypeKey替換原來的默認的Converter。

在寫入的時候,由AbstractExcelWriteExecutor根據數據的類型,獲取正確的轉化器將JavaObject轉化爲正確的CellData

讀過程

List<TheDate> listUsCentralWriteUsCentralRead = EasyExcel.read(file)
    .registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL))
    .head(TheDate.class).sheet().doReadSync();
  • EasyExcel.read(file): 建立ExcelReaderBuilder對象,配置輸入文件位置,默認表頭和默認監聽器。
  • registerConverter(new DateTimeZoneStringConverter(TIME_ZONE_ID_US_CENTRAL)): 爲ExcelReaderBuilder.readWorkbook添加自定義轉化器。
  • head(TheDate.class): 設置表頭。
  • sheet(): 建立ExcelReaderSheetBuilder,並配置ExcelReader的上下文,也就是轉化器等信息。
  • doReadSync(): 同步讀,將數據從文件中讀取到對象列表中。

和寫過程相似,轉化器的配置就發生在sheet()中,且過程基本是同樣的。按照:

ExcelReaderSheetBuilder.sheet() -> ExcelReaderSheetBuilder.build()
    -> new ExcelReader(readWorkbook) -> new ExcelAnalyserImpl(readWorkbook)
    -> new AnalysisContextImpl(readWorkbook) -> new ReadWorkbookHolder(readWorkbook)
    -> new AbstractReadHolder()

到這裏就能夠找到配置Converter的代碼了:

if (parentAbstractReadHolder == null) {
    setConverterMap(DefaultConverterLoader.loadDefaultReadConverter());
} else {
    setConverterMap(new HashMap<String, Converter>(parentAbstractReadHolder.getConverterMap()));
}
if (readBasicParameter.getCustomConverterList() != null
    && !readBasicParameter.getCustomConverterList().isEmpty()) {
    for (Converter converter : readBasicParameter.getCustomConverterList()) {
        getConverterMap().put(
            ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()),
            converter);
    }
}

和寫過程不一樣,讀過程經過DefaultConverterLoader.loadDefaultReadConverter()加載映射關係,加載以後能夠獲得由converter.supportJavaTypeKey()converter.supportExcelTypeKey()構成的key,以converter爲value的map,有以下的映射列表:

BigDecimal.class <- CellDataTypeEnum.BOOLEAN:   BigDecimalBooleanConverter
BigDecimal.class <- CellDataTypeEnum.NUMBER:    BigDecimalNumberConverter
BigDecimal.class <- CellDataTypeEnum.STRING:    BigDecimalStringConverter

Boolean.class <- CellDataTypeEnum.BOOLEAN:      BooleanBooleanConverter
Boolean.class <- CellDataTypeEnum.NUMBER:       BooleanNumberConverter
Boolean.class <- CellDataTypeEnum.STRING:       BooleanStringConverter

Byte.class <- CellDataTypeEnum.BOOLEAN:     ByteBooleanConverter
Byte.class <- CellDataTypeEnum.NUMBER:      ByteNumberConverter
Byte.class <- CellDataTypeEnum.STRING:      ByteStringConverter

Date.class <- CellDataTypeEnum.NUMBER:      DateNumberConverter
Date.class <- CellDataTypeEnum.STRING:      DateStringConverter

Double.class <- CellDataTypeEnum.BOOLEAN:   DoubleBooleanConverter
Double.class <- CellDataTypeEnum.NUMBER:    DoubleNumberConverter
Double.class <- CellDataTypeEnum.STRING:    DoubleStringConverter

Float.class <- CellDataTypeEnum.BOOLEAN:    FloatBooleanConverter
Float.class <- CellDataTypeEnum.NUMBER:     FloatNumberConverter
Float.class <- CellDataTypeEnum.STRING:     FloatStringConverter

Integer.class <- CellDataTypeEnum.BOOLEAN:  IntegerBooleanConverter
Integer.class <- CellDataTypeEnum.NUMBER:   IntegerNumberConverter
Integer.class <- CellDataTypeEnum.STRING:   IntegerStringConverter

Long.class <- CellDataTypeEnum.BOOLEAN:     LongBooleanConverter
Long.class <- CellDataTypeEnum.NUMBER:      LongNumberConverter
Long.class <- CellDataTypeEnum.STRING:      LongStringConverter

Long.class <- CellDataTypeEnum.BOOLEAN:     LongBooleanConverter
Long.class <- CellDataTypeEnum.NUMBER:      LongNumberConverter
Long.class <- CellDataTypeEnum.STRING:      LongStringConverter

Short.class <- CellDataTypeEnum.BOOLEAN:    ShortBooleanConverter
Short.class <- CellDataTypeEnum.NUMBER:     ShortNumberConverter
Short.class <- CellDataTypeEnum.STRING:     ShortStringConverter

String.class <- CellDataTypeEnum.BOOLEAN:   StringBooleanConverter
String.class <- CellDataTypeEnum.NUMBER:    StringNumberConverter
String.class <- CellDataTypeEnum.STRING:    StringStringConverter

String.class <- CellDataTypeEnum.ERROR:     StringErrorConverter

和寫入不一樣,讀具備更多的組合方式,excel文件中的字段類型能夠有多種,對應的javaObject的屬性也能夠有多種。經過這樣的映射關係能夠肯定輸入數據的類型要轉化爲目標數據類型所須要使用到的轉化器。

若是有自定義的Converter,則會使用自動定義的Conveter,則會根據supportJavaTypeKeysupportExcelTypeKey替換原來的默認的Converter。

類型轉化的使用就得看ReadListener的子類的使用了。

參考和其餘

源碼見:tutorial/tutorial-excel
該功能已經提出issue,能夠關注:但願爲DateTimeFormat增長時區參數 #841

前端生成Excel的技術能夠了解:

相關文章
相關標籤/搜索