当前业务需要计算机器使用过了多少天,需要使用自然日计算,想着这么经典的问题直接丢给chatgpt得了,结果GPT给出的答案是有问题的,而且不容易看出来:

用户:使用golang计算时间变量t和当前时间相差多少个自然日

ChatGPT:
你可以使用Go的time包来计算两个时间之间相差的自然日。自然日是指从午夜到午夜的一整天,而不考虑时间的时区或具体的小时数。

下面是一个示例代码,展示了如何计算变量t和当前时间之间相差多少个自然日:

package main

import (
"fmt"
"time"
)

func main() {
// 假设 t 是你的时间变量
t := time.Date(2025, time.February, 1, 0, 0, 0, 0, time.UTC)

// 获取当前时间
now := time.Now()

// 将两个时间的日期部分提取出来(去掉时分秒)
tDate := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())

// 计算两个日期之间的天数差
daysDiff := nowDate.Sub(tDate).Hours() / 24

// 输出相差的自然日
fmt.Printf("t和当前时间相差%.0f个自然日\n", daysDiff)
}

解释:
使用time.Date将时间 t 和当前时间 now 转换成只有日期(年、月、日)的部分。这样忽略了具体的时分秒,确保计算的是自然日的差异。
使用Sub方法计算时间差,并将结果转化为小时数,最后除以24获取天数差。
你可以根据实际需要替换time.Date(2025, time.February, 1, 0, 0, 0, 0, time.UTC)中的时间来测试不同的时间差。

问题1

golang time.Sub函数返回的Duration是有上限的,当相差时间太大,会导致计算的天数不对,需要进行校验,在golang官方文档中也给出了说明:

func (t time.Time) Sub(u time.Time) time.Duration

Sub returns the duration t-u. If the result exceeds the maximum (or minimum) value that can be stored in a Duration, the maximum (or minimum) duration will be returned. To compute t-d for a duration d, use t.Add(-d).

这里需要加校验和错误处理逻辑:

if t1Date.Add(sub).Unix() != t2Date.Unix() {
log.Error("error calculate days between", t1Date, t2Date, sub)
return 0
}

问题2

在gpt给出的日期提取逻辑中:

// 将两个时间的日期部分提取出来(去掉时分秒)
tDate := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())

将时间归零,时区使用了.Location函数,这样会出现同样的凌晨的时间,因为时区不一样,导致实际时间不一样

比如说:

t是UTC时间的今天0点,now是CST时间的上午8点,实际上这两个时间一样的,没有差值

此时tDate是UTC的0点,nowDate是CST的0点

t2Date.Sub(t1Date) = 8

此时,daysDiff := nowDate.Sub(tDate).Hours() / 24 值为 0

相差0天,没有问题

但是

如果t是UTC时间的昨天0点,now是CST时间的上午8点,实际相差1天


那么t2Date.Sub(t1Date) = 16

daysDiff := nowDate.Sub(tDate).Hours() / 24 值为 0


就有问题了

解决方案

时区转换为同一时区:

t后面加上.Local()转为本地时区

tDate := time.Date(t.Local().Year(), t.Local().Month(), t.Local().Day(), 0, 0, 0, 0, t.Local)
nowDate := time.Date(now.Local().Year(), now.Local().Month(), now.Local().Day(), 0, 0, 0, 0, now.Local)

然后再进行校验和计算天数