时间
time.Now()
获取当前时间- 返回值是time类型
- 默认获取的是当地时间
- 使用
time.Now().UTC()
获取对应的格林威治时间
- 使用
-
time类型
- 实际是一个结构体
- 由Unix时间和地域信息组成
- 字符串打印:
2018-11-21 11:50:39.540473 +0800 CST m=+0.000311562
+8000 CST
指中国标准时间+0000 UTC
指格林威治时间
- 实际是一个结构体
-
unix时间
- 从格林威治时间1970年1月1日0时开始到现在的总秒数
time.Now().Unix()
// 总纳秒数
time.Now().UnixNano()
格式化时间
- time类型->字符串
- 只能用
2006-01-02 15:04:05
这个时间点作为格式化模板 - 格式化模板中的数字均有对应的含义,无论出现在哪个位置
- 例如
2006
代表年,06
代表年的后两位,03
代表月份
- 例如
- 只能用
str1 := time.Now().Format("2006-01-02 15:04:05")
str2 := time.Now().Format("2006年1月2日 15:04:05")
- 字符串->time类型
- 返回值是time类型
- 默认被解析的时间是UTC时间,而不是北京时间
- 我们应该总是使用
time.ParseInLocation
来解析时间,并给第三个参数传递time.Local
- 我们应该总是使用
timestamp1, err := time.Parse("2006-01-02 15:04:05", str1)
timestamp2, err := time.Parse("2006年1月2日 15:04:05", str2)
- 时间格式常量
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
时间长度(Duration)
type Duration int64
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
time.ParseDuration(s string) (Duration, error)
// 减去7分钟
m,_ := time.ParseDuration("-7m")
// 加上10秒
s,_ := time.ParseDuration("10s")
time.Duration
表示时间长度- 以纳秒为基数
- 底层数据类型为
int64
- int64类型的变量不能直接和
time.Duration
类型相乘,需要显示转换; 常量除外- 不行:
num * time.Second
- 可以:
time.Duration(num) * time.Second
- 可以:
5 * time.Second
- 不行:
时间比较
dt := time.Date(2018, 1, 10, 0, 0, 1, 100, time.Local)
fmt.Println(time.Now().After(dt)) // true
fmt.Println(time.Now().Before(dt)) // false
fmt.Println(dt.Equal(time.Now())) // 判断两个时间点是否相等时使用Equal函数
时间计算
time.Add()
用来获得指定时间
// 将t0加duration获取t1使用Add方法
func (t Time) Add(d Duration) Time
//一天之前
time.Now().Add(-24 * time.Hour)
// 一年前+2月后+三天前
time.Now().AddDate(-1,2,-3)
time.Sub()
用来计算时间长度
//获取t0和t1的duration使用Sub
func (t Time) Sub(u Time) Duration
start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
// 获取当日0点
func Today() time.Time {
t := time.Now()
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
// 获取昨日0点
func Yesterday() time.Time {
t := Today()
return t.AddDate(0, 0, -1)
}
time.Round
和time.Truncate
获取整点时间
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2016-06-13 15:34:39", time.Local)
// 整点(向下取整)
fmt.Println(t.Truncate(1 * time.Hour))
// 整点(最接近)
fmt.Println(t.Round(1 * time.Hour))
时区
// 本地时区
time.Local
// UTC时区
time.UTC
超时控制
time.After
func After(d Duration) <-chan Time
// 在时间d后自动执行函数f
func AfterFunc(d Duration, f func()) *Timer
-
一个
time.After
应该只被使用一次,否则会发生死锁 -
time.After
从执行到定义的那一行开始计时 -
time.After
和select联合使用以实现超时- select中定义的
time.After
从select执行时开始计时,如果select在超时前再被执行则重新开始计时- 当外层有for循环时容易发生
- 因此如果需要全局超时,而不是每次执行的超时,需要在for循环前定义
time.After
- 如果select中包含default分支则超时永远执行不到
- select中定义的
-
time.AfterFunc
等待一定时间后执行函数- 底层启动一个goroutine执行函数
- 可以利用返回值的Stop操作取消执行
time.Tick
time.Tick
是对time.NewTicker
的简化- 不要定义在for循环内,容易造成CPU暴涨
- 例如
for range time.Tick(time.Second)
- 这样会导致每次循环都创建一个计时goroutine
- 应该定义在循环前
- 例如
time.Tick
函数 仅仅在应用整个生命周期都需要时才适合- 否则应该使用
time.NewTicker
并手动stop time.Tick
函数的行为很像创建一个 goroutine 在循环里面调用time.Sleep
,然后在它每次醒来时发送事件- 如果停止监听 tick,但是计时器 goroutine 还在运行,徒劳地向一个没有 goroutine 在接收的通道中不断发送
- 这会造成goroutine泄露
- 否则应该使用
func Tick(d Duration) <-chan Time
// 定时执行任务
timer := time.Tick(time.Second)
for range timer {
fmt.Println("hello")
}
- timer 是一个四叉堆实现,在 timer 数量较多、频繁创建和删除 timer 的场景下,会频繁对这个 timer 堆进行调整,开销较大
- 如果对于 timer 的精度要求不高,可以采用类似 netty 的方案,基于时间轮做超时
- 将删除和创建 timer 的复杂度变成了
O(1)
- 将删除和创建 timer 的复杂度变成了
- 如果对于 timer 的精度要求不高,可以采用类似 netty 的方案,基于时间轮做超时
- 同时多个请求复用一个 timer,可以大幅减少 timer 小对象的分配开销
time.NewTicker
- 同上,不要定义在for循环内
- 用完后记得用Stop函数关闭goroutine
func() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop() //一定要stop
for range ticker.C {
fmt.Println("hello")
}
}