Go每日一库之68:dateparse

简介

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

快速使用

本文代码使用 Go Modules。

创建目录并初始化:

bash 复制代码
$ mkdir dateparse && cd dateparse
$ go mod init github.com/go-quiz/go-daily-lib/dateparse

安装dateparse库:

bash 复制代码
$ go get -u github.com/araddon/dateparse

使用:

go 复制代码
package main

import (
  "fmt"
  "log"
  "github.com/araddon/dateparse"
)

func main() {
  t1, err := dateparse.ParseAny("3/1/2014")
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(t1.Format("2006-01-02 15:04:05"))

  t2, err := dateparse.ParseAny("mm/dd/yyyy")
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(t2.Format("2006-01-02 15:04:05"))
}

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

bash 复制代码
$ go run main.go
2014-03-01 00:00:00
2021/06/24 14:52:39 Could not find format for "mm/dd/yyyy"
exit status 1

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

go 复制代码
func main() {
  t, err := dateparse.ParseStrict("3/1/2014")
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(t.Format("2006-01-02 15:04:05"))
}

运行:

bash 复制代码
$ go run main.go
2021/06/24 14:57:18 This date has ambiguous mm/dd vs dd/mm type format
exit status 1

格式

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

go 复制代码
// src/time/format.go
const (
  ANSIC       = "Mon Jan _2 15:04:05 2006"
  UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
  RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
  RFC822      = "02 Jan 06 15:04 MST"
  RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
  RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
  RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
  RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
  RFC3339     = "2006-01-02T15:04:05Z07:00"
  RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
  Kitchen     = "3:04PM"
  // Handy time stamps.
  Stamp      = "Jan _2 15:04:05"
  StampMilli = "Jan _2 15:04:05.000"
  StampMicro = "Jan _2 15:04:05.000000"
  StampNano  = "Jan _2 15:04:05.000000000"
)

支持的完整格式查看dateparse README

时区

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

go 复制代码
func main() {
  tz1, _ := time.LoadLocation("America/Chicago")
  t1, _ := dateparse.ParseIn("2021-06-24 15:50:30", tz1)
  fmt.Println(t1.Local().Format("2006-01-02 15:04:05"))

  t2, _ := dateparse.ParseIn("2021-06-24 15:50:30", time.Local)
  fmt.Println(t2.Local().Format("2006-01-02 15:04:05"))
}

运行:

bash 复制代码
$ go run main.go
2021-06-25 04:50:30
2021-06-24 15:50:30

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

cli

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

bash 复制代码
$ go install github.com/araddon/dateparse/dateparse

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

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

bash 复制代码
$ dateparse --timezone="Asia/Shanghai" "2021-06-24 06:46:08"

Your Current time.Local zone is CST

Layout String: dateparse.ParseFormat() => 2006-01-02 15:04:05

Your Using time.Local set to location=Asia/Shanghai CST

+-------------+---------------------------+-------------------------------+-------------------------------------+
| method      | Zone Source               | Parsed                        | Parsed: t.In(time.UTC)              |
+-------------+---------------------------+-------------------------------+-------------------------------------+
| ParseAny    | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
| ParseAny    | time.Local = timezone arg | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
| ParseAny    | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
| ParseIn     | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseIn     | time.Local = timezone arg | 2021-06-24 06:46:08 +0800 CST | 2021-06-23 22:46:08 +0000 UTC       |
| ParseIn     | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseLocal  | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseLocal  | time.Local = timezone arg | 2021-06-24 06:46:08 +0800 CST | 2021-06-23 22:46:08 +0000 UTC       |
| ParseLocal  | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseStrict | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseStrict | time.Local = timezone arg | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseStrict | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
+-------------+---------------------------+-------------------------------+-------------------------------------+

输出当前本地时区,格式字符串(可用于生成同样格式的日期时间字符串)和一个表格。表格里面的数据是分别对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命令行的部分源码,可以对照查看:

go 复制代码
func main() {
  parsers := map[string]parser{
    "ParseAny":    parseAny,
    "ParseIn":     parseIn,
    "ParseLocal":  parseLocal,
    "ParseStrict": parseStrict,
  }

  for name, parser := range parsers {
    time.Local = nil
    table.AddRow(name, "time.Local = nil", parser(datestr, nil, false), parser(datestr, nil, true))
    if timezone != "" {
      time.Local = loc
      table.AddRow(name, "time.Local = timezone arg", parser(datestr, loc, false), parser(datestr, loc, true))
    }
    time.Local = time.UTC
    table.AddRow(name, "time.Local = time.UTC", parser(datestr, time.UTC, false), parser(datestr, time.UTC, true))
  }
}

func parseIn(datestr string, loc *time.Location, utc bool) string {
  t, err := dateparse.ParseIn(datestr, loc)
  if err != nil {
    return err.Error()
  }
  if utc {
    return t.In(time.UTC).String()
  }
  return t.String()
}

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

go 复制代码
Central Standard Time (USA) UT-6:00
Central Standard Time (Australia) UT+9:30
China Standard Time UT+8:00
Cuba Standard Time UT-4:00

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

总结

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

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

参考

  1. dateparse GitHub:github.com/araddon/dat...
  2. Go 每日一库 GitHub:github.com/go-quiz/go-...
相关推荐
梁梁梁梁较瘦13 小时前
边界检查消除(BCE,Bound Check Elimination)
go
梁梁梁梁较瘦13 小时前
指针
go
梁梁梁梁较瘦13 小时前
内存申请
go
半枫荷14 小时前
七、Go语法基础(数组和切片)
go
梁梁梁梁较瘦1 天前
Go工具链
go
半枫荷1 天前
六、Go语法基础(条件控制和循环控制)
go
半枫荷3 天前
五、Go语法基础(输入和输出)
go
小王在努力看博客3 天前
CMS配合闲时同步队列,这……
go
Anthony_49263 天前
逻辑清晰地梳理Golang Context
后端·go
Dobby_054 天前
【Go】C++ 转 Go 第(二)天:变量、常量、函数与init函数
vscode·golang·go