在编程中,我们经常会遭遇八小时时间差问题。这是由时区差异引起的,为了能更好地解决它们,我们需要理解几个时间定义标准。
GMT(Greenwich Mean Time),格林威治平时。GMT 根据地球的自转和公转来计算时间,它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。GMT 是前世界标准时。
UTC(Coordinated Universal Time),协调世界时。UTC 比 GMT 更精准,它根据原子钟来计算时间。在不需要精确到秒的情况下,可以认为 UTC=GMT。UTC 是现世界标准时。
从格林威治本初子午线起,往东为正,往西为负,全球共划分为 24 个标准时区,相邻时区相差一个小时。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println(time.Now())
}
中国大陆使用的是东八时区的标准时,即北京时间 CST,China Standard Time。
$ go run main.go
2022-07-17 16:37:31.186043 +0800 CST m=+0.000066647
这是默认时区下的结果,time.Now()的打印中会标注+0800 CST。
假设我们是在美国洛杉矶时区下,那得到的结果是什么呢?
$ TZ="America/Los_Angeles" go run main.go
2022-07-17 01:39:12.391505 -0700 PDT m=+0.000069514
可以看到,此时的结果是-0700 PDT 时间,即 PDT(Pacific Daylight Time)太平洋夏季时间。由于时区差异,两次执行的时间结果相差了 15 小时。
注意,在使用 Docker 容器时,系统默认的时区就是 UTC 时间(0 时区),和我们实际需要的北京时间相差八个小时,这是导致八小时时间差问题的经典场景。
时区问题的应对策略,可以详细查看 src/time/zoneinfo_unix.go 中 initLocal() 函数的加载逻辑。例如,可以通过指定环境变量 TZ,修改/etc/localtime文件等方式来解决。
因为时区问题非常重要,所以放在了文章第一部分讲述。下面开始介绍 time 库的使用。
时间瞬间 time.Time
time 库,最核心的对象是 time.Time 结构体。它的定义如下,用以表示某个瞬间的时间。
type Time struct {
// wall and ext encode the wall time seconds, wall time nanoseconds,
// and optional monotonic clock reading in nanoseconds.
wall uint64
ext int64
loc *Location
}
计算机在时间处理上,主要涉及到两种时钟。
-
墙上时钟(wall time),又称为钟表时间,用于表示具体的日期与时间。
-
单调时钟(monotonic clocks),总是保证时间是向前的,不会出现墙上时钟的回拨问题,因此它很适合用于测量持续时间段。
wall 和 ext 字段就是用于记录墙上时钟和单调时钟,精度为纳秒。字段的对应位数上关联着用于确定时间的具体年、月、日、小时、分钟、秒等信息。
loc 字段记录时区位置,当 loc 为 nil 时,默认为 UTC 时间。
因为 time.Time 用于表示具有纳秒精度的时间瞬间,在程序中通常应该将它作为值存储和传递,而不是指针。
即在时间变量或者结构体字段中,我们应该使用 time.Time,而非 *time.Time。
获取 time.Time
我们可以通过 Now 函数获取当前本地时间
func Now() Time {}
也可以通过 Date 函数,根据年、月、日等时间和时区参数获取指定时间
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {}
转换秒、毫秒、纳秒…时间戳
计算机世界中,将 UTC 时间 1970 年1月1日 0 时 0 分 0 秒作为 Unix 时间 0。所谓的时间瞬间转换为 Unix 时间戳,即计算的是从 Unix 时间 0 到指定瞬间所经过的秒数、微秒数等。
func (t Time) Unix() int64 {} // 从 Unix 时间 0 经过的秒数
func (t Time) UnixMicro() int64 {} // 从 Unix 时间 0 经过的微秒数
func (t Time) UnixMilli() int64 {} // 从 Unix 时间 0 经过的毫秒数
func (t Time) UnixNano() int64 {} // 从 Unix 时间 0 经过的纳秒数
- 10位数的时间戳是以 秒 为单位;
- 13位数的时间戳是以 毫秒 为单位;
- 19位数的时间戳是以 纳秒 为单位;
举个例子:
package main
import (
"time"
"fmt"
)
func main() {
fmt.Printf("时间戳(秒):%v;\n", time.Now().Unix())
fmt.Printf("时间戳(纳秒):%v;\n",time.Now().UnixNano())
fmt.Printf("时间戳(毫秒):%v;\n",time.Now().UnixNano() / 1e6)
fmt.Printf("时间戳(纳秒转换为秒):%v;\n",time.Now().UnixNano() / 1e9)
}
输出结果为:
时间戳(秒):1530027865;
时间戳(纳秒):1530027865231834600;
时间戳(毫秒):1530027865231;
时间戳(纳秒转换为秒):1530027865;
获取基本字段
t := time.Now()
fmt.Println(t.Date()) // 2022 July 17
fmt.Println(t.Year()) // 2022
fmt.Println(t.Month()) // July
fmt.Println(t.ISOWeek()) // 2022 28
fmt.Println(t.Clock()) // 22 21 56
fmt.Println(t.Day()) // 17
fmt.Println(t.Weekday()) // Sunday
fmt.Println(t.Hour()) // 22
fmt.Println(t.Minute()) // 21
fmt.Println(t.Second()) // 56
fmt.Println(t.Nanosecond())// 494313000
fmt.Println(t.YearDay()) // 198
持续时间 time.Duration
持续时间 time.Duration 用于表示两个时间瞬间 time.Time 之间所经过的时间。它通过 int64 表示纳秒计数,能表示的极限大约为 290 年。
// A Duration represents the elapsed time between two instants
// as an int64 nanosecond count. The representation limits the
// largest representable duration to approximately 290 years.
type Duration int64
在 Go 中,持续时间只是一个以纳秒为单位的数字而已。如果持续时间等于 1000000000,则它代表的含义是 1 秒或 1000 毫秒或 1000000 微秒或 1000000000 纳秒。
例如,相隔 1 小时的两个时间瞬间 time.Time 值,它们之间的持续时间 time.Duration 值为
1*60*60*1000*1000*1000
Go 的 time 包中定义了这些持续时间常量值
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
同时,time.Duration 提供了能获取各时间粒度数值的方法
func (d Duration) Nanoseconds() int64 {} // 纳秒
func (d Duration) Microseconds() int64 {} // 微秒
func (d Duration) Milliseconds() int64 {} // 毫秒
func (d Duration) Seconds() float64 {} // 秒
func (d Duration) Minutes() float64 {} // 分钟
func (d Duration) Hours() float64 {} // 小时
时间计算
在学习了时间瞬间和持续时间之后,我们来看如何做时间计算。
func (t Time) Add(d Duration) Time {}
- Add 函数用于增加/减少( d 的正值表示增加、负值表示减少) time.Time 的持续时间。我们可以对某瞬时时间,增加或减少指定纳秒级以上的时间。
func (t Time) Sub(u Time) Duration {}
- Sub 函数可以得出两个时间瞬间之间的持续时间。
func (t Time) AddDate(years int, months int, days int) Time {}
- AddDate 函数基于年、月和日的维度增加/减少 time.Time 的值。
当然,基于当前时间瞬间 time.Now() 的计算是最普遍的需求。因此,time 包还提供了以下便捷的时间计算函数。
func Since(t Time) Duration {}
Since 函数是 time.Now().Sub(t) 的快捷方法。
func Until(t Time) Duration {}
Until 函数是 t.Sub(time.Now()) 的快捷方法。
使用示例
t := time.Now()
fmt.Println(t) // 2022-07-17 22:41:06.001567 +0800 CST m=+0.000057466
//时间增加 1小时
fmt.Println(t.Add(time.Hour * 1)) // 2022-07-17 23:41:06.001567 +0800 CST m=+3600.000057466
//时间增加 15 分钟
fmt.Println(t.Add(time.Minute * 15))// 2022-07-17 22:56:06.001567 +0800 CST m=+900.000057466
//时间增加 10 秒钟
fmt.Println(t.Add(time.Second * 10))// 2022-07-17 22:41:16.001567 +0800 CST m=+10.000057466
//时间减少 1 小时
fmt.Println(t.Add(-time.Hour * 1)) // 2022-07-17 21:41:06.001567 +0800 CST m=-3599.999942534
//时间减少 15 分钟
fmt.Println(t.Add(-time.Minute * 15))// 2022-07-17 22:26:06.001567 +0800 CST m=-899.999942534
//时间减少 10 秒钟
fmt.Println(t.Add(-time.Second * 10))// 2022-07-17 22:40:56.001567 +0800 CST m=-9.999942534
time.Sleep(time.Second * 5)
t2 := time.Now()
// 计算 t 到 t2 的持续时间
fmt.Println(t2.Sub(t)) // 5.004318874s
// 1 年之后的时间
t3 := t2.AddDate(1, 0, 0)
// 计算从 t 到当前的持续时间
fmt.Println(time.Since(t)) // 5.004442316s
// 计算现在到明年的持续时间
fmt.Println(time.Until(t3)) // 8759h59m59.999864s
package main
// Importing fmt and time
import "fmt"
import "time"
// Calling main
func main() {
// Declaring time in UTC
t:= time.Date(2020, 11, 9, 7, 0, 0, 0, time.UTC)
// Declaring durations
d1:= t.Add(time.Second * 4)
d2:= t.Add(time.Minute * 2)
d3:= t.Add(time.Hour * 1)
d4:= t.Add(time.Hour * 22 * 7)
// Prints output
fmt.Printf("%v\n", t)
fmt.Printf("%v\n", d1)
fmt.Printf("%v\n", d2)
fmt.Printf("%v\n", d3)
fmt.Printf("%v", d4)
}
求两个日期的间隔天数
func timeSub(t1, t2 time.Time) int {
t1 = time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, time.Local)
t2 = time.Date(t2.Year(), t2.Month(), t2.Day(), 0, 0, 0, 0, time.Local)
return int(t1.Sub(t2).Hours() / 24)
}
func TimeTest(){
t1 := time.Date(2019, 12, 1, 12, 0, 0, 0, time.Local)
t2 := time.Date(2019, 11, 1, 12, 0, 0, 0, time.Local)
fmt.Println("just one day: should return 1 but got %v", timeSub(t1, t2))
}
格式化时间
在其他语言中,一般会使用通用的时间模板来格式化时间。例如 Python,它使用 %Y 代表年、%m 代表月、%d 代表日等。
但是,Go 不一样,它使用固定的时间(需要注意,使用其他的时间是不可以的)作为布局模板,而这个固定时间是 Go 语言的诞生时间。
Mon Jan 2 15:04:05 MST 2006
格式化时间涉及到两个转换函数
func Parse(layout, value string) (Time, error) {}
- Parse 函数用于将时间字符串根据它所能对应的布局转换为 time.Time 对象。
func (t Time) Format(layout string) string {}
- Formate 函数用于将 time.Time 对象根据给定的布局转换为时间字符串。
在 time 库中,Go 提供了一些预定义的布局模板常量,这些可以直接拿来使用。
const (
Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order.
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
Kitchen = "3:04PM"
// Handy time stamps.
Stamp = "Jan _2 15:04:05"
StampMilli = "Jan _2 15:04:05.000"
StampMicro = "Jan _2 15:04:05.000000"
StampNano = "Jan _2 15:04:05.000000000"
)
下面是我们可选的布局参数对照表
年 06/2006
月 01/1/Jan/January
日 02/2/_2
星期 Mon/Monday
小时 03/3/15
分 04/4
秒 05/5
毫秒 .000/.999
微秒 .000000/.999999
纳秒 .000000000/.999999999
am/pm PM/pm
时区 MST
时区小时数差-0700/-07/-07:00/Z0700/Z07:00
示例:日期格式化为字符串
const (
layoutISO = "2006-01-02"
layoutUS = "January 2, 2006"
)
date := "2012-08-09"
t, _ := time.Parse(layoutISO, date)
fmt.Println(t) // 2012-08-09 00:00:00 +0000 UTC
fmt.Println(t.Format(layoutUS)) // August 9, 2012
- 获取当前时间并转换为一定格式的字符串
import(
"reflect"
"fmt"
"time"
)
func main(){
t := time.Now()
fmt.Println(t, reflect.TypeOf(t)) //2023-03-15 19:12:05.868806848 +0800 CST m=+0.107687430 time.Time
//日期格式化为字符串
t = time.Now().Format("2006-01-02 15:04:05")
fmt.Println(t, reflect.TypeOf(t)) //2018-12-21 14:17:04 string
t = time.Now().Format("2006-01-02 ")
fmt.Println(t, reflect.TypeOf(t)) //2018-12-21 string
t = time.Now().Format("2006/01/02 ")
fmt.Println(t, reflect.TypeOf(t)) // 2018/12/21 string
t = time.Now().Format("2006\\01\\02") //注意2018\12\21会报错,在go中如果想要输出\----->\\
fmt.Println(t, reflect.TypeOf(t)) //2018\12\21 string
}
- 格林时间转换普通时间
func main() {
strTime := "2018-03-24T20:01:00+08:00"
t, err := time.ParseInLocation("2006-01-02T15:04:05+08:00", strTime, time.Local)
fmt.Println(t, err)
strTime = strTime[:len(strTime)-6]
t, err = time.ParseInLocation("2006-01-02T15:04:05", strTime, time.Local)
fmt.Println(t, err, )
b := t.String()
fmt.Println(b[:20])
}
时区转换
如果在代码中,需要获取同一个 time.Time 在不同时区下的结果,我们可以使用它的 In 方法。
func (t Time) In(loc *Location) Time {}
它的使用非常简单,直接看示例代码
now := time.Now()
fmt.Println(now) // 2022-07-18 21:19:59.9636 +0800 CST m=+0.000069242
loc, _ := time.LoadLocation("UTC")
fmt.Println(now.In(loc)) // 2022-07-18 13:19:59.9636 +0000 UTC
loc, _ = time.LoadLocation("Europe/Berlin")
fmt.Println(now.In(loc)) // 2022-07-18 15:19:59.9636 +0200 CEST
loc, _ = time.LoadLocation("America/New_York")
fmt.Println(now.In(loc)) // 2022-07-18 09:19:59.9636 -0400 EDT
loc, _ = time.LoadLocation("Asia/Dubai")
fmt.Println(now.In(loc)) // 2022-07-18 17:19:59.9636 +0400 +04
应用
获取前一天的时间
package main
import "time"
nowTime := time.Now()
yesterdayTime := nowTime.AddDate(0,0,-1); // 年,月,日 获取前一天时间
resTime := yesterdayTime.Format('2006-01-02 15:04:05')
getTime = nowTime.AddDate(0, -1, 0) //年,月,日 获取一个月前的时间
resTime = getTime.Format("2006-01-02 15:04:05") //获取的时间的格式
fmt.Println(resTime)
getTime = nowTime.AddDate(-2, 0, 0) //年,月,日 获取两年前的时间
resTime = getTime.Format("20060102") //获取的时间的格式
fmt.Println(resTime)
获取本月的第一天、最后一天
//获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。
func GetFirstDateOfMonth(d time.Time) time.Time {
d = d.AddDate(0, 0, -d.Day() + 1)
return GetZeroTime(d)
}
//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。
func GetLastDateOfMonth(d time.Time) time.Time {
return GetFirstDateOfMonth(d).AddDate(0, 1, -1)
}
//获取某一天的0点时间
func GetZeroTime(d time.Time) time.Time {
return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
}
func main() {
d := time.Now()
fmt.Println( GetFirstDateOfMonth(d))
fmt.Println( GetFirstDateOfMonth(d))
fmt.Println( GetLastDateOfMonth(d))
}
获取今天是本年第几周
package main
import (
"fmt"
"time"
)
func week_of_year()(int){
yearDay := time.Now().YearDay()
week := yearDay / 7
yu := yearDay % 7
if yu == 0{
return week
}
return week + 1
}
func main() {
fmt.Println(week_of_year())
}
获取本周周一
func main() {
now := time.Now()
offset := int(time.Monday - now.Weekday())
if offset > 0 {
offset = -6
}
weekStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
fmt.Println(weekStart)
}
今天是星期几
d := time.Now()
fmt.Println(int(d.Weekday()))
判断是不是星期天
package main
import "fmt"
var weekday = [7]string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
func main() {
var a, b, c uint16 = 2020, 4, 3
fmt.Printf("%d年%d月%d日是:%s\n", a, b, c, ZellerFunction2Week(a, b, c))
}
func ZellerFunction2Week(year, month, day uint16) string {
var y, m, c uint16
if month >= 3 {
m = month
y = year % 100
c = year / 100
} else {
m = month + 12
y = (year - 1) % 100
c = (year - 1) / 100
}
week := y + (y / 4) + (c / 4) - 2*c + ((26 * (m + 1)) / 10) + day - 1
if week < 0 {
week = 7 - (-week)%7
} else {
week = week % 7
}
which_week := int(week)
return weekday[which_week]
}
获取指定年份中的所有天数
func main() {
days := make([]string, 0)
year := time.Now().Year()
for month := 1; month <= 12; month++ {
for day := 1; day <= 31; day++ {
//如果是2月
if month == 2 {
if isLeapYear(year) && day == 30 { //闰年2月29天
break
} else if !isLeapYear(year) && day == 29 { //平年2月28天
break
} else {
days = append(days, fmt.Sprintf("%d-%02d-%02d", year, month, day))
}
} else if month == 4 || month == 6 || month == 9 || month == 11 { //小月踢出来
if day == 31 {
break
}
days = append(days, fmt.Sprintf("%d-%02d-%02d", year, month, day))
} else {
days = append(days, fmt.Sprintf("%d-%02d-%02d", year, month, day))
}
}
}
fmt.Println(days)
}
万年历
package main
import (
"flag"
"fmt"
"os"
)
var year int
var month int
var fs *flag.FlagSet
var showVersion, showUsage bool
func init() {
//初始化解析参数
fs = flag.NewFlagSet("calendar", flag.ExitOnError)
fs.BoolVar(&showUsage, "help", false, "Show this message")
fs.BoolVar(&showUsage, "h", false, "Show this message")
fs.BoolVar(&showVersion, "version", false, "Print version information.")
fs.BoolVar(&showVersion, "v", false, "Print version information.")
fs.IntVar(&year, "year", 2019, "Input year.")
fs.IntVar(&year, "y", 2019, "Input year.")
fs.IntVar(&month, "month", 8, "Input month.")
fs.IntVar(&month, "m", 8, "Input month.")
}
func perpetualCalendar(year int, month int) {
var monthDays int = 0
var totalDyas int = 0
var isLeapYear bool = false
fmt.Println("query date is:", year, month)
if year%400 == 0 || (year%4 == 0 && year%100 != 0) {
isLeapYear = true
fmt.Println(year, "is leap year")
} else {
isLeapYear = false
fmt.Println(year, "is not lear year")
}
//计算距离1900年的天数
for i := 1900; i < year; i++ {
if i%400 == 0 || (i%4 == 0 && i%100 != 0) {
totalDyas += 366
} else {
totalDyas += 365
}
}
//计算截至到当月1号前的总天数
for j := 1; j <= month; j++ {
switch j {
case 1, 3, 5, 7, 8, 10, 12:
monthDays = 31
break
case 2:
if isLeapYear {
monthDays = 29
} else {
monthDays = 28
}
break
case 4, 6, 9, 11:
monthDays = 30
break
default:
fmt.Println("input month is error.")
}
if j != month {
totalDyas += monthDays
}
}
//计算当月1号是星期几
var weekDay int = 0
weekDay = 1 + totalDyas%7
if weekDay == 7 {
weekDay = 0
}
fmt.Println("weekDay is: ", weekDay)
//展示日期
fmt.Println("星期日\t" + "星期一\t" + "星期二\t" + "星期三\t" + "星期四\t" + "星期五\t" + "星期六\t")
for k := 0; k < weekDay; k++ {
fmt.Printf("\t")
}
for m := 1; m <= monthDays; m++ {
fmt.Printf("%d\t", m)
if (weekDay+m)%7 == 0 {
fmt.Println()
}
}
fmt.Println()
}
func main() {
//开启main 函数的解析功能
fs.Parse(os.Args[1:])
if showUsage {
fs.PrintDefaults()
os.Exit(0)
}
if showVersion {
fmt.Println("version:v1.0")
os.Exit(0)
}
perpetualCalendar(year, month)
原文:https://blog.csdn.net/qq_21794823/article/details/86098594
获取前N周
func SetXWeek(x int){
now := time.Now()
offset := int(time.Monday - now.Weekday())
if offset > 0 {
offset = -6
}
thisMon := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
xMon := thisMon.AddDate(0, 0, -7*x).Format("20060102150405")
xSun := thisMon.AddDate(0, 0, -7*x+6).Format("20060102150405")
fmt.Printf(xMon, xSun)
}
获取前10分钟