rand.Seed(time.Now().UTC().UnixNano()) 中的 UTC() 调用是否多余?

Is the UTC() call redundant in rand.Seed(time.Now().UTC().UnixNano())?

网上很多例子都用rand.Seed(time.Now().UTC().UnixNano())来初始化伪随机数生成器种子。

我发现如果我省略 UTC() 调用,它仍然可以正常工作。

Unix(或 UnixNano)时间无论如何是自纪元以来的秒数(或毫秒),即 1970-01-01 00:00:00.000000000 UTC。 Unix 或 UnixNano 时间与时区无关。

以下面的代码为例:

package main

import (
        "fmt"
        "time"
)

func main() {
        t := time.Now()
        fmt.Println(t.UnixNano())
        fmt.Println(t.UTC().UnixNano())
}

所以我的问题是:调用 UTC() 是否有任何目的,或者省略 UTC() 调用而只调用 rand.Seed(time.Now().UnixNano()) 是否安全?

您设置伪随机数生成器种子,使生成的数字难以猜测。

当您查看 UTC() 方法 documentation 时,您会发现它唯一做的就是设置位置(时区)。使用哪个时区生成随机种子是无关紧要的。

重要的是,使用 UnixNano(),该平台实际上 return 计时如此精确。否则,可能会猜到随机种子,这可能允许:random number generator attack

请考虑一种更安全的方法来初始化随机种子生成器:

可以肯定地说在使用 UnixNano()

时可以省略 UTC()

先看time.go中UTC()的代码:1107:

// UTC returns t with the location set to UTC.
func (t Time) UTC() Time {
    t.setLoc(&utcLoc)
    return t
}

它只设置当前时间的位置。

现在,根据 time.go 文件中对 In() 方法的评论,位置信息仅用于“显示目的”。参见 time.go:1119:

// In returns a copy of t representing the same time instant, but
// with the copy's location information set to loc for display
// purposes.
//
// In panics if loc is nil.
func (t Time) In(loc *Location) Time {
    if loc == nil {
        panic("time: missing Location in call to Time.In")
    }
    t.setLoc(loc)
    return t
}

位置仅在必须显示时间时使用:

// abs returns the time t as an absolute time, adjusted by the zone offset.
// It is called when computing a presentation property like Month or Hour.
func (t Time) abs() uint64 {
    l := t.loc
    // Avoid function calls when possible.
    if l == nil || l == &localLoc {
        l = l.get()
    }
    sec := t.unixSec()
    if l != &utcLoc {
        if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
            sec += int64(l.cacheZone.offset)
        } else {
            _, offset, _, _ := l.lookup(sec)
            sec += int64(offset)
        }
    }
    return uint64(sec + (unixToInternal + internalToAbsolute))
}

运行下面代码看看区别。两者都基于相同的 UnixNano,只有小时变化,因为位置仅在打印前应用:

var now = time.Now()
var utc = now.UTC()
fmt.Printf("now UnixNano: %d, Hour: %d, Minute: %d, Second: %d\n", now.UnixNano(), now.Hour(), now.Minute(), now.Second())
fmt.Printf("utc UnixNano: %d, Hour: %d, Minute: %d, Second: %d\n", utc.UnixNano(), utc.Hour(), utc.Minute(), utc.Second())

now UnixNano: 1595836999431598000, Hour: 10, Minute: 3, Second: 19
utc UnixNano: 1595836999431598000, Hour: 8, Minute: 3, Second: 19

Time.UnixNano() returns 源时间的 Unix 时间,自 1970 年 1 月 1 日 UTC 以来经过的纳秒数。它总是在 UTC 区域中解释,源时间的位置无关紧要。 unix 时间与区域无关。它的文档清楚地说明了这一点:

The result does not depend on the location associated with t.

所以你不需要调用Time.UTC(),你会得到相同的结果。

看这个例子:

t1, err := time.Parse("2006-01-02 15:04:05 -0700", "2020-07-27 13:50:00 +0200")
if err != nil {
    panic(err)
}
fmt.Printf("%v\n\t%v\n\t%v\n", t1, t1.UnixNano(), t1.UTC().UnixNano())

t2, err := time.Parse("2006-01-02 15:04:05 -0700", "2020-07-27 13:50:00 +0000")
if err != nil {
    panic(err)
}
fmt.Printf("%v\n\t%v\n\t%v\n", t2, t2.UnixNano(), t2.UTC().UnixNano())

我们解析 2 个输入时间,一次在非 UTC 时区,另一个在 UTC 时区。我们为两者打印 UnixNano(),调用和不调用 UTC()。结果是一样的。

输出(在 Go Playground 上尝试):

2020-07-27 13:50:00 +0200 +0200
    1595850600000000000
    1595850600000000000
2020-07-27 13:50:00 +0000 UTC
    1595857800000000000
    1595857800000000000
  • UTC() 调用是否有任何目的 -
  • 省略 UTC() 是否安全 -