吴云华 / golang中常用的时间和日期操作

作者:wuyunhua | 2023-10-15

golang标准库 time 包提供了时间和日期的基本操作,我见过很多其他语言因为自带的时间不好用而诞生了各种第三方库,但golang的 time 足够好用,接口设计也很优雅,本文总结了golang中常用的时间和日期操作。

时间、时间戳和时间字符串

我们总是与这三种类型的时间打交道,所以我们来看看如何获取这三种类型的时间,以及如何相互转换。

// 获取当前时间
now := time.Now()       // time.Time类型
// 获取当前时间戳
nowUnix := now.Unix()   // 1697352906,int64类型
// 获取当前时间字符串
nowString := now.Format("2006-01-02 15:04:05") // 2023-10-15 14:55:06 string类型

// 时间戳转time.Time
t := time.Unix(nowUnix, 0) // time.Time类型
// 时间戳转时间字符串
tString := time.Unix(nowUnix, 0).Format("2006-01-02 15:04:05") // 2023-10-15 14:55:06 string类型

// time.Time转时间戳
tUnix := t.Unix() // 1697352906,int64类型
// time.Time转时间字符串
tString := t.Format("2006-01-02 15:04:05") // 2023-10-15 14:55:06 string类型

// 时间字符串转time.Time
t, err := time.Parse("2006-01-02 15:04:05", tString) // time.Time类型
// 时间字符串转时间戳
tUnix, err := time.Parse("2006-01-02 15:04:05", tString).Unix() // 1697352906,int64类型

重要概念time.Duration:持续时间

如果说time.Time是一个瞬时概念,那么time.Duration就是一个持续概念,代表一段时间。time.Duration是一个int64类型,表示持续的纳秒数,time.Duration的零值是0,time.Duration的最大值是1<<63-1纳秒,大约292年。

// 标准库里Duration是这样定义的:
type Duration int64
// 所以Duration 的底层类型是int64,又有这样的常量定义:
const (
    Nanosecond  Duration = 1
    Microsecond          = 1000 * Nanosecond
    Millisecond          = 1000 * Microsecond
    Second               = 1000 * Millisecond
    Minute               = 60 * Second
    Hour                 = 60 * Minute
)
// 所以 1 小时 20 分 30秒 可以这样表示:
d := 1 * time.Hour + 20 * time.Minute + 30 * time.Second + 0 * time.Microsecond + 0 * time.Nanosecond
// 如果把一个数字转换为Duration,那么默认单位是纳秒
d := time.Duration(1) // 1纳秒
d := time.Duration(1e9) // 1秒
// 有的时间处理函数要求类型必须是Duration,就需要进行依次强转
time.Sleep(time.Duration(1e9))
// 但更好的做法是直接使用time.Second、time.Minute等常量
time.Sleep(1 * time.Second)
// Duration提供了一些常用的方法
d.Hours()           // 1.3416666666666667 float64类型
d.Minutes()         // 80.5 float64类型
d.Seconds()         // 4830.0 float64类型
d.Milliseconds()    // 4830000 int64类型
d.Microseconds()    // 4830000000 int64类型
d.Nanoseconds()     // 4830000000000 int64类型

time.Time常用操作

建议总是使用time.Time来进行时间操作,而不是使用时间戳或时间字符串,因为time.Time是标准库提供的类型,可以保证兼容性和稳定性。

  • time.Time零值
// t是一个结构体,结构体的零值是各 field 零值的组合,而不是nil,注意不能使用if t == nil来判断t是否为零值
var t time.Time
// 但是标准库提供了一个IsZero()方法来判断t是否为零值,所以可以使用if t.IsZero()来判断t是否为零值
t.IsZero()      // true
  • time.Time的构造
// 构造一个时间,传入的参数依次是年、月、日、时、分、秒、纳秒、时区
var t = time.Date(2023, 10, 15, 15, 30, 0, 0, time.Local)
  • time.Time的比较
var t = time.Now()
var t2 = time.Date(2023, 10, 15, 15, 30, 0, 0, time.Local)
// 比较两个时间是否相等
t.Equal(t2) // false
// 比较两个时间的先后
t.Before(t2) // true
t.After(t2) // false
  • time.Time的漫游
var t = time.Now()
// 加一段时间
t2 := time.Now().Add(1 * time.Hour)
// 减一段时间
t3 := time.Now().Add(-1 * time.Hour)
// t2和t之间经历了多久
d := t2.Sub(t) // time.Duration类型
// 从时间t3开始,到现在持续了多久
d := time.Since(t3) // time.Duration类型
  • time.Time的格式化

golang日期格式是被吐槽最多的,但我个人认为,这种基于layout布局的时间格式化方法非常优雅,只要记住一些常用的layout布局,就可以轻松应对大部分场景。

var t = time.Now()
// 格式化为RFC3339格式
t.Format(time.RFC3339) // 2023-10-15T15:30:00+08:00
// go 1.20之后,新增了一些内置 layout
DateTime   = "2006-01-02 15:04:05"
DateOnly   = "2006-01-02"
TimeOnly   = "15:04:05"
// 可以这样打印时间
t.Format(DateTime) // 2023-10-15 15:30:00

// 可以任意组合
t.Format("2006-01-02 15:04:05") // 2023-10-15 15:30:00
  • time.Time的解析
// 解析时间字符串
t, err := time.Parse("2006-01-02 15:04:05", "2023-10-15 15:30:00")
// 解析时间字符串,指定时区
t, err := time.ParseInLocation("2006-01-02 15:04:05", "2023-10-15 15:30:00", time.Local)

时区

// 明确指定时区
location, err := time.LoadLocation("America/New_York")
timeInNY := time.Now().In(location)

// 转换时区
locationNY, _ := time.LoadLocation("America/New_York")
locationTokyo, _ := time.LoadLocation("Asia/Tokyo")
timeInNY := time.Now().In(locationNY)
timeInTokyo := timeInNY.In(locationTokyo)

// 解析带时区的时间字符串
timeStr := "2023-10-15 12:00:00 +0200" // 带时区信息
parsedTime, err := time.Parse("2006-01-02 15:04:05 -0700", timeStr)

UTC时间

UTC(协调世界时)是一种标准的时间表示方式,不考虑任何时区偏移。

// 获取当前UTC时间
utcTime := time.Now().UTC()
utcTimeString := utcTime.Format("2006-01-02 15:04:05")

// 解析UTC时间字符串
utcTimeStr := "2023-10-15 12:00:00"
utcTime, err := time.Parse("2006-01-02 15:04:05", utcTimeStr)

一些与时间有关的技巧

  • 有时候需要记录一个操作的耗时,可以这样做:
start := time.Now()

defer func(){
    elapsed := time.Since(start)
    fmt.Println(elapsed.Milliseconds()) // 毫秒
}()
// do something
  • 获取当前是一年中的第几周等
currentTime := time.Now()
// 获取当前时间所在的年份和周数
year, week := currentTime.ISOWeek()
// 获取当前时间所在的年份和天数
dayOfYear := currentTime.YearDay()
// 获取当前日期是一周中的第几天
weekday := currentTime.Weekday()
  • 获取一天的开始和结束
// 获取当前时间的零点
startOfDay := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 0, 0, 0, 0, currentTime.Location())
// 获取第二天开始时间
endOfDay := startOfDay.AddDate(0, 0, 1)
  • 获取一个月的开始和结束
// 获取当月的第一天
year, month, _ := currentTime.Date()
firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, currentTime.Location())

// 获取下个月的第一天
nextMonth := firstOfMonth.AddDate(0, 1, 0)

总结

个人认为,golang的time包对时间和日期的抽象非常优雅,建议大家尽量多使用time.Time来操作时间,接受golang的这套时间操作方式,而不是想着用第三方包来变成 Java/php 的样子。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.wuyunhua.cn/golang-time-and-date