天天看点

go踩坑记|time.IsZero()使用踩坑分享

背景

业务场景中使用到的API或者RPC接口定义都采用的是thrift,因此时间的传递格式是i64类型的秒级时间戳。一般会选择统一逻辑对RPC的请求转换成数据库的model,时间类型的转换则是通过

time.Unix(sec,0)

方式转换的,时间是可以为0值。 不久前做一个数据发布录入功能,里面有一个发布时间字段,通过上面的方式转换成model的time.Time类型之后,统一判断如果发布时间是0值(

t.IsZero()

)则重新赋值为当前系统时间即

time.Now()

问题

按照上面方式写好代码测试发现,数据库存在了很多发布时间是0。调试发现:

var sec int64
t:=time.Unix(sec,0)
fmt.Println(t.IsZero()) // false
           

time.Unix(0,0).IsZero()

的结果是false。

排查

首先想到的是看下

IsZero

的实现,如下:

// IsZero reports whether t represents the zero time instant,
// January 1, year 1, 00:00:00 UTC.
func (t Time) IsZero() bool {
	return t.sec() == 0 && t.nsec() == 0
}
           

从这里看好像是没问题的,因为

time.Unix

传入的两个参数都是0即

sec=0

nsec=0

,但是为什么返回false呢?于是怀疑

sec()

或者

nsec()

方法有问题,于是跟进并没有发现问题。

// nsec returns the time's nanoseconds.
func (t *Time) nsec() int32 {
	return int32(t.wall & nsecMask)
}

// sec returns the time's seconds since Jan 1 year 1.
func (t *Time) sec() int64 {
	if t.wall&hasMonotonic != 0 {
		return wallToInternal + int64(t.wall<<1>>(nsecShift+1))
	}
	return t.ext
}

           

既然取的时候没问题,那就应该是设值的时候有问题了。然后查看

time.Unix(sec,nsec)

实现如下:

// Unix returns the local Time corresponding to the given Unix time,
// sec seconds and nsec nanoseconds since January 1, 1970 UTC.
// It is valid to pass nsec outside the range [0, 999999999].
// Not all sec values have a corresponding time value. One such
// value is 1<<63-1 (the largest int64 value).
func Unix(sec int64, nsec int64) Time {
	if nsec < 0 || nsec >= 1e9 {
		n := nsec / 1e9
		sec += n
		nsec -= n * 1e9
		if nsec < 0 {
			nsec += 1e9
			sec--
		}
	}
	return unixTime(sec, int32(nsec))
}

func unixTime(sec int64, nsec int32) Time {
	return Time{uint64(nsec), sec + unixToInternal, Local}
}
//	unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
           

终于发现了问题,也就是

time.Unix

入参

sec

nsec

认为是从

1970-01-01 08:00:00

开始计算的,也就是后面加上的

unixToInternal

,代表的就是从时间0值

0001-01-01 00:00:00

1970-01-01 08:00:00

的秒级的时间戳。

简言之:

time.Unix(0,0)=1970-01-01 08:00:00

,而

IsZero

判断是时间是否等于

0001-01-01 00:00:00

,因此

time.Unix(0,0).IsZero()

的结果是false。

解决

自定义一个函数

IsZero

,判断当前的时间是否为0值:

import "time"

//1970-01-01 08:00:00 +0800 CST
var zeroTime = time.Unix(0, 0)

// IsZero reports whether t represents the zero time instant
func IsZero(t time.Time) bool {
	return t.IsZero() || zeroTime.Equal(t)
}
           

测试:

func TestIsZero(t *testing.T) {
	type args struct {
		t time.Time
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{name: "test1", args: args{time.Unix(0, 0)}, want: true},
		{name: "test2", args: args{}, want: true},
		{name: "test3", args: args{time.Now()}, want: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := IsZero(tt.args.t); got != tt.want {
				t.Errorf("IsZero() = %v, want %v", got, tt.want)
			}
		})
	}
}
           

=== RUN TestIsZero

=== RUN TestIsZero/test1

=== RUN TestIsZero/test2

=== RUN TestIsZero/test3

— PASS: TestIsZero (0.00s)

— PASS: TestIsZero/test1 (0.00s)

— PASS: TestIsZero/test2 (0.00s)

— PASS: TestIsZero/test3 (0.00s)

PASS

从测试结果符合预期即:time.Unix(0,0)及没有初始化的time.Time默认值都为true。

另外从上面也可以看出,time的IsZero方法可以用来判断是否对time.Time类型的属性做过初始化,或者说该类型的时间是否为

0001-01-01 00:00:00

本次旅程到此结束,希望对你有所帮助!