Golang中使用time.Parse和time.Sub解析时间间隔

Golang中使用time.Parse和time.Sub解析时间间隔 我需要解析格式为 HH:MM:SS 的 time.Duration。

使用 time.Parse("15:04:05", stringToParse) 来解析非常方便,但之后我需要获取持续时间。为此有另一个方便的方法 time.Sub。

所以基本上这段代码应该能工作:

func parseDur(s string) time.Duration {
	t, _ := time.Parse("15:04:05", o)
	return t.Sub(time.Time{})
}

但是这段代码返回的解析后持续时间是 -1 年。我可以通过返回以下内容来修复它:

t.AddDate(1, 0, 0).Sub(time.Time{})

但这看起来太丑了!

看起来 time.Time{} 返回的是 0001-01-01 00:00:00 +0000 UTC 但是 time.Parse() 默认返回的是 0000-01-01 00:00:00 +0000 UTC

这不是设计缺陷吗?应该修复吗?


更多关于Golang中使用time.Parse和time.Sub解析时间间隔的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

感谢您为这个问题提供的出色解决方案!在我看来,第一个方案似乎更优雅。

我知道时间的零值是 UTC 时间 1 年 1 月 1 日 00:00:00.000000000,但为什么 time.Parse 的返回值不是基于这个零值呢?

更多关于Golang中使用time.Parse和time.Sub解析时间间隔的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


为什么 time.Parse 的返回值不基于这个零值?

Time 类型的零值是 UTC 时间的公元 1 年 1 月 1 日 00:00:00.000000000。由于这个时间在实际中不太可能出现,IsZero 方法提供了一种简单的方式来检测未显式初始化的时间。

如果年份不适用,就像你的情况一样,零是年份的明显值。time.Time 的零值是一个不太可能出现的值。

GRbit:

这不是设计缺陷吗?

不是

Time类型的零值是公元1年1月1日 00:00:00.000000000 UTC。由于这个时间在实际中不太可能出现,IsZero方法提供了一种简单的方式来检测一个时间是否未被显式初始化。


package main

import (
	"fmt"
	"time"
)

const clockLayout = "15:04:05"

var clockZero, _ = time.Parse(clockLayout, "00:00:00")

func clockDuration(clock string) (time.Duration, error) {
	c, err := time.Parse(clockLayout, clock)
	if err != nil {
		return 0, err
	}
	return c.Sub(clockZero), nil
}

func main() {
	d, err := clockDuration("01:02:03")
	fmt.Println(d, err)
}

Go Playground - The Go Programming Language

1h2m3s <nil>


package main

import (
	"fmt"
	"time"
)

func clockDuration(clock string) (time.Duration, error) {
	c, err := time.Parse("15:04:05", clock)
	if err != nil {
		return 0, err
	}
	h, m, s := c.Clock()
	d := time.Duration(h)*time.Hour +
		time.Duration(m)*time.Minute +
		time.Duration(s)*time.Second
	return d, nil
}

func main() {
	d, err := clockDuration("01:02:03")
	fmt.Println(d, err)
}

Go Playground - The Go Programming Language

1h2m3s <nil>

这是一个常见的问题,根源在于 time.Parse 的默认日期处理方式。time.Parse 在解析不包含日期部分的时间字符串时,会使用默认的日期 0000-01-01(公元0年1月1日),而 time.Time{} 的零值日期是 0001-01-01(公元1年1月1日)。两者相差1年,导致 Sub 计算出的持续时间为负值。

这不是设计缺陷,而是 time.Parse 的预期行为。更简洁的解决方案是使用 time.ParseDuration 解析标准格式,或者手动解析 HH:MM:SS 格式并计算持续时间。

以下是两种推荐方案:

方案1:手动解析并计算持续时间(适用于 HH:MM:SS 格式)

func parseDur(s string) (time.Duration, error) {
    var h, m, sec int
    _, err := fmt.Sscanf(s, "%d:%d:%d", &h, &m, &sec)
    if err != nil {
        return 0, err
    }
    return time.Duration(h)*time.Hour + time.Duration(m)*time.Minute + time.Duration(sec)*time.Second, nil
}

方案2:使用 time.Parse 并校正基准时间

func parseDur(s string) (time.Duration, error) {
    t, err := time.Parse("15:04:05", s)
    if err != nil {
        return 0, err
    }
    // 使用与 Parse 相同的基准日期进行计算
    base := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
    return t.Sub(base), nil
}

方案3:如果输入是标准持续时间格式,直接使用 time.ParseDuration

// 注意:这要求输入格式为 "1h2m3s" 而非 "01:02:03"
dur, err := time.ParseDuration("1h30m45s")

第一种方案最直接,避免了日期基准不一致的问题。第二种方案明确了基准时间,使逻辑更清晰。根据你的具体需求选择即可。

回到顶部