GO excelize读取excel进行时间类型转换的示例代码(自动转换)

 更新时间:2024年10月25日 09:17:10   作者:chu_yubo  
我们经常会遇到如何自动识别excel中的时间类型数据并转化成对应的 "Y-m-d H:i:s"类型数据,本文小编给大家介绍了GO excelize读取excel进行时间类型转换的示例代码(自动转换),需要的朋友可以参考下

需求分析

需求:如何自动识别excel中的时间类型数据并转化成对应的 "Y-m-d H:i:s"类型数据。

分析:excelize在读取excel时,GetRows() 返回的都是字符串类型,并且有些时间类型的数据会进行转换,如果全部转化成 float64 格式,然后转换成对应的字符串,并且excelize提供函数

func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
    ...
}

可以将float64 转换成time.Time 类型,time.Time 很容易转化成对应"Y-m-d H:i:s"格式字符串类型数据。所以我们的难点就在于如何自动识别excel中时日期时间类型数据

excel 单元格格式

以下3月1日数据写入到excel中,excel都会识别成2024/3/1,但是对应单元格格式不同,转化为常规类型的话大部分都相同

  • 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
}

结果打印

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

由此我们可以看出时间类型打印出来的内容不尽相同,这里我们可以想办法把他们全部转化成45352

这里我们就把他们转化成常规类型,常规类型的数据大部分都为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
	}
    //转化为常规类型,对应style NumFmt 0
	styleId, _ := f.NewStyle(&excelize.Style{NumFmt: 0})
    //示例例中都放入A列,所以将A列数据全部转化成对应的常规类型
	_ = 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
}

再次进行打印

0 - 0 : 45352
1 - 0 : 45352
2 - 0 : 45352
3 - 0 : 45352
4 - 0 : 45352
5 - 0 : 2024-03-01 00:00:00

这时我们就可以看到大部分数据都已经转化成了45352,所以接下来很简单将数据转化成float64类型,再转化成time.time类型,最后转化成我们想要的数据类型

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 说明无法转化成float64 那么有可能本身是字符串时间进行返回
						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
}

打印结果

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

此时可以解决了我们的问题,指定对应的列,转化为常规类型,然后再转化为float64(针对无法转化的数据返回),再转化为time.time类型,最后转化成我们需要的类型

进阶

那么如何自动进行转化?

其实我们可以根据单元格自定义类型来进行转化,正如上面的例子,如当单元格自定义类型为:

  • yyyy"年"m"月"d"日"
  • yyyy/m/d
  • mmm-yy
  • yyyy"年"m"月"
  • yyyy/m/d h:mm
  • ...
  • 等类型的时候我们需要将他们转化成常规类型,然后根据后续操作转化成我们想要的类型。

如何自定类型判断呢?

其实根据上述转化成常规类型的操作我们就可以知道是哪个字段来进行确定单元格格式类型

styleId, _ := f.NewStyle(&excelize.Style{NumFmt: 0})

Style 中的 NumFmt 来进行决定

我们可以看下excelize 中 针对 NumFmt 的注释(在NewStyle方法上面有详细注释),这里我只粘贴部分注释代码

//	 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"时"mm"分"
//	 33    | h"时"mm"分"ss"秒"
//	 34    | 上午/下午 h"时"mm"分"
//	 35    | 上午/下午 h"时"mm"分"ss"秒
//	 36    | yyyy"年"m"月
//	 50    | yyyy"年"m"月
//	 51    | m"月"d"日
//	 52    | yyyy"年"m"月
//	 53    | m"月"d"日
//	 54    | m"月"d"日
//	 55    | 上午/下午 h"时"mm"分
//	 56    | 上午/下午 h"时"mm"分"ss"秒
//	 57    | yyyy"年"m"月
//	 58    | m"月"d"日"

我们可以知道NumFmt 对应的值代表着各种单元格格式,此时我们可以整理一下我们需要将其转化成常规类型的数据(只做参考,并没有全部实验,自己可以把常用的实验一下)

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"月
}

好了,现在我们要转换的单元格格式了,那么接下来我们就可以遍历一下excel的全部单元格格式类型,看一下哪些字段需要进行转化了,不过接下来问题又来了,如何知道excel里面对应的单元格格式呢

如何知道excel中单元格格式

excelize 中提供方法 GetCellStyle() 可以获取该单元格的所有样式对应的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) {
    ...
}

根据styleId 我们可以找到对应的所有样式配置 GetStyle()

// GetStyle provides a function to get style definition by given style index.
func (f *File) GetStyle(idx int) (*Style, error) {
    ...
}

单元格格式 就对应的是 style.NumFmt

这时我们只需要知道每一个单元格的styleId 对应的 style.NumFmt 就可以将数据进行转化(这里做了三个循环,第一遍是获取了对应styleId, 第二遍是更改表样式,将指定类型转化为常规类型,第三遍就是获取对应的数据)

// 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 数组
	styles := make([]int, 0)
	//所有需要更改单元格格式的 styleId 数组
	needChangeStyleIds := make([]int, 0)
    //所更改的cells
	needChangeCells := make([]string, 0)
	if len(rows) > 0 {
		//需要转化成的 style 对应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 说明无法转化成float64 那么有可能本身是字符串时间进行返回
						    	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 判断元素是否再数组内
func InArray[T int | float64 | string](array []T, value T) bool {
	for _, v := range array {
		if v == value {
			return true
		}
	}
	return false
}

通过三次遍历操作,自动转化了时间类型

以上就是GO excelize读取excel进行时间类型转换的示例代码(自动转换)的详细内容,更多关于GO excelize excel时间类型转换的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言fmt.Sprintf格式化输出的语法与实例

    Go语言fmt.Sprintf格式化输出的语法与实例

    Go 可以使用 fmt.Sprintf 来格式化字符串,下面这篇文章主要给大家介绍了关于Go语言fmt.Sprintf格式化输出的语法与实例,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • Go语言字符串常见操作的使用汇总

    Go语言字符串常见操作的使用汇总

    这篇文章主要为大家总结了Go语言中常见的几种字符串操作,例如:位置索引、替换、统计次数等,文中的示例代码讲解详细,感兴趣的可以了解一下
    2022-04-04
  • golang中使用proto3协议导致的空值字段不显示的问题处理方案

    golang中使用proto3协议导致的空值字段不显示的问题处理方案

    这篇文章主要介绍了golang中使用proto3协议导致的空值字段不显示的问题处理方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • golang框架gin的日志处理和zap lumberjack日志使用方式

    golang框架gin的日志处理和zap lumberjack日志使用方式

    这篇文章主要介绍了golang框架gin的日志处理和zap lumberjack日志使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • GoLang channel底层代码分析详解

    GoLang channel底层代码分析详解

    Channel和goroutine的结合是Go并发编程的大杀器。而Channel的实际应用也经常让人眼前一亮,通过与select,cancel,timer等结合,它能实现各种各样的功能。接下来,我们就要梳理一下GoLang channel底层代码实现
    2022-10-10
  • goland 实现自动格式化代码

    goland 实现自动格式化代码

    这篇文章主要介绍了goland 实现自动格式化代码的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Golang测试框架goconvey进行单元测试流程介绍

    Golang测试框架goconvey进行单元测试流程介绍

    goconvey是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多Web界面特性,这篇文章主要介绍了使用goconvey进行单元测试流程,感兴趣的同学可以参考下文
    2023-05-05
  • Golang 正则匹配效率详解

    Golang 正则匹配效率详解

    这篇文章主要介绍了Golang 正则匹配效率详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • golang json性能分析详解

    golang json性能分析详解

    json格式可以算我们日常最常用的序列化格式之一了,Go语言作为一个由Google开发,号称互联网的C语言的语言,自然也对JSON格式支持很好。下面这篇文章主要给大家详细分析介绍了golang json性能的相关资料,需要的朋友可以参考下。
    2018-02-02
  • Go与C语言的互操作实现

    Go与C语言的互操作实现

    在Go与C语言互操作方面,Go更是提供了强大的支持。尤其是在Go中使用C,你甚至可以直接在Go源文件中编写C代码,本文就详细的介绍一下如何使用,感兴趣的可以了解一下
    2021-12-12

最新评论