简介

不管什么时候,处理时间总是让人头疼的一件事情。因为时间格式太多样化了,再加上时区,夏令时,闰秒这些细枝末节处理起来更是困难。所以在程序中,涉及时间的处理我们一般借助于标准库或第三方提供的时间库。今天要介绍的dateparse专注于一个很小的时间处理领域——解析日期时间格式的字符串。

快速使用

本文代码使用 Go Modules。

创建目录并初始化:

  1. $ mkdir dateparse && cd dateparse
  2. $ go mod init github.com/go-quiz/go-daily-lib/dateparse

安装dateparse库:

  1. $ go get -u github.com/araddon/dateparse

使用:

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/araddon/dateparse"
  6. )
  7. func main() {
  8. t1, err := dateparse.ParseAny("3/1/2014")
  9. if err != nil {
  10. log.Fatal(err)
  11. }
  12. fmt.Println(t1.Format("2006-01-02 15:04:05"))
  13. t2, err := dateparse.ParseAny("mm/dd/yyyy")
  14. if err != nil {
  15. log.Fatal(err)
  16. }
  17. fmt.Println(t2.Format("2006-01-02 15:04:05"))
  18. }

ParseAny()方法接受一个日期时间字符串,解析该字符串,返回time.Time类型的值。如果传入的字符串dateparse库无法识别,则返回一个错误。上面程序运行输出:

  1. $ go run main.go
  2. 2014-03-01 00:00:00
  3. 2021/06/24 14:52:39 Could not find format for "mm/dd/yyyy"
  4. exit status 1

需要注意,当我们写出”3/1/2014”这个时间的时候,可以解释为2014年3月1日,也可以解释为2014年1月3日。这就存在二义性,dateparse默认采用mm/dd/yyyy这种格式,也就是2014年3月1日。我们也可以使用ParseStrict()函数让这种具有二义性的字符串解析失败:

  1. func main() {
  2. t, err := dateparse.ParseStrict("3/1/2014")
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. fmt.Println(t.Format("2006-01-02 15:04:05"))
  7. }

运行:

  1. $ go run main.go
  2. 2021/06/24 14:57:18 This date has ambiguous mm/dd vs dd/mm type format
  3. exit status 1

格式

dateparse支持丰富的日期时间格式,基本囊括了所有常用的格式。它支持标准库time中预定义的所有格式:

  1. // src/time/format.go
  2. const (
  3. ANSIC = "Mon Jan _2 15:04:05 2006"
  4. UnixDate = "Mon Jan _2 15:04:05 MST 2006"
  5. RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
  6. RFC822 = "02 Jan 06 15:04 MST"
  7. RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
  8. RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
  9. RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
  10. RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
  11. RFC3339 = "2006-01-02T15:04:05Z07:00"
  12. RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
  13. Kitchen = "3:04PM"
  14. // Handy time stamps.
  15. Stamp = "Jan _2 15:04:05"
  16. StampMilli = "Jan _2 15:04:05.000"
  17. StampMicro = "Jan _2 15:04:05.000000"
  18. StampNano = "Jan _2 15:04:05.000000000"
  19. )

支持的完整格式查看dateparse README

时区

dateparse支持在特定时区解析日期时间字符串。我们可以通过调用标准库的time.LoadLocation()方法,传入时区标识字符串来获得时区对象。时区标识字符串是类似Asia/ShanghaiAmerica/Chicago这样的格式,它表示一个具体的时区,前者上海,后者洛杉矶。调用dateparse.ParseIn()方法传入时区对象,在指定时区中解析。time包中还预定义了两个时区对象,time.Local表示本地时区,time.UTC表示 UTC 时区。时区的权威数据请看IANA

  1. func main() {
  2. tz1, _ := time.LoadLocation("America/Chicago")
  3. t1, _ := dateparse.ParseIn("2021-06-24 15:50:30", tz1)
  4. fmt.Println(t1.Local().Format("2006-01-02 15:04:05"))
  5. t2, _ := dateparse.ParseIn("2021-06-24 15:50:30", time.Local)
  6. fmt.Println(t2.Local().Format("2006-01-02 15:04:05"))
  7. }

运行:

  1. $ go run main.go
  2. 2021-06-25 04:50:30
  3. 2021-06-24 15:50:30

美国洛杉矶时区的”2021年6月24日 15时30分30秒”等于本地时区(北京时间)的”2021年6月25日 04时50分30秒”。

cli

dateparse还提供了一个命令行工具,用于极快地查看日期时间格式。安装:

  1. $ go install github.com/araddon/dateparse/dateparse

默认会安装在$GOPATH路径下,我习惯上把$GOPATH/bin放到$PATH中。所以dateparse命令可以直接使用。

dateparse命令接收一个字符串,和一个可选的时区选项:

  1. $ dateparse --timezone="Asia/Shanghai" "2021-06-24 06:46:08"
  2. Your Current time.Local zone is CST
  3. Layout String: dateparse.ParseFormat() => 2006-01-02 15:04:05
  4. Your Using time.Local set to location=Asia/Shanghai CST
  5. +-------------+---------------------------+-------------------------------+-------------------------------------+
  6. | method | Zone Source | Parsed | Parsed: t.In(time.UTC) |
  7. +-------------+---------------------------+-------------------------------+-------------------------------------+
  8. | ParseAny | time.Local = nil | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
  9. | ParseAny | time.Local = timezone arg | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
  10. | ParseAny | time.Local = time.UTC | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
  11. | ParseIn | time.Local = nil | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC |
  12. | ParseIn | time.Local = timezone arg | 2021-06-24 06:46:08 +0800 CST | 2021-06-23 22:46:08 +0000 UTC |
  13. | ParseIn | time.Local = time.UTC | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC |
  14. | ParseLocal | time.Local = nil | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC |
  15. | ParseLocal | time.Local = timezone arg | 2021-06-24 06:46:08 +0800 CST | 2021-06-23 22:46:08 +0000 UTC |
  16. | ParseLocal | time.Local = time.UTC | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC |
  17. | ParseStrict | time.Local = nil | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC |
  18. | ParseStrict | time.Local = timezone arg | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC |
  19. | ParseStrict | time.Local = time.UTC | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC |
  20. +-------------+---------------------------+-------------------------------+-------------------------------------+

输出当前本地时区,格式字符串(可用于生成同样格式的日期时间字符串)和一个表格。表格里面的数据是分别对ParseAny/ParseIn/ParseLocal/ParseStrict在不同的时区下调用的结果。

method列表示调用的方法,Zone Source列表示将本地时区设置的值,Parsed列是以日期时间字符串调用ParseAny()返回的time.Time对象的Format()方法调用结果,Parsed: t.In(time.UTC)列在返回的time.Time对象调用Format()方法前将其转为 UTC 时间。

由于ParseAny/ParseStrict不会考虑本地时区,都是在 UTC 下解析字符串,所以这 6 行的最后两列结果都一样。

ParseIn的第二行,将time.Local设置为我们通过命令行选项设置的时区,上面我设置为Asia/Shanghai,对应的 UTC 时间相差 8 小时。ParseLocal也是如此。

下面是dateparse命令行的部分源码,可以对照查看:

  1. func main() {
  2. parsers := map[string]parser{
  3. "ParseAny": parseAny,
  4. "ParseIn": parseIn,
  5. "ParseLocal": parseLocal,
  6. "ParseStrict": parseStrict,
  7. }
  8. for name, parser := range parsers {
  9. time.Local = nil
  10. table.AddRow(name, "time.Local = nil", parser(datestr, nil, false), parser(datestr, nil, true))
  11. if timezone != "" {
  12. time.Local = loc
  13. table.AddRow(name, "time.Local = timezone arg", parser(datestr, loc, false), parser(datestr, loc, true))
  14. }
  15. time.Local = time.UTC
  16. table.AddRow(name, "time.Local = time.UTC", parser(datestr, time.UTC, false), parser(datestr, time.UTC, true))
  17. }
  18. }
  19. func parseIn(datestr string, loc *time.Location, utc bool) string {
  20. t, err := dateparse.ParseIn(datestr, loc)
  21. if err != nil {
  22. return err.Error()
  23. }
  24. if utc {
  25. return t.In(time.UTC).String()
  26. }
  27. return t.String()
  28. }

注意输出的本地时区为 CST,它可以代表不同的时区:

  1. Central Standard Time (USA) UT-6:00
  2. Central Standard Time (Australia) UT+9:30
  3. China Standard Time UT+8:00
  4. Cuba Standard Time UT-4:00

CST 可以同时表示美国、澳大利亚、中国和古巴四个国家的标准时间。

总结

使用dateparse可以很方便地从日期时间字符串中解析出时间对象和格式(layout)。同时dateparse命令行可以快速的查看和转换相应时区的时间,是一个非常不错的小工具。

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue😄

参考

  1. dateparse GitHub:github.com/araddon/dateparse
  2. Go 每日一库 GitHub:https://github.com/go-quiz/go-daily-lib