目录
- 简介
- 类型
- 时区
- 小心有坑
- 时间解析的使用场景
- 时间操作
- 获取当前时间
- 时区设置
- 时间格式化(时间类型转字符串)
- 时间类型转时间戳
- 时间戳转时间类型
- 时间字符串转时间类型
- 时间计算
- 获取时间类型具体内容
- 时间加减
- 时间间隔(耗时)
- 时间取整(向上取整向下取整)
- 拓展
- json时间转换
简介
在各个语言之中都有时间类型的处理,因为这个地球是圆的(我仿佛在讲废话),有多个时区,每个时区的时间不一样,在程序中有必要存在一种方式,或者说一种类型存储时间,还可以通过一系列的方法转换成不同国家的时间。
上问提到了时间、时区,还有一个概念为两个时间之间的差值,比如小熊每次可以坚持1个小时(锻炼),1个小时这种时间形容词就是时间间隔。
这就是三种时间处理的类型。
类型
Time、Location、Duration 时间、时区、时间间隔。它们都在time包里面。
Time时间类型
程序中应使用 Time 类型值来保存和传递时间,一个结构体,精确到纳秒。里面的变量都是私有的用不到,先不去管他。
type Time struct {
sec int //秒
nsec int //纳秒
loc *Location //时区
}
- 一个Time类型值可以被多个go程同时使用。因为它是 time.Time 类型,而不是 指针*time.Time 类型。
- 时间需要初始化:IsZero 方法提供了检验时间是否是显式初始化。
- 时区类型作为Time结构体中的一个字段,标记这个时间当前是哪个时区。
Duration 时间间隔,两个时间之间的差值,以纳秒为单位,最长 290 年,作为常识即可。
type Duration int
时区
我们在使用time.Time类型一般都是Local时间,也就是本地时间,现在就是中国时间。
// 本地时间(如果是在中国,获取的是东八区时间)
curLocalTime := time.Now()
// UTC时间
curUTCTime := time.Now().UTC()
time 包提供了 Location (也就是时区)的两个实例:Local 和 UTC。
- Local 代表当前系统本地时区;UTC 代表通用协调时间,也就是零时区。
- time 包默认(为显示提供时区)使用 Local 时区。
- 平时使用的都是 Local 时间,数据库存储的时候要注意,一般 orm 框架会自动实现这个。
默认就是Local中国时间!
问题:时区这个怎么设置?传字符串进去吗?
curLocalTime := time.Now() //这是local
curUtcTime := curLocalTime.In(time.UTC) //这是UTC
时区特别容易出错,Time 我们使用都是本地时间,但是!坑来了!
小心有坑
timeStr := "-01-13 22:32:17"
utcTimeObj, err := time.Parse("-01-02 15:04:05", timeStr)
if err == nil {
fmt.Println(utcTimeObj, utcTimeObj.Unix())
}
你猜猜会输出什么?返回的竟然是UTC时间2022-01-13 22:32:17 +0000 UTC。这个经常有人出错。解析字符串时,都以协调时UTC时间为准。
还有另一个办法,比较稳。我们应该总是使用 time.ParseInLocation 来解析时间,并给第三个参数传递 time.Local。
localTimeObj, err := time.ParseInLocation("-01-02 15:04:05", timeStr, time.Local)
if err == nil {
fmt.Println(localTimeObj)
}
它返回的是time 类型是吗?没错!这两个返回的都是time类型。
问:这个会用在哪个场景?
好问题,问到点子上了!
时间解析的使用场景
前后端传输json数据的时候,或者数据库存储读取的时候。前后端建议使用时间戳传输,不要使用时间字符串可以大大省心。数据库如果使用orm的框架,一般是会自动处理时间存储。
我们约定好用时间戳传递,总是有一些比较轴的同事一定要用字符串传输,你有没有这样的同事?如果非要使用字符串传输,在传递json的时候就需要反复的做解析相当的不友善。
但也不是不能做~~
大家了解过json解析和反解析有哪两个方法吗?有没有人重写过 UnmarshalJSON 和 MarshalJSON。我们来复习一下。
我写的书里面的提到在不同办法的接口,有可能json字段的类型会发生改变,一般做兼容性处理的时候会重写到。
看这个截图,字符串转换成结构体,反过来结构体转换成字符串,就是用MarshalJSON。
type Person struct {
Id int `json:"id"`
Name string `json:"name"`
Birthday Time `json:"_"`
}
比如一个结构体,里面有一个时间类型,你的前端同事又不传时间戳,你就得手动转换成时间类型,或者时间戳,这个你自己决定。这里是 Birthday 举例,我的注解里面用的json是一个下划线,在解析的时候就不会写入。
问:这个不写入, 是 json库实现的,还是自己实现的?
json库。json库读取注解,匹配json中的字段名称,写入到结构体中。我的注解里写成了下划线,这只是一个占位符,习惯上这么写。你也可以写成-中杠线。
我先写了一个People的反解析函数,json.UnmarshalJSON会尝试调用。看截图
- 先解析到匿名结构体变量中,birthday字段赋值给了s.Brithday,其他字段给了s.tmp
- s.Birthday是一个字符串类型,再把这个类型转换成时间类型。
- 把 localtime 放到 tmp 里面,tmp 就是之前的 people。
所以返回的就是tmp, 才是我们要的。
*p = People(s.tmp)
最后再创建一个People,把tmp传递过去。
【思考题】为什么这里还要创建一个,直接赋值s.tmp给*p可以不?(这里我给你们挖了一个坑)。
我定义的是新类型,并不是创建,实际上是一个强制类型转换。哈哈哈,我就是蔫坏。
关于时间处理的各种函数我也列在下面了,大家收藏看就行了。还是刚刚提到的各种完整代码。
时间操作
获取当前时间
import time
func getCurTime() {
// 本地时间(如果是在中国,获取的是东八区时间)
curLocalTime := time.Now()
// UTC时间
curUTCTime := time.Now().UTC()
fmt.Println(curLocalTime, curUTCTime)
}
时区设置
不同国家(有时甚至是同一个国家内的不同地区)使用不同的时区。对于要输入和输出时间的程序来说,必须对系统所处的时区加以考虑。Go 语言使用 Location 来表示地区相关的时区,一个 Location 可能表示多个时区。展开讲解 time 包提供了 Location 的两个实例:Local 和 UTC
- Local 代表当前系统本地时区;UTC 代表通用协调时间,也就是零时区。
- time 包默认(为显示提供时区)使用 Local 时区。
- 平时使用的都是Local 时间,数据库存储的时候要注意,一般orm 框架会自动实现这个。
func setTimeZone() {
curLocalTime := time.Now()
curUtcTime := curLocalTime.In(time.UTC)
fmt.Println(curUtcTime)
}
通常,我们使用 time.Local 即可,偶尔可能会需要使用 UTC。在解析时间时,心中一定记得有时区这么回事。当你发现时间出现莫名的情况时,很可能是因为时区的问题,特别是当时间相差 8 小时时。
时间格式化(时间类型转字符串)
func timeTimeStr() {
localTimeStr := time.Now().Format("-01-02 15:04:05")
// UTC时间
utcTimeStr := time.Now().UTC().Format("-01-02 15:04:05")
fmt.Println(localTimeStr, utcTimeStr)
}
时间类型转时间戳
func getCurTimeStamp() {
// 时间戳,精确到秒
timestamp := time.Now().Unix()
// 时间戳,精确到纳秒
timestampNano := time.Now().UnixNano()
fmt.Println(timestamp, timestampNano)
}
相关函数或方法:
- time.Unix(sec, nsec int64) 通过 Unix 时间戳生成 time.Time 实例;
- time.Time.Unix() 得到 Unix 时间戳;
- time.Time.UnixNano() 得到 Unix 时间戳的纳秒表示;
时间戳转时间类型
func timestampTime() {
timestamp := time.Now().Unix()
localTimeObj := time.Unix(timestamp,)
fmt.Println(localTimeObj)
}
时间字符串转时间类型
func timeStrTime() {
timeStr := "-01-13 22:32:17"
// 返回的是UTC时间-01-13 22:32:17 +0000 UTC
utcTimeObj, err := time.Parse("-01-02 15:04:05", timeStr)
if err == nil {
fmt.Println(utcTimeObj, utcTimeObj.Unix())
}
// 返回的是当地时间-01-13 22:32:17 +0800 CST
localTimeObj, err := time.ParseInLocation("-01-02 15:04:05", timeStr, time.Local)
if err == nil {
fmt.Println(localTimeObj)
}
}
time.Parse 解析出来的时区却是 time.UTC(可以通过 Time.Location() 函数知道是哪个时区)。在中国,它们相差 8 小时。 所以,一般的,我们应该总是使用 time.ParseInLocation 来解析时间,并给第三个参数传递 time.Local。
时间计算
获取时间类型具体内容
t := time.Now()
fmt.Println("time.Now():", t) //-10-24 22:10:53.328973 +0800 CST m=+0.006015101
year, month, day := t.Date()
fmt.Println("日期:", year, month, day) // October 24
fmt.Println("一年中的第几天:", t.YearDay()) //
fmt.Println("星期几:", t.Weekday()) // Saturday
fmt.Println("年:", t.Year()) //
fmt.Println("月:", t.Month()) // October
fmt.Println("日:", t.Day()) //
fmt.Println("时:", t.Hour()) //
fmt.Println("分:", t.Minute()) //
fmt.Println("秒:", t.Second()) //
fmt.Println("纳秒:", t.Nanosecond()) //
fmt.Println("秒时间戳:", t.Unix()) //
fmt.Println("纳秒时间戳:", t.UnixNano()) //
fmt.Println("毫秒时间戳:", t.UnixNano() /e6) // 1603548653328
时间加减
转换为Time类型比较容易做加减。
- 时间点可以使用 Before、After 和 Equal 方法进行比较。
- Sub 方法让两个时间点相减,生成一个 Duration 类型值(代表时间段)。
- Add 方法给一个时间点加上一个时间段,生成一个新的 Time 类型时间点。
func addTime() {
curTime := time.Now()
// 加秒
addSecondTime := curTime.Add(time.Second *)
// 加分钟
addMinuteTime := curTime.Add(time.Minute *)
addMinuteTime := curTime.Add(time.Second * time.Duration(60*1))
fmt.Println(addSecondTime, addMinuteTime, addMinuteTime)
}
时间类型中有提前定义固定的时间长度常量,比如一小时的长度就是time.Hour
t := time.Now()
addOneHour := t.Add(time.Hour)
addTwoHour := t.Add( * time.Hour)
fmt.Println("增加小时:", addOneHour)
fmt.Println("增加小时:", addTwoHour)
subTwoHour := t.Add(- * time.Hour)
fmt.Println("减去小时:", subTwoHour)
addDate := t.AddDate(, 0, 0)
fmt.Println("增加年:", addDate) // 2021-10-24 22:10:53.328973 +0800 CST
subDate := t.AddDate(-, 0, 0)
fmt.Println("减去年:", subDate) // 2019-10-24 22:10:53.328973 +0800 CST
before := t.Before(t.Add(time.Hour))
fmt.Println("before:", before)
after := t.After(t.Add(time.Hour))
fmt.Println("after:", after)
时间间隔(耗时)
t := time.Now()
time.Sleep(e9) // 休眠2秒
delta := time.Now().Sub(t)
fmt.Println("时间差:", delta) //.0534341s
时间取整(向上取整向下取整)
t, _ := time.ParseInLocation("-01-02 15:04:05", "2016-06-13 15:34:39", time.Local)
// 整点(向下取整)
fmt.Println(t.Truncate( * time.Hour))
// 整点(最接近)
fmt.Println(t.Round( * time.Hour))
// 整分(向下取整)
fmt.Println(t.Truncate( * time.Minute))
// 整分(最接近)
fmt.Println(t.Round( * time.Minute))
t, _ := time.ParseInLocation("2006-01-02 15:04:05", t.Format("2006-01-02 15:00:00"), time.Local)
fmt.Println(t)
拓展
json时间转换
前后端建议使用时间戳传输,不要使用时间字符串可以大大省心,如果非要使用字符串传输,在传递json的时候就需要反复的做解析相当的不友善,但也不是不能做。 方式一、省心方式,重定义时间类型
type Time time.Time
const (
timeFormart = "-01-02 15:04:05"
)
func (t *Time) UnmarshalJSON(data []byte) (err error) {
now, err := time.ParseInLocation(`"`+timeFormart+`"`, string(data), time.Local)
*t = Time(now)
return
}
func (t Time) MarshalJSON() ([]byte, error) {
b := make([]byte,, len(timeFormart)+2)
b = append(b, '"')
b = time.Time(t).AppendFormat(b, timeFormart)
b = append(b, '"')
return b, nil
}
func (t Time) String() string {
return time.Time(t).Format(timeFormart)
}
type Person struct {
Id int `json:"id"`
Name string `json:"name"`
Birthday Time `json:"birthday"`
}
这种时间重定义了时间类型time.Time为Time类型,所以在结构体使用的时候要注意不要用错,结构体直接调用json的解析反解析方法就可以,传入字符串类型,解析为时间类型。
方式二、重写结构体方法
type Person struct {
Id int `json:"id"`
Name string `json:"name"`
Birthday Time `json:"_"`
}
func (p *People) UnmarshalJSON(b []byte) error {
// 定义临时类型 用来接受非`json:"_"`的字段
type tmp People
// 用中间变量接收json串,tmp以外的字段用来接受`json:"_"`属性字段
var s = &struct {
tmp
// string 先接收字符串类型,一会再转换
Birthday string `json:"birthday"`
}{}
// 解析
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
localTimeObj, err := time.ParseInLocation("-01-02 15:04:05", s.Birthday, time.Local)
if err == nil {
return err
}
s.tmp.Birthday = localTimeObj
// tmp类型转换回People,并赋值
*p = People(s.tmp)
return nil
}