Go系列:时间操作

3,590 阅读6分钟

常见概念

  • GMT和UTC时间:GMT格林尼治时间,作为世界标准时间,UTC与GMT误差非常小,可以认为是一致的, 圈子里都以UTC时间作为标准时间;
  • 时区:英国为0时区,中国在东八区 (UTC+08),UTC时间加上8小时就是我们本地时间了 有时会看到CST +0800 就是代表的中国标准时间;
  • 时间戳:是从1970年1月1日(UTC/GMT的午夜 1970-01-01T00:00:00)开始所经过的秒数。

时间获取

Go中的time.Time代表一个纳秒精度的时间点,每一个时间点都具有一个地点信息,当计算时间的表示格式时,如FormatHourYear等方法,都会考虑该信息。

获取当前时间

time.Time 类型定义

// 注意:类型里包含三个未导出的字段
type Time struct {
    wall uint64    // wall 和 ext 组成比较复杂
    ext  int64
    loc *Location  // 表示时区
}

获取时间、时间戳、周、天等

now := time.Now()              // 注:time.Time 类型的时间
fmt.Printf("%v\n", now)        // 输出:2021-09-16 14:54:56.253573 +0800 CST m=+0.432441917
timestamp := now.Unix()        // 注:unix时间戳 秒  now.UnixNano() 纳秒时间戳
fmt.Printf("%v\n", timestamp)  // 输出:1631775296

year := now.Year()
fmt.Printf("%v\n", year)       // 输出:2021
month := now.Month()           // 注:是`Month`类型数据
fmt.Printf("%v\n", month)      // 输出:September  String返回月份的英文名
// 还有 Day()、Hour()、Minute()、Second()等函数获取其他值

y, m, d := now.Date()
fmt.Printf("year:%d, month:%d, day:%d\n", y, m, d) // 输出:year:2021, month:9, day:16

// 返回星期
fmt.Println(now.Weekday())     // 输出:Thursday 
//返回一年中对应的第几天
fmt.Println(now.YearDay())     // 输出:259
//返回时区
fmt.Println(now.Location())    // 输出:Local

格式化时间

需要注意的是格式化字符串不是常见的YmdHis,而是2006-01-02 15:04:05Go的成立日期。

now := time.Now()  
fmt.Println(now.Format("2006-01-02 15:03:04"))  // 输出:2021-09-16 15:03:28
fmt.Println(now.Format("2006.01.02"))           // 输出:2021.09.16
fmt.Println(now.Format("15:03:04"))             // 输出:15:03:31

时间转化

时间戳转time.Time

timestamp := time.Now().Unix()                 // 当前时间戳
t := time.Unix(timestamp,0)                    // 转化为 time.Time 格式
fmt.Println(t.Format("2006-01-02 15:04:05"))   // 输出:2021-09-16 15:44:24

字符串转time.Time

time.Parse()方法使用的是UTC时间,和CST时间(中国标准时间)相差8小时,所以字符串转化为time.time时间时需要指定时区,可以使用time.ParseInLocation()方法。

t, _ := time.Parse("2006-01-02 15:04:05", "2021-01-10 15:01:02")
fmt.Println(t)  // 输出:2021-09-16 15:49:02 +0000 UTC
str := time.Now().Format("2006-01-02 15:04:05")  // 获取string格式当前时间 
t, _ := time.ParseInLocation("2006-01-02 15:04:05", str, time.Local)  // 指定本地时区
fmt.Println(t)  // 输出:2021-09-16 15:54:59 +0800 CST

时间戳、time.Time、和字符串时间转化关系如下:

          time.Unix()                 Time.Format()
 int	------------>                 ------------>      string
时间戳     Time.Unix()    time.Time    time.Parse()      日期字符串
        <------------                 <------------              
                                      time.ParseInLocation()  

自定义结构体time.Time

在使用GORM的时候,发现查询数据的展示形式对开发不友好,那么我们可以自定义一个time.Tiem类型数据,并重写MarshalJSON方法。

type DateTime time.Time

// MarshalJSON 自定义解析json数据类型,将原始CST时间解析为标准时间格式
func (t DateTime) MarshalJSON() ([]byte, error) {
    var stamp = fmt.Sprintf("\"%s\"", time.Time(t).Format("2006-01-02 15:04:05"))
    return []byte(stamp), nil
}

// 定义基础Model 用我们自定义的 DateTime 类型定义时间字段
type Model struct {
    ID        uint     `gorm:"column:id;autoIncrement;comment:'主键'" json:"id"`
    CreatedAt DateTime `gorm:"column:created_at;default:null;comment:'创建时间'" json:"created_at"`
    UpdatedAt DateTime `gorm:"column:updated_at;default:null;comment:'更新时间'" json:"updated_at"`
}
// gorm 查询数据
func (u *User) Find() {
    users := []User{}
    db.Limit(1).Find(&users)
    // 格式化输出json
    d, _ := json.MarshalIndent(users, "", "    ")
    fmt.Printf("查询结果:%v\n", string(d))
}

查询结果:

1631784287509.jpg

时间计算

时间区间引入一个新的类型Duration,Duration类型代表两个时间点之间经过的时间,以纳秒为单位。可表示的最长时间段大约290年。

type Duration int64

24小时内的时间增减

ParseDuration()方法解析一个时间段字符串。一个时间段字符串是一个序列,每个片段包含可选的正负号、十进制数、可选的小数部分和单位后缀如"300ms"、"-1.5h"、"2h45m"。合法的单位有"ns"、"us" 、"ms"、"s"、"m"、"h"。

now := time.Now()
fmt.Println(now)   // 输出:2021-09-16 16:25:17.883157 +0800 CST m=+0.381067667

t, _ := time.ParseDuration("1h2m3s") // 1小时2分3s之后  注:有负号表示之前
fmt.Println(t)     // 输出:1h2m3s
m := now.Add(t)    // 当前时间点加t时间段
fmt.Println(m)     // 输出:2021-09-16 17:26:48.754091 +0800 CST m=+3723.580913168

24小时外的时间增减

超过一天的时间增加减少的操作使用time.AddDate()方法,AddDate返回增加了给出的年份、月份和天数的时间点Time。例如,时间点January 1, 2011调用AddDate(-1, 2, 3)会返回March 4, 2010。

AddDate会将结果规范化,类似Date函数的做法。因此,举个例子,给时间点October 31添加一个月,会生成时间点December 1(从时间点November 31规范化而来)。

time.AddDate()方法的定义:

func (t Time) AddDate(years int, months int, days int) Time

其中参数yearsmonthsdays都是有符号的整型、负数代表减去对应数值后的时间点(之前的时间点),整数代表加上对应数值后的时间点(之后的时间点)

t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2021-08-31 23:59:59", time.Local)            // 将2021-08-31增加一个月和一天
fmt.Println(t)         // 输出:2021-08-31 23:59:59 +0800 CST
m := t.AddDate(0, 1, 1)
fmt.Println(m)         // 2021-10-02 23:59:59 +0800 CST

时期比较

有如下函数可以比较时间点的之前、之后、相等

func (t Time) Before(u Time) bool  // 如果 t 代表的时间点在 u 之前,返回真;否则返回假。
func (t Time) After(u Time) bool   // 如果 t 代表的时间点在 u 之后,返回真;否则返回假。
func (t Time) Equal(u Time) bool   // 比较时间是否相等,相等返回真;否则返回假。
now := time.Now()
t, _ := time.ParseDuration("10s") // 1小时2分3s之后  注:有负号表示之前
m := now.Add(t)        // 当前时间点加t时间段
if now.Before(m) {
    fmt.Println(true)  // 输出:true
}

时间区间

还有两个方法time.Since()time.Until()计算给定时间和当前时间的时间差或当前时间和给定时间的时间差。

time.Since(t Time) Duration  // 返回当前时间与 t 的时间差,返回值是 Duration
time.Until(t Time) Duration  // 返回 t 与当前时间的时间差,返回值是 Duration
t, _ := time.ParseDuration("-1h")
m := now.Add(t)
fmt.Println(m)
// time.Since(m) 返回 Duration 类型时间段
// Hours 将时间段表示为float64类型的小时数
fmt.Println(time.Since(m).Hours())  // 输出:1.0000000214697222
fmt.Println(time.Until(m).Hours())  // 输出:-1.0000000325694445

计算函数执行时间可以这么实现:

// 计算函数执行时间
func main() {
    start := time.Now()
    defer func() {
        fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
    }()
    // 后续逻辑
    ....
}