【问题标题】:Setting timezone globally in golang在 golang 中全局设置时区
【发布时间】:2019-01-25 10:32:20
【问题描述】:

我正在尝试为我的应用程序修改 golang 时区

我看过 time 包,初始化时区发生在

time/zoneinfo_unix.go @initLocal

该函数只是尝试读取环境变量TZ,如果它有效则加载它

如果不是,则回退到/etc/localtime,如果无效,则回退到UTC


到目前为止我已经尝试过什么

1- 工作正常 - 但我不想使用其中任何一种方法 - :

  • 在我的 docker 文件中,我将一个 ENV 传递给容器,TZ = Africa/Cairo
  • 进入容器 bash,运行 $ export TZ = Africa/Cairo

2-没用

  • 在我的应用程序初始化中(应用程序初始化在一个单独的包中,正在导入到主程序中),我使用 os.SetEnv("TZ", "Africa/Cairo")

当我简化 main 并使用 os.SetEnv("TZ", "Africa/Cairo") 而不导入“os - time”以外的任何其他包时,它会按预期工作


关于如何使第二种方法起作用的任何想法?

Docker 镜像:golang:1.11.2

【问题讨论】:

  • main() 中首先调用os.Setenv() 似乎可行,请参见此处:play.golang.org/p/pckEDxd6p8F
  • 感谢您的回复,我试过了,它仅在您提供的示例非常简单时才有效,但在我的情况下,主要不是那么简单,它使用外部包应用初始化
  • 然后把它放在主 pkg 中的init() func 中?并且只是添加我的两个 p - 在容器中设置 env var 对我来说似乎非常好......
  • @Havelock 如果main 包导入另一个使用time 包的包,这将不起作用,这将在主init() 可以运行之前执行。
  • 既然您指的是 docker,我猜这是一项服务而不是 CLI 工具。如果您关心默认时区是什么,请考虑存在缺陷并重新设计您的设计,使其与系统 TZ 无关。服务应该接受任何时区的输入,以 UTC 处理它们,以 UTC 存储它们,并在任何时区呈现它们。系统时区应该完全不相关。

标签: go time


【解决方案1】:

您可以使用 os.Setenv("TZ", "Africa/Cairo") 从应用程序内部实现您想要的,重要的是您必须在任何其他包使用 time 包中的任何内容之前调用它。

如何保证?创建一个除了设置时区之外什么都不做的包(稍后您可以向其中添加其他内容,但对于我们的示例来说就足够了)。

像这样:

package tzinit

import (
    "os"
)

func init() {
    os.Setenv("TZ", "Africa/Cairo")
}

将这个tzinitfirst 导入到你的main 包中,如下所示:

package main

import _ "path/to/tzinit"

// Your other, "regular" imports:
import (
    "fmt"
    "os"
    "time"
    ...
)

因此设置 TZ 环境变量将在任何其他包可以访问 time 包之前发生。

请注意,我只为tzinit 使用了单独的import 声明,原因是许多代码编辑器/IDE 会按字母顺序重新排列您的导入,这将确保导入tzinit 将仍然是第一个导入.

一句警告。

Spec: Package initialization 说明了初始化包的要求和规则,并且没有指定处理导入的顺序(唯一保证的是所有引用的包都将在可以使用之前递归初始化)。这意味着尽管当前的编译器按所列方式处理它们,但您不能 100% 依赖它。即使是main 包也存在多个源文件的问题,以不同的顺序将它们提供给编译器也可能会更改初始化顺序。规范将此作为“建议”:

为确保可重现的初始化行为,鼓励构建系统将属于同一包的多个文件按照词法文件名顺序呈现给编译器。

所以为了安全起见,最好在启动 Go 应用程序之前设置 TZ 环境变量。

【讨论】:

  • 非常感谢您的回答
  • 您不能依赖导入语句的顺序。导入顺序是针对整个包的,而不是单个文件中的顺序。 (实际上它们目前是根据包文件的解析顺序排列的,但我认为没有指定顺序)
  • @JimB 是的,想将其添加为警告,但没有时间。现在就做。
  • Go 应用程序无法正确获取时区信息时遇到问题,结果发现缺少“tzdata”包。添加该包并设置 TZ 变量可以帮助解决问题。
【解决方案2】:

在这里为偶然发现此页面的人添加我的分析器。 time包中有一个全局变量,在main.go这样使用它

package main

import "time"

func main() {
    loc, err := time.LoadLocation("Africa/Cairo")
    // handle err
    time.Local = loc // -> this is setting the global timezone
}

您的系统必须安装时区数据库。 在 docker 中,您必须 apt get/apk add tzdata。但是如果你使用的是go1.15,你也可以嵌入时区数据库而不需要在系统上安装tzdata

package main

import (
    "time"
    _ "time/tzdata"
)

func main() {
    loc, err := time.LoadLocation("Africa/Cairo")
    // handle err
    time.Local = loc // -> this is setting the global timezone
}

【讨论】:

  • 如果您更喜欢以编程方式更改时区设置而不是更改环境变量,建议您这样做。
  • 但这可能会导致一些 time.* 函数的数据竞争(尽管我在 init() 函数中更改了位置,但我有时会在测试中得到它们)。
  • 是的,这可能会导致数据竞争 --> github.com/golang/go/issues/34814#issuecomment-540596197
【解决方案3】:

我可能迟到了,但在全局环境中设置时区并不是一种可靠的方法。 它应该在变量或结构中全局设置。以下是在变量中设置的时区示例。也在Go Playground

package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    if err := setTimezone("America/Los_Angeles"); err != nil {
        log.Fatal(err) // most likely timezone not loaded in Docker OS
    }
    t := getTime(time.Now())
    fmt.Println(t)
}

var loc *time.Location

func setTimezone(tz string) error {
    location, err := time.LoadLocation(tz)
    if err != nil {
        return err
    }
    loc = location
    return nil
}

func getTime(t time.Time) time.Time {
    return t.In(loc)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-15
    相关资源
    最近更新 更多