首页 > 解决方案 > 在结构中表示可选 time.Time 的惯用方式

问题描述

我已经阅读了两个可选参数?Golang 将 nil 作为可选参数传递给函数?

并且仍然想知道我的情况是否更具体。

我所拥有的是:

type Periodical struct {
    Interval *interval.Interval
    StartsAt time.Time
    EndsAt   time.Time
}

表示具有开始日期并且可能有也可能没有结束日期的定期事件(定期事件运行的时间不确定)。

eachYear := Periodical{
    interval.Years(1),
    StartsAt: time.Parse("2 Jan 2006", "1 Jan 1970")}

会扔

periodical/periodical.go:31:39: cannot use nil as type time.Time in field value

这是可以理解的, - 我没有指定EndsAt time.Time

但那我真的在那里做什么呢?

我是否被迫有一个特殊的标志来忽视EndsAt这样的事情?

type Periodical struct {
    Interval     *interval.Interval
    StartsAt     time.Time
    EndsAt       time.Time
    isIndefinite bool       // This looks ugly already
}

然后如果我想要Yearly/Anually我会做类似的事情

eachYear := Periodical{
    interval.Years(1),
    time.Parse("2 Jan 2006", "1 Jan 1970"),
    time.Parse("2 Jan 2006", "1 Jan 1970"),
    isIndefinite: true}

虽然,我可以在业务逻辑中解释这个标志,但是这个EndsAt设置为相同(或任何其他)日期看起来有点乏味。

我还在periodical包上定义了一个方法,它允许有一个速记的定期事件,如下所示:

func Monthly(s, e time.Time) Periodical {
    return Periodical{StartsAt: s, EndsAt: e, Interval: interval.Months(1)}
}

我该怎么做才能省略end(第二个参数)?我是否被迫为此使用单独的方法,或者做一些看起来有点时髦且缺乏可读性的事情:

func Monthly(s time.Time, end ...time.Time) Periodical {
    if len(end) == 1  {
        return Periodical{
            StartsAt: s,
            EndsAt: end[0],
            Interval: interval.Months(1),
            isIndefinite: false}
    } else if len(end) > 1 {
        panic("Multiple end dates are not allowed, don't know what to do with those")
    } else {
        return Periodical{
            StartsAt: s,
            EndsAt: time.Now(),
            Interval: interval.Months(1),
            isIndefinite: true}
    }
}

虽然它成功了,但它看起来很丑,不是吗?我简洁的单行代码现在分散在几行代码中。

所以,这就是为什么我想知道,实现我想要做的事情的惯用方式是什么?

标签: gotimestructoptional-parameters

解决方案


time.Time是一个结构。它的零值——尽管是一个有效的时间值——就像从未使用过。您可以利用零值时间来表示丢失或未定义的时间值。甚至还有一种Time.IsZero()方法可以告诉您一个time.Time值是否为零值。

请注意,零值时间不是像其他一些语言那样的 1970 年 1 月 1 日,而是 1 年 1 月 1 日,正如您在Go Playground上看到的那样。这也记录在time.Time

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

此外,在创建Periodical值时,使用键控复合文字,您可以省略不想设置的字段,从而将它们保持为零值:

eachYear := Periodical{
    Interval: interval.Years(1),
    StartsAt: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
}

请注意,您不能time.Parse()在此复合文字中使用它,因为它返回 2 个值(atime.Time和 an error)。要么time.Date()像上面的例子那样使用,要么先创建时间值(处理错误),然后只使用时间值。

判断是否EndsAt指定:

if eachYear.EndsAt.IsZero() {
    fmt.Println("EndsAt is missing")
}

如果您需要将已经设置的(非零)时间值归零,您可以使用time.Time{}复合文字:

eachYear.StartsAt = time.Time{}

另请注意,尽管在编组一个time.Time值时,即使它是零值(因为它是一个有效的时间值),即使您使用该omitempty选项,它也会被发送。在这些情况下,您必须使用*time.Time指针或编写自定义封送器。详情见Golang JSON omitempty With time.Time Field


推荐阅读