GO excelize讀取excel進(jìn)行時(shí)間類型轉(zhuǎn)換的示例代碼(自動(dòng)轉(zhuǎn)換)
需求分析
需求:如何自動(dòng)識(shí)別excel中的時(shí)間類型數(shù)據(jù)并轉(zhuǎn)化成對(duì)應(yīng)的 "Y-m-d H:i:s"類型數(shù)據(jù)。
分析:excelize在讀取excel時(shí),GetRows() 返回的都是字符串類型,并且有些時(shí)間類型的數(shù)據(jù)會(huì)進(jìn)行轉(zhuǎn)換,如果全部轉(zhuǎn)化成 float64 格式,然后轉(zhuǎn)換成對(duì)應(yīng)的字符串,并且excelize提供函數(shù)
func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
...
}
可以將float64 轉(zhuǎn)換成time.Time 類型,time.Time 很容易轉(zhuǎn)化成對(duì)應(yīng)"Y-m-d H:i:s"格式字符串類型數(shù)據(jù)。所以我們的難點(diǎn)就在于如何自動(dòng)識(shí)別excel中時(shí)日期時(shí)間類型數(shù)據(jù)
excel 單元格格式
以下3月1日數(shù)據(jù)寫入到excel中,excel都會(huì)識(shí)別成2024/3/1,但是對(duì)應(yīng)單元格格式不同,轉(zhuǎn)化為常規(guī)類型的話大部分都相同
- 2024年3月1日------------- yyyy"年"m"月"d"日"-------------453352
- 2024/3/1------------- yyyy/m/d-------------453352
- Mar-24------------- mmm-yy-------------453352
- 2024年3月------------- yyyy"年"m"月"-------------453352
- 2024/3/1 0:00-------------yyyy/m/d h:mm-------------453352
- '2024-03-01 00:00:00-------------通用-------------2024-03-01 00:00:00
excelize 讀取
func parseFileUrl(filePath string) ([]map[string]string, error) {
f, err := excelize.OpenFile(filePath)
if err != nil {
return nil, err
}
sheetName := f.GetSheetName(0)
rows, err := f.GetRows(sheetName)
if err != nil {
return nil, err
}
if len(rows) > 0 {
for rowKey, cols := range rows {
if len(cols) > 0 {
for colKey, value := range cols {
fmt.Println(rowKey, "-", colKey, ":", value)
}
}
}
}
return nil, err
}
結(jié)果打印
0 - 0 : 45352 1 - 0 : 03-01-24 2 - 0 : Mar-24 3 - 0 : 45352 4 - 0 : 3/1/24 00:00 5 - 0 : 2024-03-01 00:00:00
由此我們可以看出時(shí)間類型打印出來的內(nèi)容不盡相同,這里我們可以想辦法把他們?nèi)哭D(zhuǎn)化成45352
這里我們就把他們轉(zhuǎn)化成常規(guī)類型,常規(guī)類型的數(shù)據(jù)大部分都為45352
func parseFileUrl(filePath string) ([]map[string]string, error) {
f, err := excelize.OpenFile(filePath)
if err != nil {
return nil, err
}
sheetName := f.GetSheetName(0)
rows, err := f.GetRows(sheetName)
if err != nil {
return nil, err
}
//轉(zhuǎn)化為常規(guī)類型,對(duì)應(yīng)style NumFmt 0
styleId, _ := f.NewStyle(&excelize.Style{NumFmt: 0})
//示例例中都放入A列,所以將A列數(shù)據(jù)全部轉(zhuǎn)化成對(duì)應(yīng)的常規(guī)類型
_ = f.SetColStyle(sheetName, "A", styleId)
rows, err = f.GetRows(sheetName)
if len(rows) > 0 {
for rowKey, cols := range rows {
if len(cols) > 0 {
for colKey, value := range cols {
fmt.Println(rowKey, "-", colKey, ":", value)
}
}
}
}
return nil, err
}
再次進(jìn)行打印
0 - 0 : 45352 1 - 0 : 45352 2 - 0 : 45352 3 - 0 : 45352 4 - 0 : 45352 5 - 0 : 2024-03-01 00:00:00
這時(shí)我們就可以看到大部分?jǐn)?shù)據(jù)都已經(jīng)轉(zhuǎn)化成了45352,所以接下來很簡單將數(shù)據(jù)轉(zhuǎn)化成float64類型,再轉(zhuǎn)化成time.time類型,最后轉(zhuǎn)化成我們想要的數(shù)據(jù)類型
func parseFileUrl(filePath string) ([]map[string]string, error) {
f, err := excelize.OpenFile(filePath)
if err != nil {
return nil, err
}
sheetName := f.GetSheetName(0)
rows, err := f.GetRows(sheetName)
if err != nil {
return nil, err
}
styleId, _ := f.NewStyle(&excelize.Style{NumFmt: 0})
_ = f.SetColStyle(sheetName, "A", styleId)
rows, err = f.GetRows(sheetName)
if len(rows) > 0 {
for rowKey, cols := range rows {
if len(cols) > 0 {
for colKey, value := range cols {
timeFloat, err := strconv.ParseFloat(value, 64)
if err != nil {
//err 說明無法轉(zhuǎn)化成float64 那么有可能本身是字符串時(shí)間進(jìn)行返回
timeTime, err := time.Parse("2006-01-02 15:04:05", value)
if err != nil {
fmt.Println(rowKey, "-", colKey, ":", value)
} else {
value = timeTime.Format("2006-01-02 15:04:05")
fmt.Println(rowKey, "-", colKey, ":", value)
}
break
}
timeTime, _ := excelize.ExcelDateToTime(timeFloat, false)
value = timeTime.Format("2006-01-02 15:04:05")
fmt.Println(rowKey, "-", colKey, ":", value)
}
}
}
}
return nil, err
}
打印結(jié)果
0 - 0 : 2024-03-01 00:00:00 1 - 0 : 2024-03-01 00:00:00 2 - 0 : 2024-03-01 00:00:00 3 - 0 : 2024-03-01 00:00:00 4 - 0 : 2024-03-01 00:00:00 5 - 0 : 2024-03-01 00:00:00
此時(shí)可以解決了我們的問題,指定對(duì)應(yīng)的列,轉(zhuǎn)化為常規(guī)類型,然后再轉(zhuǎn)化為float64(針對(duì)無法轉(zhuǎn)化的數(shù)據(jù)返回),再轉(zhuǎn)化為time.time類型,最后轉(zhuǎn)化成我們需要的類型
進(jìn)階
那么如何自動(dòng)進(jìn)行轉(zhuǎn)化?
其實(shí)我們可以根據(jù)單元格自定義類型來進(jìn)行轉(zhuǎn)化,正如上面的例子,如當(dāng)單元格自定義類型為:
- yyyy"年"m"月"d"日"
- yyyy/m/d
- mmm-yy
- yyyy"年"m"月"
- yyyy/m/d h:mm
- ...
- 等類型的時(shí)候我們需要將他們轉(zhuǎn)化成常規(guī)類型,然后根據(jù)后續(xù)操作轉(zhuǎn)化成我們想要的類型。
如何自定類型判斷呢?
其實(shí)根據(jù)上述轉(zhuǎn)化成常規(guī)類型的操作我們就可以知道是哪個(gè)字段來進(jìn)行確定單元格格式類型
styleId, _ := f.NewStyle(&excelize.Style{NumFmt: 0})
Style 中的 NumFmt 來進(jìn)行決定
我們可以看下excelize 中 針對(duì) NumFmt 的注釋(在NewStyle方法上面有詳細(xì)注釋),這里我只粘貼部分注釋代碼
// Index | Format String // -------+---------------------------------------------------- // 0 | General // 1 | 0 // 2 | 0.00 // 3 | #,##0 // 4 | #,##0.00 // 5 | ($#,##0_);($#,##0) // 6 | ($#,##0_);[Red]($#,##0) // 7 | ($#,##0.00_);($#,##0.00) // 8 | ($#,##0.00_);[Red]($#,##0.00) // 9 | 0% // 10 | 0.00% // 11 | 0.00E+00 // 12 | # ?/? // 13 | # ??/?? // 14 | m/d/yy // 15 | d-mmm-yy // 16 | d-mmm // 17 | mmm-yy // 18 | h:mm AM/PM // 19 | h:mm:ss AM/PM // 20 | h:mm // 21 | h:mm:ss // 22 | m/d/yy h:mm // ... | ... // 37 | (#,##0_);(#,##0) // 38 | (#,##0_);[Red](#,##0) // 39 | (#,##0.00_);(#,##0.00) // 40 | (#,##0.00_);[Red](#,##0.00) // 41 | _(* #,##0_);_(* (#,##0);_(* "-"_);_(@_) // 42 | _($* #,##0_);_($* (#,##0);_($* "-"_);_(@_) // 43 | _(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_) // 44 | _($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_) // 45 | mm:ss // 46 | [h]:mm:ss // 47 | mm:ss.0 // 48 | ##0.0E+0 // 49 | @ // Number format code in zh-cn language: // // Index | Symbol // -------+------------------------------------------- // 27 | yyyy"年"m"月" // 28 | m"月"d"日" // 29 | m"月"d"日" // 30 | m-d-yy // 31 | yyyy"年"m"月"d"日" // 32 | h"時(shí)"mm"分" // 33 | h"時(shí)"mm"分"ss"秒" // 34 | 上午/下午 h"時(shí)"mm"分" // 35 | 上午/下午 h"時(shí)"mm"分"ss"秒 // 36 | yyyy"年"m"月 // 50 | yyyy"年"m"月 // 51 | m"月"d"日 // 52 | yyyy"年"m"月 // 53 | m"月"d"日 // 54 | m"月"d"日 // 55 | 上午/下午 h"時(shí)"mm"分 // 56 | 上午/下午 h"時(shí)"mm"分"ss"秒 // 57 | yyyy"年"m"月 // 58 | m"月"d"日"
我們可以知道NumFmt 對(duì)應(yīng)的值代表著各種單元格格式,此時(shí)我們可以整理一下我們需要將其轉(zhuǎn)化成常規(guī)類型的數(shù)據(jù)(只做參考,并沒有全部實(shí)驗(yàn),自己可以把常用的實(shí)驗(yàn)一下)
var ConversionTimeNumFmt = []int{
14, //m/d/yy
15, //d-mmm-yy
17, //mmm-yy
22, //m/d/yy h:mm
27, // yyyy"年"m"月"
30, //m-d-yy
31, //yyyy"年"m"月"d"日"
36, //yyyy"年"m"月
50, //yyyy"年"m"月
52, //yyyy"年"m"月
57, //yyyy"年"m"月
}
好了,現(xiàn)在我們要轉(zhuǎn)換的單元格格式了,那么接下來我們就可以遍歷一下excel的全部單元格格式類型,看一下哪些字段需要進(jìn)行轉(zhuǎn)化了,不過接下來問題又來了,如何知道excel里面對(duì)應(yīng)的單元格格式呢
如何知道excel中單元格格式
excelize 中提供方法 GetCellStyle() 可以獲取該單元格的所有樣式對(duì)應(yīng)的styleId
// GetCellStyle provides a function to get cell style index by given worksheet
// name and cell reference. This function is concurrency safe.
func (f *File) GetCellStyle(sheet, cell string) (int, error) {
...
}
根據(jù)styleId 我們可以找到對(duì)應(yīng)的所有樣式配置 GetStyle()
// GetStyle provides a function to get style definition by given style index.
func (f *File) GetStyle(idx int) (*Style, error) {
...
}
單元格格式 就對(duì)應(yīng)的是 style.NumFmt
這時(shí)我們只需要知道每一個(gè)單元格的styleId 對(duì)應(yīng)的 style.NumFmt 就可以將數(shù)據(jù)進(jìn)行轉(zhuǎn)化(這里做了三個(gè)循環(huán),第一遍是獲取了對(duì)應(yīng)styleId, 第二遍是更改表樣式,將指定類型轉(zhuǎn)化為常規(guī)類型,第三遍就是獲取對(duì)應(yīng)的數(shù)據(jù))
// parseFileUrl 解析文件流excel
func parseFileUrl(filePath string) ([]map[string]string, error) {
f, err := excelize.OpenFile(filePath)
if err != nil {
return nil, err
}
sheetName := f.GetSheetName(0)
rows, err := f.GetRows(sheetName)
if err != nil {
return nil, err
}
//讀取excel 所有styleId 數(shù)組
styles := make([]int, 0)
//所有需要更改單元格格式的 styleId 數(shù)組
needChangeStyleIds := make([]int, 0)
//所更改的cells
needChangeCells := make([]string, 0)
if len(rows) > 0 {
//需要轉(zhuǎn)化成的 style 對(duì)應(yīng)style Id
styleIdZero, _ := f.NewStyle(&excelize.Style{NumFmt: 0})
for rowKey, cols := range rows {
if len(cols) > 0 {
for colKey, _ := range cols {
columnNumber, _ := excelize.CoordinatesToCellName(colKey+1, rowKey+1)
styleId, _ := f.GetCellStyle(sheetName, columnNumber)
if !arrayHelper.InArray(styles, styleId) {
styles = append(styles, styleId)
}
}
}
}
fmt.Println(styles)
if len(styles) > 0 {
for _, styleId := range styles {
style, _ := f.GetStyle(styleId)
if arrayHelper.InArray(ConversionTimeNumFmt, style.NumFmt) {
needChangeStyleIds = append(needChangeStyleIds, styleId)
}
}
}
for rowKey, cols := range rows {
if len(cols) > 0 {
for colKey, _ := range cols {
columnNumber, _ := excelize.CoordinatesToCellName(colKey+1, rowKey+1)
styleId, _ := f.GetCellStyle(sheetName, columnNumber)
if arrayHelper.InArray(needChangeStyleIds, styleId) {
_ = f.SetCellStyle(sheetName, columnNumber, columnNumber, styleIdZero)
needChangeCells = append(needChangeCells, columnNumber)
}
}
}
}
rows, err = f.GetRows(sheetName)
if err != nil {
return nil, err
}
if len(rows) > 0 {
for rowKey, cols := range rows {
if len(cols) > 0 {
for colKey, value := range cols {
columnNumber, _ := excelize.CoordinatesToCellName(colKey+1, rowKey+1)
if arrayHelper.InArray(needChangeCells, columnNumber) {
timeFloat, err := strconv.ParseFloat(value, 64)
if err != nil {
//err 說明無法轉(zhuǎn)化成float64 那么有可能本身是字符串時(shí)間進(jìn)行返回
timeTime, err := time.Parse("2006-01-02 15:04:05", value)
if err != nil {
fmt.Println(rowKey, "-", colKey, ":", value)
} else {
value = timeTime.Format("2006-01-02 15:04:05")
fmt.Println(rowKey, "-", colKey, ":", value)
}
break
}
timeTime, _ := excelize.ExcelDateToTime(timeFloat, false)
value = timeTime.Format("2006-01-02 15:04:05")
fmt.Println(rowKey, "-", colKey, ":", value)
}
}
}
}
}
}
return nil, err
}
var ConversionTimeNumFmt = []int{
14, //m/d/yy
15, //d-mmm-yy
17, //mmm-yy
22, //m/d/yy h:mm
27, // yyyy"年"m"月"
30, //m-d-yy
31, //yyyy"年"m"月"d"日"
36, //yyyy"年"m"月
50, //yyyy"年"m"月
52, //yyyy"年"m"月
57, //yyyy"年"m"月
}
其中InArray方法為
// InArray 判斷元素是否再數(shù)組內(nèi)
func InArray[T int | float64 | string](array []T, value T) bool {
for _, v := range array {
if v == value {
return true
}
}
return false
}
通過三次遍歷操作,自動(dòng)轉(zhuǎn)化了時(shí)間類型
以上就是GO excelize讀取excel進(jìn)行時(shí)間類型轉(zhuǎn)換的示例代碼(自動(dòng)轉(zhuǎn)換)的詳細(xì)內(nèi)容,更多關(guān)于GO excelize excel時(shí)間類型轉(zhuǎn)換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go結(jié)構(gòu)體指針引發(fā)的值傳遞思考分析
這篇文章主要為大家介紹了Go結(jié)構(gòu)體指針引發(fā)的值傳遞思考分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
深入了解Golang?interface{}的底層原理實(shí)現(xiàn)
在?Go?語言沒有泛型之前,接口可以作為一種替代實(shí)現(xiàn),也就是萬物皆為的?interface。那到底?interface?是怎么設(shè)計(jì)的底層結(jié)構(gòu)呢?下面咱們透過底層分別看一下這兩種類型的接口原理。感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助2022-10-10
一文帶你理解Golang中的Time結(jié)構(gòu)
根據(jù)golang的time包的文檔可以知道,golang的time結(jié)構(gòu)中存儲(chǔ)了兩種時(shí)鐘,一種是Wall?Clocks,一種是Monotonic?Clocks,下面我們就來簡單了解一下這兩種結(jié)構(gòu)吧2023-09-09

