01-时区

如果应用程序里不显式地设置时区,那么golang的time包里的函数统一用的是默认的UTC时区。

0.1. 系统时区

在Linux中有许多时间管理工具,如datetimedatectl来获取当前系统的时区并且和远端NTP服务器进行同步来启动自动的更精确的系统时间处理。

0.1.1. timedatectl

timedatectl命令是基于RHEL/CentOS7Fedora 21+发行版的新实用程序,它是systemd系统和服务管理器的一部分,是在基于sysvinit守护程序的Linux发行版中使用的旧传统date命令的替代。

timedatectl --help
timedatectl [OPTIONS...] COMMAND ...

Query or change system time and date settings.

  -h --help                Show this help message
     --version             Show package version
     --no-pager            Do not pipe output into a pager
     --no-ask-password     Do not prompt for password
  -H --host=[USER@]HOST    Operate on remote host
  -M --machine=CONTAINER   Operate on local container
     --adjust-system-clock Adjust system clock when changing local RTC mode

Commands:
  status                   显示当前时间设置
  set-time TIME            设置系统时间
  set-timezone ZONE        设置系统沙丘
  list-timezones           显示已知的时区
  set-local-rtc BOOL       控制RTC是否在当地时间(local time,1=true/0=false)
  set-ntp BOOL             启用或禁用网络时间同步(true/false)

要在系统上显示当前时间和日期,请从命令行使用timedatectl命令,如下所示:

0.1.1.1. 显示当前设置

timedatectl status

                      Local time: 一 2020-03-09 15:03:56 CST
                  Universal time: 一 2020-03-09 07:03:56 UTC
                        RTC time: 一 2020-03-09 07:03:56
                       Time zone: Asia/Shanghai (CST, +0800)
       System clock synchronized: yes
systemd-timesyncd.service active: yes
                 RTC in local TZ: no

# RTC time 表示的是硬件时钟时间

始终通过系统上设置的时区来管理Linux系统上的时间,上面的命令输出中,Time zone就是当前系统的时区。

0.1.1.2. 设置时区

设置时区使用如下命令:

timedatectl set-timezone "Asia/Shanghai"

# 始终建议使用并设置coordinated universal time,即UTC。

0.1.1.3. 设置时间

# 仅设置时间,可以按照HH:MM:SS(时分秒)的时间格式
timedatectl set-time 20:21:22

# 仅设置日期,可以按照YY:MM:DD(年月日)中的日期格式
timedatectl set-time 20200229

# 同时设置时间和日期
timedatectl set-time '2020-03-04 13:14:15'

0.1.2. Debian及其发行版

cat /etc/timezone

Asia/Shanghai

直接修改这个文件,即可生效。

0.1.3. REHL/CentOS7/Fedora

这几个操作系统的 /etc/localtime是目录/usr/share/zoneinfo下文件的符号链接。

sudo ln  -sf /usr/share/zoneinfo/zoneinfo /etc/localtime

# 参数说明
-s, --symbolic              创建符号链接而不是硬链接
-f, --force                 强行删除任何已存在的目标文件

# 例如要将时区修改为东八区
sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

0.2. Golang代码中设置时区

方法一

可以使用os.Setenv("TZ","Asia/Shanghai")从应用程序内部实现,重要的是必须先调用此函数,然后其他任何程序包才能使用time程序包中的任何内容。要保证生效,可以创建一个除了设置时区外什么都不做的包。

package tzinit

import (
    "os"
)

func init() {
    os.Setenv("TZ", "Asia/Shanghai")
}

首先将这个tzinit包导入main包中,如下所示:

package main

import _ "path/to/tzinit"

// Your other, "regular" imports:

import (
    "fmt"
    "os"
    "time"
    ...
)

这样的话,设置TZ环境变量的操作将在任何其他程序包可以访问时间程序包之前进行。

请注意,只是为tzinit使用了单独的导入声明,其原因是因为许多IDE会按字母顺序重新排列导入,单独导入声明将确保导入tzinit包是第一个导入的。

The Spec: Package initialization”说明了程序包初始化的要求和规则,并且未指定导入的处理顺序(唯一可以保证的是,所有引用的程序包将在使用前递归初始化)。这意味着,尽管当前的编译器按列出的顺序处理它们,但不能100%依靠它。

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

方法二

2020-8-11,golang 1.15版本发布,其中增加了"time/tzdata"包。

tzdata包提供了时区数据库的嵌入式副本。将此包导入到程序中的任意位置后,如果time包在系统上找不到tzdata文件,它将使用此嵌入式信息。

导入这个包将会使程序增加约800KB。通常应该在main包而不是其他库包(通常不应该决定是否在程序中包括时区数据库)中导入。

也可以在编译时使用-tags timetzdata来自动导入这个包。

0.3. docker容器中设置时区

在Linux系统下Go运行时会从多个来源读取时区信息,在$GOROOT/src/time/zoneinfo.unix文件里能够找到Go运行时是从哪些地方读取时区信息的。

// Many systems use /usr/share/zoneinfo, Solaris 2 has
// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ.
var zoneSources = []string{
   "/usr/share/zoneinfo/",
   "/usr/share/lib/zoneinfo/",
   "/usr/lib/locale/TZ/",
   runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}

0.3.1. 在Dockerfile中设置环境变量

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 不生效的时候,再使用这个命令,在非交互的方式下重新配置一个已经按照的包
RUN dpkg-reconfigure -f noninteractive tzdata

不同Linux发行版的时区设置略有不同,上面的设置可以在大部分发行版最为基础镜像的容器中正常运行。

当使用Alpine作为基础镜像时,需要先安装tzdate工具,同时删除不要的文件来精简镜像:

tzdata软件包,全称time zone and daylight-saving time(DST) data,供各个Linux系统安装以读取Time Zone Database中数据。

# 墙内可以使用镜像地址加快下载安装的进度
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories

RUN apk add --no-cache tzdata && rm -rf /var/cache/apk/*
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

0.3.2. scratch基础镜像

scratch镜像的容器里中,上面列的几个目录都没有,scratch镜像里并不包含这些时区文件。解决办法就是从build阶段的镜像里拷贝时区文件到最终的应用镜像。

FROM golang:alpine as build
RUN apk --no-cache add tzdata
WORKDIR /app
ADD . /app
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp

FROM scratch as final
COPY --from=build /app/myapp .
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
ENV TZ=Asia/Shanghai

CMD ["/myapp"]

0.3.3. 挂载宿主机时区文件

docker run -v /etc/timezone:/etc/timezone:ro [image-name]
volumes:
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"

但是,挂载的这个方式总感觉不是很好,容器还是要尽量少的和宿主机有交互,减少攻击面

上次修改: 12 April 2020