在结构中表示可选 time.Time 的惯用方式

Idiomatic way to represent optional time.Time in a struct

我都读过 Optional Parameters? and Golang pass nil as optional argument to a function?

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

我得到的是:

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 包上定义了一个方法,它允许像这样有一个 shorthand 周期性事件:

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}
    }
}

虽然有效果,但看起来很丑,不是吗?我简洁的一行代码现在分散在几行代码中。

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

time.Time is a struct. Its zero value–although being a valid time value–is like never used. You can utilize the zero value time to signal the missing or undefined time value. There is even a Time.IsZero() 方法告诉您 time.Time 值是否为零值。

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

The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC. As this time is unlikely to come up in practice, the IsZero method gives a simple way of detecting a time that has not been initialized explicitly.

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

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

注意不能像上例那样使用time.Parse() in this composite literal as that returns 2 values (a time.Time and an error). Either use time.Date(),或者先创建时间值(处理错误),直接使用时间值。

判断是否指定了EndsAt

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

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

eachYear.StartsAt = time.Time{}

另请注意,当封送 time.Time 值时,即使它是零值(因为它是一个有效的时间值),即使您使用 omitempty 选项也会发送它.在这些情况下,您必须使用 *time.Time 指针或编写自定义编组器。有关详细信息,请参阅