《Go语言编程之旅》学习03--时间工具

日之朝矣

我们继续使用上次学习单词格式转换的目录

在cmd目录下新增文件time.go

在internal目录下新建timer文件夹,在timer文件夹下新增文件time.go

现在我们的目录是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Tools
│ go.mod
│ go.sum
│ main.go # 程序入口

├─cmd # flag定义与初始化
│ root.go
│ time.go
│ word.go

└─internal # 逻辑处理
├─timer
│ time.go

└─word
word.go

定义与初始化命令行参数

先在/cmd/time.go目录下定义一下time命令timeCmd以及两个子命令,nowTimeCmdcalculateTimeCmd,并将子命令添加到timeCmd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var timeCmd = &cobra.Command{
Use: "time",
Short: "时间格式处理",
Long: "时间格式处理",
Run: func(cmd *cobra.Command, args []string) {},
}

var nowTimeCmd = &cobra.Command{
Use: "now",
Short: "获取当前时间",
Long: "获取当前时间",
Run: func(cmd *cobra.Command, args []string) {},
}

var calculateTimeCmd = &cobra.Command{
Use: "calc",
Short: "计算所需时间",
Long: "计算所需时间",
Run: func(cmd *cobra.Command, args []string) {},
}


func init(){
timeCmd.AddCommand(nowTimeCmd)
timeCmd.AddCommand(calculateTimeCmd)
}

然后到cmd/root.go中的init参数中添加一下time命令标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cmd

import (
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{}

func Execute() error {
return rootCmd.Execute()
}

func init() {
rootCmd.AddCommand(wordCmd)
rootCmd.AddCommand(timeCmd) // 这一行是新增的
}

获取当前时间

到internal/timer/time.go文件中增加如下一个方法

1
2
3
func GetNowTime() time.Time{
return time.Now()
}

回到cmd/time.go中,修改nowTimeCmd的Run方法,只用输出一下即可

1
2
3
4
5
6
7
8
9
var nowTimeCmd = &cobra.Command{
Use: "now",
Short: "获取当前时间",
Long: "获取当前时间",
Run: func(cmd *cobra.Command, args []string) {
nowTime := timer.GetNowTime()
log.Printf("输出结果 %s, %d",nowTime.Format("2006-01-02 15:04:05"))
},
}

时间推算

这部分主要是做到时间的增加与减少

到internal/timer/time.go中新增如下方法

1
2
3
4
5
6
7
8
func GetCalculateTime(currentTimer time.Time, d string) (time.Time, error) {
duration, err := time.ParseDuration(d)
if err != nil {
return time.Time{}, err
}

return currentTimer.Add(duration), nil
}

在这个方法中,我们默认如果所给的duration格式无法解析的话,返回当前时间与错误信息

到cmd/time.go中修改calculateTimeCmd的Run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var calculateTimeCmd = &cobra.Command{
Use: "calc",
Short: "计算所需时间",
Long: "计算所需时间",
Run: func(cmd *cobra.Command, args []string) {
var currentTimer time.Time
var layout = "2006-01-02 15:04:05"
if calculateTime == "" {
currentTimer = timer.GetNowTime()
} else {
var err error
colon := strings.Count(calculateTime, ":")
if colon == 0 {
layout = "2006-01-02"
}
if colon == 2 {
layout = "2006-01-02 15:04:05"
}
currentTimer, err = time.Parse(layout, calculateTime)
if err != nil {
t, _ := strconv.Atoi(calculateTime)
currentTimer = time.Unix(int64(t), 0)
}
}
t, err := timer.GetCalculateTime(currentTimer, duration)
if err != nil {
log.Fatalf("timer.GetCalculateTime err: %v", err)
}

log.Printf("输出结果: %s, %d", t.Format(layout), t.Unix())
},
}

我们通过判断:数量来判断时间格式,这里仅仅判断了两种时间格式

time库支持如下时间格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const (
Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order.
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"
DateTime = "2006-01-02 15:04:05"
DateOnly = "2006-01-02"
TimeOnly = "15:04:05"
)

可以像这样子使用这些预定义格式,例如:

1
t := time.Now().Format(time.RFC3339)

验证

1
2
3
4
5
6
7
8
go run main.go time now
输出结果: 2023-02-26 13:32:55, 1677389575

go run main.go time calc -c="2029-09-04 12:02:33" -d=5m
输出结果: 2029-09-04 12:07:33, 1883218053

go run main.go time calc -c="2029-09-04 12:02:33" -d=-2h
输出结果: 2029-09-04 10:02:33, 1883210553

时区问题

如果把代码放到其他地方跑,可能会遇到少了八个小时的问题,其实就是时区出了问题。因此我们在使用时间相关函数时一定要关注时区问题

在标准库 time 上,提供了 Location 的两个实例:Local 和 UTC。Local 代表当前系统本地时区;UTC 代表通用协调时间,也就是零时区,在默认值上,标准库 time 使用的是 UTC 时区。

设置时区

这一部分基本上是复制粘贴原文的内容

标准库 time 中的 LoadLocation 方法来根据名称获取特定时区的 Location 实例,原型如下:

1
func LoadLocation(name string) (*Location, error)

在该方法中,如果所传入的 name 是”UTC”或为空,返回 UTC;如果 name 是 “Local”,返回当前的本地时区 Local;否则 name 应该是 IANA 时区数据库(IANA Time Zone Database,简称 tzdata)里有记录的地点名(该数据库记录了地点和对应的时区),如 “America/New_York”。

那么为了保证我们所获取的时间,与我们所期望的时区一致,我们要对获取时间的代码进行修改,设置当前时区为 Asia/Shanghai

internal/timer/time.go

1
2
3
4
func GetNowTime() time.Time {
location, _ := time.LoadLocation("Asia/Shanghai")
return time.Now().In(location)
}

在前面的实践代码中,我们用到了 time.Format 方法,与此还有一个相对应的方法并没有介绍到它,就是 time.Parse 方法,Parse 方法会解析格式化的字符串并返回它表示的时间值,它非常的常见,并且有一个非常需要注意的点。首先我们一起看看下面这个示例程序,如下:

1
2
3
4
5
6
7
8
9
func main() {
location, _ := time.LoadLocation("Asia/Shanghai")
inputTime := "2029-09-04 12:02:33"
layout := "2006-01-02 15:04:05"
t, _ := time.Parse(layout, inputTime)
dateTime := time.Unix(t.Unix(), 0).In(location).Format(layout)

log.Printf("输入时间:%s,输出时间:%s", inputTime, dateTime)
}

那么你觉得这个示例程序的输出时间的结果是什么呢,还是 2029-09-04 12:02:33 吗,我们一起来看看最终的输出结果,如下:

1
输入时间:2029-09-04 12:02:33,输出时间:2029-09-04 20:02:33

从输出结果上来看,输入和输出时间竟然相差了八个小时,这显然是时区的设置问题,但是这里你可能又打起了嘀咕,明明在调用 Format 方法前我们已经设置了时区…这究竟是为什么呢?

实际上这与 Parse 方法有直接关系,因为 Parse 方法会尝试在入参的参数中中分析并读取时区信息,但是如果入参的参数没有指定时区信息的话,那么就会默认使用 UTC 时间。因此在这种情况下我们要采用 ParseInLocation 方法,指定时区就可以解决这个问题,如下:

1
2
t, _ := time.ParseInLocation(layout, inputTime, location)
dateTime := time.Unix(t.Unix(), 0).In(location).Format(layout)

也就是所有解析与格式化的操作都最好指定时区信息,否则当你遇到时区问题的时候,并且已经上线,那么后期再进行数据清洗就比较麻烦了。

为什么是 2006-01-02 15:04:05

实际上,2006-01-02 15:04:05 是一个参考时间的格式,也就是其它语言中 Y-m-d H:i:s 格式,在功能上用于时间的格式化处理,这个我们在前面章节中已经进行过验证。

那么为什么要用 2006-01-02 15:04:05 呢,其实这些”数字“是有意义的,在 Go 语言中强调必须显示参考时间的格式,因此每个布局字符串都是一个时间戳的表示,并非随便写的时间点,如果你觉得记忆困难,可参见官方例子中的如下方式:

1
2
Jan 2 15:04:05 2006 MST
1 2 3 4 5 6 -7

而转换到 2006-01-02 15:04:05 的时间格式,我们也可以将其记忆为 2006 年 1 月 2 日 3 点 4 分 5 秒。

  • 标题: 《Go语言编程之旅》学习03--时间工具
  • 作者: 日之朝矣
  • 创建于 : 2023-02-26 13:48:04
  • 更新于 : 2023-10-10 08:35:51
  • 链接: https://rzzy.fun/2023/02/26/go-time-tools/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论