在Golang中,时间日期的操作主要通过标准库
time
包来完成,该包提供了丰富的函数和类型来处理日期和时间
基础操作
获取当前时间
使用time.Now()
函数获取当前时间:
now := time.Now()
fmt.Println(now)
格式化时间
使用time.Format(layout)
方法按照指定的布局格式化时间:
formatted := now.Format("2006-01-02 15:04:05")
fmt.Println(formatted)
注意:布局字符串中的年、月、日等元素必须遵循特定的约定,如2006
代表年份,01
代表月份等。
解析时间字符串
使用time.Parse(layout, value string)
解析时间字符串为Time
类型:
parsedTime, err := time.Parse("2006-01-02 15:04:05", "2023-04-01 14:20:00")
if err != nil {
fmt.Println("Error parsing time:", err)
} else {
fmt.Println(parsedTime)
}
高级操作
计算时间间隔
使用time.Sub()
计算两个时间点之间的间隔:
later := now.Add(time.Hour)
duration := later.Sub(now)
fmt.Println(duration)
时间比较
使用Before()
, After()
, Equal()
方法比较时间:
if now.Before(later) {
fmt.Println("Now is before later")
}
设置时间
使用time.Date()
函数创建特定的日期时间:
specificTime := time.Date(2023, 4, 30, 15, 30, 0, 0, time.UTC)
fmt.Println(specificTime)
时间戳转换
- 将时间转换为Unix时间戳(以秒为单位):
t.Unix()
- 将时间转换为Unix纳秒时间戳:
t.UnixNano()
- 从Unix时间戳创建时间:
time.Unix(sec int64, nsec int64)
时区处理
使用time.LoadLocation()
加载时区,并通过time.In()
调整时间的时区:
location, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
} else {
easternTime := now.In(location)
fmt.Println(easternTime)
}
定时器和ticker
time.NewTimer(d Duration)
创建一个计时器,d后触发事件。
func main() {
// 创建一个5秒后到期的计时器
timer := time.NewTimer(5 * time.Second)
// 使用select等待计时器的通道发送信号或者主goroutine被中断
<-timer.C
fmt.Println("5 seconds have passed")
}
time.NewTicker(d Duration)
创建一个周期性的ticker,每隔d时间触发一次事件。
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个每4秒触发一次的ticker
ticker := time.NewTicker(4 * time.Second)
done := make(chan bool) // 用于控制ticker的停止
go func() {
for {
select {
case <-ticker.C:
fmt.Println("Current time:", time.Now().Format("15:04:05"))
case <-done: // 接收到停止信号后退出循环
return
}
}
}()
// 让ticker运行10秒后停止
time.Sleep(10 * time.Second) //注意主main goroutine退出了就退出整个程序了
done <- true // 发送信号到done通道
ticker.Stop() // 停止ticker,释放资源(可选,因为goroutine已退出)
}
限制并发执行
使用时间相关的操作来限制并发执行,例如基于时间窗口的限流
使用 channel 作为信号量(Semaphore)来限制并发执行的 goroutine 数量。这是一个非常实用的模式,尤其是在需要控制同时运行的任务数量,以避免资源过度消耗或满足外部服务的调用限制时。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
semaphore := make(chan struct{}, 5) // 同时只允许5个goroutine执行
for i := 1; i <= 10; i++ {
semaphore <- struct{}{} // 获取令牌
fmt.Println("------for循环遍历到第", i)
go func(i int) {
defer func() { <-semaphore }() // 执行完释放令牌
// 执行具体任务
time.Sleep(time.Millisecond * time.Duration(rand.Intn(3000)))
fmt.Printf("---------------------------任务 %d 已完成\n", i)
}(i)
}
}
解释:此时for循环跟goroutine是并发的
-
初始化信号量(Semaphore):
semaphore := make(chan struct{}, 5)
创建了一个容量为5的无缓冲channel。这里的struct{}
是一个空结构体,不占用任何内存空间,常用于这种只需要信号而不传递具体值的场景。声明中的数字5意味着最多允许5个goroutine同时运行。 -
并发循环: 通过
for
循环启动10个goroutine来模拟执行一系列任务。每个goroutine代表一个独立的任务。 -
获取令牌:
semaphore <- struct{}{}
这行代码实质上是向channel中发送一个空结构体。因为channel的容量是5,所以在前5个goroutine发送信号时会立即完成,之后的goroutine会阻塞在这里,直到有goroutine释放令牌。 -
执行任务: 在每个goroutine中,使用匿名函数执行具体任务。这里使用
time.Sleep
模拟不同长度的任务执行时间,实际应用中这将替换为真正的任务逻辑。 -
释放令牌:
defer func() { <-semaphore }()
确保无论goroutine中的代码如何结束(正常执行完毕或因错误提前退出),都会从channel中接收一个信号,从而释放一个令牌,允许等待的goroutine继续执行。
通过这种方式,你有效地控制了并发执行的goroutine数量,确保了在任何时候并发执行的任务都不会超过预设的限制(本例中为5)。这是一种简单而有效的并发控制机制,适用于多种并发编程场景
简单做个案例
package utils
import (
"time"
)
// UnixToTime 时间戳转换成日期函数
func UnixToTime(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-02 15:04:05")
}
// DateToUnix 日期转换成时间戳
func DateToUnix(str string) int64 {
template := "2006-01-02 15:04:05"
t, err := time.ParseInLocation(template, str, time.Local)
if err != nil {
return 0
}
return t.Unix()
}
// GetUnix 获取当前时间戳
func GetUnix() int64 {
return time.Now().Unix()
}
// GetDate 获取当前日期
func GetDate() string {
template := "2006-01-02 15:04:05"
return time.Now().Format(template)
}
// GetDay 获取年月日
func GetDay() string {
template := "20060102"
return time.Now().Format(template)
}