Golang中time包的bug问题求助

Golang中time包的bug问题求助

package main

import (
	"fmt"
	"time"
)

func main() {
	FORMAT := "2006-01-02 15:04:05"
	a := time.Now()
	fmt.Println("Now:", time.Now().Format(FORMAT))
	fmt.Println("A:", a.Format(FORMAT), a.Unix(), a.UnixNano())

	fmt.Println("\nSleep...\n")
	time.Sleep(time.Second * 10)
	fmt.Println("Now:", time.Now().Format(FORMAT))

	b := a.Add(5 * time.Hour)
	c := time.Now()

	fmt.Println("B:", b.Format(FORMAT), b.Unix(), b.UnixNano())
	fmt.Println("C:", c.Format(FORMAT), c.Unix(), c.UnixNano())

	if b.After(c) {
		fmt.Println("After,B after C")
	} else {
		fmt.Println("After,B is not after C")
	}

	if b.Unix() >= c.Unix() {
		fmt.Println("Unix,B after C")
	} else {
		fmt.Println("Unix,B is not after C")
	}

	if b.UnixNano() >= c.UnixNano() {
		fmt.Println("UnixNano,B after C")
	} else {
		fmt.Println("UnixNano,B is not after C")
	}
}

QQ图片20201210171635

Go版本: go version go1.14.1 windows/amd64 go version go1.15.6 windows/amd64

在程序休眠期间,修改系统时间(增加一天),After 函数返回了 true?

这是为什么?


更多关于Golang中time包的bug问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

好的,我明白了。

谢谢

更多关于Golang中time包的bug问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


好的,所以我需要使用 Round 函数,时间比较才会正确吗?

当代码运行到 time.Sleep(time.Second * 10) 时,修改你的操作系统时间(增加一天)。

我需要使用 ‘Round’ 函数,时间比较会正确吗?

剥离单调时钟读数的规范方法是使用 t = t.Round(0)。

这将确保时间是挂钟(系统)时间。

hollowaykeanho:

未知原因

已知原因:时间包:单调时钟

hollowaykeanho:

看起来像是一个错误。你能通过发送电子邮件到 https://groups.google.com/g/golang-nuts 联系开发者吗?

请不要这样做。阅读时间包:单调时钟

当代码运行到 time.Sleep(time.Second * 10) 时,修改你的操作系统时间(增加一天)。

已知原因:Package time: Monotonic Clocks

我之前没有意识到挂钟时间增加了。那样的话,@petrus 是对的。挂钟和单调时钟之间存在差异。

我过于关注操作系统的差异,没有注意到这里有一个调整。

编辑:抱歉,刚才出去吃饭了。

GahdWu:

在休眠期间修改系统时间(增加一天),After 函数会返回 true 吗?

这看起来像是一个错误。你可以通过发送邮件到 https://groups.google.com/g/golang-nuts 联系开发者。

我在 Debian amd64 上无法复现你的问题。

u0:test$ go run main.go
Now: 2020-12-10 18:12:28
A: 2020-12-10 18:12:28 1607595148 1607595148476836765

Sleep...

Now: 2020-12-10 18:12:38
B: 2020-12-10 23:12:28 1607613148 1607613148476836765
C: 2020-12-10 18:12:38 1607595158 1607595158477300968
After,B after C
Unix,B after C
UnixNano,B after C
u0:test$ go run main.go
Now: 2020-12-10 18:12:40
A: 2020-12-10 18:12:40 1607595160 1607595160732716728

Sleep...

Now: 2020-12-10 18:12:50
B: 2020-12-10 23:12:40 1607613160 1607613160732716728
C: 2020-12-10 18:12:50 1607595170 1607595170733191244
After,B after C
Unix,B after C
UnixNano,B after C

go version go1.15.2 linux/amd64 环境下。

GahdWu:

为什么?

显然,在你的第二次执行中,未知原因导致 C 的 Unix 格式时间表现异常。该值应该更接近 A,而不是 B。

根据你提供的代码和描述,这不是Go语言time包的bug,而是系统时间修改导致的预期行为。

time.Now() 获取的是当前系统时间,而 a.Add() 是基于 a 的时间值进行计算。当你修改系统时间后,c := time.Now() 获取的是修改后的新时间,而 b := a.Add(5 * time.Hour) 仍然基于修改前的 a 进行计算。

示例重现:

package main

import (
	"fmt"
	"time"
)

func main() {
	// 记录初始时间
	now := time.Now()
	fmt.Printf("初始时间: %v\n", now.Format("2006-01-02 15:04:05"))
	
	// 模拟5小时后
	future := now.Add(5 * time.Hour)
	fmt.Printf("5小时后: %v\n", future.Format("2006-01-02 15:04:05"))
	
	// 假设此时系统时间被向前调整了24小时
	// 实际环境中是通过修改系统时钟实现的
	adjustedNow := now.Add(24 * time.Hour) // 模拟时间调整
	fmt.Printf("调整后当前时间: %v\n", adjustedNow.Format("2006-01-02 15:04:05"))
	
	// 比较
	fmt.Printf("future.After(adjustedNow): %v\n", future.After(adjustedNow))
	fmt.Printf("future.Unix() >= adjustedNow.Unix(): %v\n", 
		future.Unix() >= adjustedNow.Unix())
}

输出结果会显示 future.After(adjustedNow) 返回 false,因为调整后的时间已经超过了 future

关键点:

  1. time.Time 值是不可变的,a.Add() 返回新的时间值
  2. After() 方法比较的是两个 time.Time 值的内在时间戳
  3. 修改系统时间会影响后续 time.Now() 的返回值
  4. 所有比较方法(AfterBeforeEqual)和Unix时间戳比较的结果应该一致

如果你需要不受系统时间修改影响的时间计算,可以考虑使用 time.Since() 或单调时钟:

package main

import (
	"fmt"
	"time"
)

func main() {
	start := time.Now()
	
	// 使用单调时钟计算经过的时间
	time.Sleep(2 * time.Second)
	elapsed := time.Since(start) // 不受系统时间修改影响
	
	fmt.Printf("实际经过时间: %v\n", elapsed)
	
	// 基于单调时钟的未来时间点
	future := start.Add(5 * time.Hour)
	actualFuture := start.Add(elapsed).Add(5 * time.Hour)
	
	fmt.Printf("理论未来时间: %v\n", future.Format("15:04:05"))
	fmt.Printf("实际未来时间: %v\n", actualFuture.Format("15:04:05"))
}

在时间敏感的应用中,建议使用单调时钟进行持续时间计算,使用系统时钟进行绝对时间显示。

为什么?

单调时钟与挂钟时钟的区别。请阅读 time 包:单调时钟

使用以下版本的代码运行你的测试,并以文本形式(而非图片)发布你的输出。

package main

import (
	"fmt"
	"time"
)

func main() {
	FORMAT := "2006-01-02 15:04:05"
	a := time.Now()
	fmt.Println("Now:", time.Now().Format(FORMAT))
	fmt.Println("A:", a.Format(FORMAT), a.Unix(), a.UnixNano())

	fmt.Println("\nSleep...\n")
	time.Sleep(time.Second * 10)
	fmt.Println("Now:", time.Now().Format(FORMAT))

	b := a.Add(5 * time.Hour)
	c := time.Now()

	fmt.Println("B:", b.Format(FORMAT), b.Unix(), b.UnixNano())
	fmt.Println("C:", c.Format(FORMAT), c.Unix(), c.UnixNano())

	fmt.Println()
	fmt.Println("Monotonic Clock (m):")
	fmt.Println("A:", a)
	fmt.Println("B:", b)
	fmt.Println("C:", c)
	fmt.Println("B after C:", b.After(c))
	fmt.Println("Wall Clock:")
	fmt.Println("A:", a.Round(0))
	fmt.Println("B:", b.Round(0))
	fmt.Println("C:", c.Round(0))
	fmt.Println("B after C:", b.Round(0).After(c.Round(0)))
	fmt.Println()

    fmt.Println("Monotonic Clock")
	if b.After(c) {
		fmt.Println("After,B after C")
	} else {
		fmt.Println("After,B is not after C")
	}

    fmt.Println("Wall Clock")
	if b.Unix() >= c.Unix() {
		fmt.Println("Unix,B after C")
	} else {
		fmt.Println("Unix,B is not after C")
	}

    fmt.Println("Wall Clock")
	if b.UnixNano() >= c.UnixNano() {
		fmt.Println("UnixNano,B after C")
	} else {
		fmt.Println("UnixNano,B is not after C")
	}
}

.

Monotonic Clock (m):
A: 2020-12-10 06:53:48.766741505 -0500 EST m=+0.000081695
B: 2020-12-10 11:53:48.766741505 -0500 EST m=+18000.000081695
C: 2020-12-10 06:53:58.776090071 -0500 EST m=+10.009429982
B after C: true
Wall Clock:
A: 2020-12-10 06:53:48.766741505 -0500 EST
B: 2020-12-10 11:53:48.766741505 -0500 EST
C: 2020-12-10 06:53:58.776090071 -0500 EST
B after C: true

Monotonic Clock
After,B after C
Wall Clock
Unix,B after C
Wall Clock
UnixNano,B after C
回到顶部