前言:我在github.com/icza/gox
发布了这个实用程序,查看timex.Diff()
。
一个月中的天数取决于日期,就像闰年一样。
如果您使用time.Since()
来获取自time.Time
值以来经过的时间,或者当您计算两个time.Time
值之间的差异时使用Time.Sub()
方法,结果是一个time.Duration
,它失去了时间上下文(因为 Duration
只是纳秒级的时间差异)。这意味着无法从Duration
值准确且明确地计算出年、月等的差异。
正确的解决方案必须在时间的上下文中计算差异。您可以为每个字段(年、月、日、小时、分钟、秒)计算差异,然后将结果归一化以不具有任何负值。建议如果它们之间的关系不是预期的,则交换Time
值。
规范化意味着如果一个值为负,就加上该字段的最大值,并将下一个字段减1。例如,如果seconds
为负,则将其加上60
,并将minutes
减去1。需要注意的一点是,在规范化差异的天数(月中的天数)时,必须应用适当月份的天数。可以使用此小技巧轻松计算:
// Max days in year y1, month M1
t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
daysInMonth := 32 - t.Day()
这背后的逻辑是,天数
32
比任何一个月份的最大天数都要大。它会自动归一化(额外的天数滚到下个月,天数适当地减少)。当我们从32中减去归一化后的天数时,我们恰好得到该月的最后一天。
时区处理:
只有当我们传递的两个时间值在同一个时区(
time.Location
)中时,差异计算才会给出正确的结果。我们在函数中加入了一个检查:如果不是这种情况,我们使用
Time.In()
方法将其中一个时间值“转换”为与另一个时间值在相同的位置。
if a.Location() != b.Location() {
b = b.In(a.Location())
}
以下是一个解决方案,可以计算年份、月份、日期、小时、分钟和秒钟之间的差异:
func diff(a, b time.Time) (year, month, day, hour, min, sec int) {
if a.Location() != b.Location() {
b = b.In(a.Location())
}
if a.After(b) {
a, b = b, a
}
y1, M1, d1 := a.Date()
y2, M2, d2 := b.Date()
h1, m1, s1 := a.Clock()
h2, m2, s2 := b.Clock()
year = int(y2 - y1)
month = int(M2 - M1)
day = int(d2 - d1)
hour = int(h2 - h1)
min = int(m2 - m1)
sec = int(s2 - s1)
if sec < 0 {
sec += 60
min--
}
if min < 0 {
min += 60
hour--
}
if hour < 0 {
hour += 24
day--
}
if day < 0 {
t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
day += 32 - t.Day()
month--
}
if month < 0 {
month += 12
year--
}
return
}
一些测试:
var a, b time.Time
a = time.Date(2015, 5, 1, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 6, 2, 1, 1, 1, 1, time.UTC)
fmt.Println(diff(a, b))
a = time.Date(2016, 1, 2, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(diff(a, b))
a = time.Date(2016, 2, 2, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 3, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(diff(a, b))
a = time.Date(2015, 2, 11, 0, 0, 0, 0, time.UTC)
b = time.Date(2016, 1, 12, 0, 0, 0, 0, time.UTC)
fmt.Println(diff(a, b))
输出结果与预期一致:
1 1 1 1 1 1
0 0 30 0 0 0
0 0 28 0 0 0
0 11 1 0 0 0
在Go Playground上尝试一下。
计算你的年龄:
// Your birthday: let's say it's January 2nd, 1980, 3:30 AM
birthday := time.Date(1980, 1, 2, 3, 30, 0, 0, time.UTC)
year, month, day, hour, min, sec := diff(birthday, time.Now())
fmt.Printf("You are %d years, %d months, %d days, %d hours, %d mins and %d seconds old.",
year, month, day, hour, min, sec)
示例输出:
You are 36 years, 3 months, 8 days, 11 hours, 57 mins and 41 seconds old.
Go playground的时间开始的魔法日期/时间是:2009年11月10日23:00:00 UTC
这是Go语言首次宣布的时间。让我们计算一下Go已经存在了多久:
goAnnounced := time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC)
year, month, day, hour, min, sec := diff(goAnnounced, time.Now())
fmt.Printf("Go was announced "+
"%d years, %d months, %d days, %d hours, %d mins and %d seconds ago.",
year, month, day, hour, min, sec)
输出:
Go was announced 6 years, 4 months, 29 days, 16 hours, 53 mins and 31 seconds ago.