何谓时间,Golang时间模块错误小结


最近测试公司的一个服务代码,发现两个容器中时间戳不匹配,一路查问题,最后发现是go中时间解析的问题,这里记录一下。

1.time.Now()

首先这个函数,返回的是当地时间,及带时区的时间。
看下源代码如何说明的:

// Now returns the current local time.
func Now() Time {
	sec, nsec, mono := now()
	mono -= startNano
	sec += unixToInternal - minWall
	if uint64(sec)>>33 != 0 {
		return Time{uint64(nsec), sec + minWall, Local}
	}
	return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}

返回值为Time结构体类型,为本地时间。本地时间与UTC时间一般而言是不同的,这里需要注意区分。

// In returns a copy of t representing the same time instant, but
// with the copy's location information set to loc for display
// purposes.
//
// In panics if loc is nil.
func (t Time) In(loc *Location) Time {
	if loc == nil {
		panic("time: missing Location in call to Time.In")
	}
	t.setLoc(loc)
	return t
}

In函数可以把本地的时间转为另一个时区显示。

package main

import 
("fmt"
"time")

func main() {
	location, _ := time.LoadLocation("Asia/Shanghai")
	timer1:=time.Now()
	timer2:=time.Now().In(location)
	fmt.Println(timer1.Format("2006-1-2 15:04:05"),timer2.Format("2006-1-2 15:04:05"))
}

结果为

2009-11-10 23:00:00 2009-11-11 07:00:00

Program exited.

下面要说明几个概念,UTC时间,时区,时间戳,方便后面函数理解。

时间值,需要包含两个部分,才算完整。一个是时刻,第二是所在的时区,不包含时区的时间是有歧义,不完整的,不带时区,说早上八点准确来说是不知道什么意义的,北京的早八点和纽约的早八点完全不是一个时间。

这再次告诉了我们,世界是这么大,无数人生活在不同时刻,我们可以说都生活在一个个平行世界,互相都没有察觉遥远的地方,有些人时间超前或滞后于自己。

所以程序里的时间,需要带时区,中国是第八时区。不带时区,可能会是个大坑。我开始也认为中国只有一个时区,还加时区属性干什么?结果说明是有必要的。

说到时区,可能都理解是什么意思,但如果说UTC,可能就不太熟悉。UTC为世界调和时,就是我们现在使用的时间。

说明如下:

  • UTC是我们现在用的时间标准,GMT是老的时间计量标准。UTC是根据原子钟来计算时间,而GMT是根据地球的自转和公转来计算时间,也就是太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间就是中午12点。由于现在世界上最精确的原子钟50亿年才会误差1秒(最精确原子钟问世:50亿年误差一秒),可以说非常精确
    全世界大约有20多个国家的不同实验室分别建立了各自独立的地方原子时。国际时间根据比较、综合世界各地原子钟数据,最后确定的原子时,称为国际原子时,简称TAI。
    TAI的起点是这样规定的:取1958年1月1日0时0分0秒世界时(UT)的瞬间作为同年同月同日0时0分0秒TAI。

UTC是根据UT的时间,相对于天文观测时间-格林尼治时间(误差的来源是地球公转与自转并不均匀)加以调整,使两者相差不会大于0.9秒,也是现在使用的世界标准时间。其也代表UTC时区,即0时区。其他时区的时间在其时刻加上对于时间差,中国为UTC+8。

好了,最后是时间戳,时间戳是以某个时刻为起点,计算到目前时间所经过的秒数,go中使用的起点为1970年1月1日00:00:00 UTC,到目前一般为十几亿秒了。go计算时间戳,与时区无关,会自动转为到UTC时区。因为计算时间戳,必须保证时区一致。接下来讲go时间模块这次遇到的问题。

2.时间解析time.parse()

time的时间模块,常用的包含三个部分,获取时间,时间格式转换,与生成时间戳。
格式转换包含时间格式输出为string格式和string格式转为时间格式。
这里主要讲string格式转为时间格式的parse模块。分为两个方法,time,parse与time.parseInLocal。结论是,不要使用time.parse(),为什么呢,下面会说。
话不多说,直接看源代码。

// In the absence of a time zone indicator, Parse returns a time in UTC.
//
// When parsing a time with a zone offset like -0700, if the offset corresponds
// to a time zone used by the current location (Local), then Parse uses that
// location and zone in the returned time. Otherwise it records the time as
// being in a fabricated location with time fixed at the given zone offset.
//
// When parsing a time with a zone abbreviation like MST, if the zone abbreviation
// has a defined offset in the current location, then that offset is used.
// The zone abbreviation "UTC" is recognized as UTC regardless of location.
// If the zone abbreviation is unknown, Parse records the time as being
// in a fabricated location with the given zone abbreviation and a zero offset.
// This choice means that such a time can be parsed and reformatted with the
// same layout losslessly, but the exact instant used in the representation will
// differ by the actual zone offset. To avoid such problems, prefer time layouts
// that use a numeric zone offset, or use ParseInLocation.
func Parse(layout, value string) (Time, error) {
	return parse(layout, value, UTC, Local)
}

// ParseInLocation is like Parse but differs in two important ways.
// First, in the absence of time zone information, Parse interprets a time as UTC;
// ParseInLocation interprets the time as in the given location.
// Second, when given a zone offset or abbreviation, Parse tries to match it
// against the Local location; ParseInLocation uses the given location.
func ParseInLocation(layout, value string, loc *Location) (Time, error) {
	return parse(layout, value, loc, loc)
}

go中对时间解析函数有很长的说明,总结一点,使用parse时,如果字符串没有带时区说明,如直接给2019-9-1 18:32:09这样的字符串,是默认为UTC时区,也可以指定时区进行解析,而parseInLocal需要给定字符串时间所代表的时区,如Asia/Shanghai,按这个时区进行解析。

好的,最后看时间戳计算函数。

// Unix returns t as a Unix time, the number of seconds elapsed
// since January 1, 1970 UTC. The result does not depend on the
// location associated with t.
// Unix-like operating systems often record time as a 32-bit
// count of seconds, but since the method here returns a 64-bit
// value it is valid for billions of years into the past or future.
func (t Time) Unix() int64 {
	return t.unixSec()
}

注意这句话,since January 1, 1970 UTC. The result does not depend on the location associated with t.结果返回的是经过转换的UTC 0时区的时间戳。

下面是这次时间的问题。

这次的服务就出在这个问题上,服务设定了一个时间段,比如7:00-16:00,然后需要加上当天日期,生成两个时间戳,对仪器检测到信号的时间进行比较,在时间内,就通过,反之,屏蔽掉。

如果使用时间比较,那么同日期下,7:00与8:00很好比较,不会有问题。但目前使用的是时间戳time.Unix()比较,那么就必须确保string解析到了正确的时区。这里Unix()说不依赖时区的意思是,全世界同一时刻,尽管有不同时区,Unix()都会把时间转为UTC0时区,再计算时间戳。

而时间解析时,使用的parse函数却没有加时区说明,导致本地时间7:00,变为了UTC0 时区的7:00。但仪器使用的时间,为local时间UTC+8 的,也就是说仪器的时间,永远比这里时间段少八个小时,可以说仪器时间慢了8小时,也可以说这里设置的超前了8小时,实际是设置超前了。

解决办法,使用location, _ := time.LoadLocation(“Asia/Shanghai”)获取正确时区,然后使用parseInLocation解析时间,问题解决。

3.小结

写服务时,时间的使用范围十分广泛,为确保没有问题,时间一定需要带上时区,确保在解析和转换时不发生意想不到的错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值