如果应用程序里不显式地设置时区,那么golang的time
包里的函数统一用的是默认的UTC
时区。
在Linux中有许多时间管理工具,如date
、timedatectl
来获取当前系统的时区并且和远端NTP服务器进行同步来启动自动的更精确的系统时间处理。
timedatectl
命令是基于RHEL/CentOS7
和Fedora 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命令,如下所示:
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就是当前系统的时区。
设置时区使用如下命令:
timedatectl set-timezone "Asia/Shanghai"
# 始终建议使用并设置coordinated universal time,即UTC。
# 仅设置时间,可以按照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'
cat /etc/timezone
Asia/Shanghai
直接修改这个文件,即可生效。
这几个操作系统的 /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
可以使用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
来自动导入这个包。
在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",
}
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
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"]
docker run -v /etc/timezone:/etc/timezone:ro [image-name]
volumes:
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
但是,挂载的这个方式总感觉不是很好,容器还是要尽量少的和宿主机有交互,减少攻击面。