节前做海外项目的时候,需要根据海外用户的归属地,下发不同的控制策略。这时候遇到两个问题。
我们先来看下为什么会有时区的概念。
引入时区概念是因为地球绕着地轴自转,导致世界不同地区的日出日落时间不同。 为了以一致和标准化的方式记录时间,地球被划分为 24 个时区,每个时区的宽度约为经度 15 度。
在引入时区之前,每个城市都有自己的本地时间,这是由太阳在天空中的位置决定的。 这导致了一个混乱和不一致的系统,火车和电报在不同的地方使用不同的时间,使得城市之间的交通和通讯难以协调。
引入时区标准化计时,更容易协调交通和通信,减少混乱。 今天,时区是我们全球基础设施的重要组成部分,计算机、移动设备和其他系统使用时区以一致和标准化的方式跟踪时间。
中国幅员辽阔,按国际通行时区划分标准可划分为东五区、东六区、东七区、东八区、东九区5个时区。民国时期全国时区被划分为昆仑时区、回藏时区(后改称新藏时区)、陇蜀时区、中原时区、以及长白时区。1949年中华人民共和国成立后,中国大陆全境统一划为东八区(UTC+8),同时以北京时间作为全国唯一的标准时间。
除了中华人民共和国的大陆地区之外,香港与澳门也因地理位置以东八区做为标准时间,分别称作香港时间[1]与澳门标准时间[2]。1949年中华民国政府退守台湾后,全国时区仅剩中原时区,现今改称为国家标准时间、“台湾时间”或“台北时间”。
美国由于东西跨度非常宽,共有六个标准时区:
每个时区都有自己的标准时间,并且在美国的地图上呈现为一个圆形的区域。当地的标准时间是由当地的地理位置决定的,例如,夏威夷标准时间位于夏威夷群岛,而太平洋标准时间位于太平洋沿岸地区。
我们以centos举例。时区信息存放路径为:/usr/share/zoneinfo。其他系统路径信息可以参考golang获取时区列表的方法。
cd /usr/share/zoneinfo
ls -a #列举所有的时区信息。上海时区存在Asina目录下。
我们根据路径信息就能拼接出时区,以shanghai举例。 shanghai时区文件存在 Asia目录中,我们进入Asia中,从文件中找到上海的文件“Shanghai”,这个时候时区值就是 “Asia/Shanghai” 。
本地的时区信息,存放在 /etc/localtime中,其实这个文件就是一个链接,指向了/usr/share/zoneinfo目录下的一个文件。
我们通过下面的命令,可以完成时区的设置信息。
# Set timezone
rm -rf /etc/localtime
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
我们可以借助python timezone模块,直接打印所有的时区即可。
import pytz
timezones = pytz.all_timezones
print(timezones)
import java.util.TimeZone;
public class TimeZoneList {
public static void main(String[] args) {
String[] availableIDs = TimeZone.getAvailableIDs();
for (String id : availableIDs) {
System.out.println(id);
}
}
}
在此示例中,TimeZone.getAvailableIDs 方法用于获取所有可用时区 ID 的数组,然后使用 for-each 循环打印这些 ID。
golang的包并没有python,java齐全,时区列表的获取需要借助系统自带的时区信息。
下面的代码从内容看,是读取服务器的目录信息,然后进行打印。通过路径信息可以看出,在windows,手机环境中就会存在问题。
package main
import (
"fmt"
"io/ioutil"
"strings"
)
var zoneDirs = []string{
// Update path according to your OS
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
}
var zoneDir string
func main() {
for _, zoneDir = range zoneDirs {
ReadFile("")
}
}
func ReadFile(path string) {
files, _ := ioutil.ReadDir(zoneDir + path)
for _, f := range files {
if f.Name() != strings.ToUpper(f.Name()[:1]) + f.Name()[1:] {
continue
}
if f.IsDir() {
ReadFile(path + "/" + f.Name())
} else {
fmt.Println((path + "/" + f.Name())[1:])
}
}
}
通过查询找到了另外一个版本,感觉这个版本应该是比较全的。其实后来想了想,其实手机应用中,应该很少使用golang写程序吧。
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp/syntax"
"runtime"
"strings"
"time"
"unicode/utf8"
// "golang.org/x/sys/windows/registry"
)
// zoneDirs adapted from https://golang.org/src/time/zoneinfo_unix.go
// https://golang.org/doc/install/source#environment
// list of available GOOS as of 10th Feb 2017
// android, darwin, dragonfly, freebsd, linux, netbsd, openbsd, plan9, solaris,windows
var zoneDirs = map[string]string{
"android": "/system/usr/share/zoneinfo/",
"darwin": "/usr/share/zoneinfo/",
"dragonfly": "/usr/share/zoneinfo/",
"freebsd": "/usr/share/zoneinfo/",
"linux": "/usr/share/zoneinfo/",
"netbsd": "/usr/share/zoneinfo/",
"openbsd": "/usr/share/zoneinfo/",
// "plan9":"/adm/timezone/", -- no way to test this platform
"solaris": "/usr/share/lib/zoneinfo/",
"windows": `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\`,
}
var zoneDir string
var timeZones []string
// InSlice ... check if an element is inside a slice
func InSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}
// ReadTZFile ... read timezone file and append into timeZones slice
func ReadTZFile(path string) {
files, _ := ioutil.ReadDir(zoneDir + path)
for _, f := range files {
if f.Name() != strings.ToUpper(f.Name()[:1])+f.Name()[1:] {
continue
}
if f.IsDir() {
ReadTZFile(path + "/" + f.Name())
} else {
tz := (path + "/" + f.Name())[1:]
// check if tz is already in timeZones slice
// append if not
if !InSlice(tz, timeZones) { // need a more efficient method...
// convert string to rune
tzRune, _ := utf8.DecodeRuneInString(tz[:1])
if syntax.IsWordChar(tzRune) { // filter out entry that does not start with A-Za-z such as +VERSION
timeZones = append(timeZones, tz)
}
}
}
}
}
func ListTimeZones() {
if runtime.GOOS == "nacl" || runtime.GOOS == "" {
fmt.Println("Unsupported platform")
os.Exit(0)
}
// detect OS
fmt.Println("Time zones available for : ", runtime.GOOS)
fmt.Println("------------------------")
fmt.Println("Retrieving time zones from : ", zoneDirs[runtime.GOOS])
if runtime.GOOS != "windows" {
for _, zoneDir = range zoneDirs {
ReadTZFile("")
}
} else { // let's handle Windows
// if you're building this on darwin/linux
// chances are you will encounter
// undefined: registry in registry.OpenKey error message
// uncomment below if compiling on Windows platform
//k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
//if err != nil {
// fmt.Println(err)
//}
//defer k.Close()
//names, err := k.ReadSubKeyNames(-1)
//if err != nil {
// fmt.Println(err)
//}
//fmt.Println("Number of timezones : ", len(names))
//for i := 0; i <= len(names)-1; i++ {
// check if tz is already in timeZones slice
// append if not
// if !InSlice(names[i], timeZones) { // need a more efficient method...
// timeZones = append(timeZones, names[i])
// }
//}
// UPDATE : Reading from registry is not reliable
// better to parse output result by "tzutil /g" command
// REMEMBER : There is no time difference between Coordinated Universal Time and Greenwich Mean Time ....
cmd := exec.Command("tzutil", "/l")
data, err := cmd.Output()
if err != nil {
panic(err)
}
fmt.Println("UTC is the same as GMT")
fmt.Println("There is no time difference between Coordinated Universal Time and Greenwich Mean Time ....")
GMTed := bytes.Replace(data, []byte("UTC"), []byte("GMT"), -1)
fmt.Println(string(GMTed))
}
now := time.Now()
for _, v := range timeZones {
if runtime.GOOS != "windows" {
location, err := time.LoadLocation(v)
if err != nil {
fmt.Println(err)
}
// extract the GMT
t := now.In(location)
t1 := fmt.Sprintf("%s", t.Format(time.RFC822Z))
tArray := strings.Fields(t1)
gmtTime := strings.Join(tArray[4:], "")
hours := gmtTime[0:3]
minutes := gmtTime[3:]
gmt := "GMT" + fmt.Sprintf("%s:%s", hours, minutes)
fmt.Println(gmt + " " + v)
} else {
fmt.Println(v)
}
}
fmt.Println("Total timezone ids : ", len(timeZones))
}
func main() {
ListTimeZones()
}
php不是非常的熟悉,只能写些简单的代码。通过代码分析,获取列表貌似只能参考时区官方文档
Go (Golang),可以使用time.FixedZone函数为一个time.Time值设置时区。
package main
import (
"fmt"
"time"
)
func main() {
location, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
return
}
now := time.Now().In(location)
fmt.Println("上海的时间是:", now)
}
在 PHP 中我们使用 date_default_timezone_set 函数为整个脚本设置时区。
$timezone = new DateTimeZone('Asia/Shanghai');
$date = new DateTime('now', $timezone);
echo '现在的时间是: ' . $date->format('Y-m-d H:i:s');
function get_local_time(timeZone) {
let date = new Date();
let formatter = new Intl.DateTimeFormat('zh-CN', {timeZone: timeZone, timeStyle: 'full', dateStyle: 'short'});
return formatter.format(date)
}
get_local_time("Asia/Shanghai");
我们借助第三方js来实现设置时区的功能。
var moment = require('moment-timezone');
var timezone = 'Asia/Shanghai';
var date = moment.tz(new Date(), timezone);
console.log(date.format());
请注意,需要先安装 moment-timezone 库,然后才能在项目中使用它。 可以通过在终端中运行以下命令来执行此操作:
npm install moment-timezone
在 Java 中可以使用 TimeZone 类设置时区。 可以使用 getTimeZone 方法获取特定时区的 TimeZone 对象。在此示例中,使用 getTimeZone 方法获取“Asia/Shanghai”时区的 TimeZone 对象,然后使用 setDefault 方法将其设置为默认时区。
import java.util.TimeZone;
public class TimeZoneExample {
public static void main(String[] args) {
TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
TimeZone.setDefault(tz);
}
}
import datetime
import pytz
timezone = pytz.timezone("America/Los_Angeles")
date = datetime.datetime.now(timezone)
print("Time in Los Angeles:", date)
#include
#include
#include
int main() {
putenv("TZ=America/Los_Angeles");
tzset();
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
printf("The current time in Los Angeles is: %s", asctime(timeinfo));
return 0;
}
在 C# 中,您可以使用 System 命名空间中的 TimeZone 类来设置时区。 TimeZone 类提供有关当前时区的信息,以及在不同时区之间转换的方法。
下面是如何在 C# 中设置时区的示例:
using System;
class Program {
static void Main(string[] args) {
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
Console.WriteLine("The current time in Los Angeles is: " + TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz));
}
}
在此示例中,FindSystemTimeZoneById 方法用于获取表示太平洋时区的 TimeZoneInfo 对象。 然后使用 ConvertTimeFromUtc 方法将当前 UTC 时间转换为太平洋时区的本地时间,并将结果打印到控制台。
您可以通过调用 GetSystemTimeZones 方法找到支持的时区列表,该方法返回所有可用 TimeZoneInfo 对象的列表。 要为特定位置设置时区,您可以使用适当的时区标识符,例如太平洋时区的“Pacific Standard Time”.
#include
#include
#include
int main() {
setenv("TZ", "America/Los_Angeles", 1);
tzset();
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
std::cout << "The current time in Los Angeles is: " << asctime(timeinfo);
return 0;
}
在此示例中,setenv 函数将 TZ 环境变量设置为 America/Los_Angeles,它指定了太平洋时区。 然后调用tzset函数读取TZ环境变量,为程序设置时区信息。
time 函数用于获取当前时间作为 time_t 值,然后将其传递给 localtime 函数,将其转换为包含分解时间信息的 tm 结构,包括本地时区。 asctime 函数用于将细分的时间信息格式化为字符串,然后打印到控制台。
大部分 Docker 镜像都是基于 Alpine,Ubuntu,Debian,CentOS 等镜像制作而成。这些镜像基本上都采用 UTC 时间,默认时区为零时区。如果创建web服务的时候,没有指定时区信息,后面会导致日志和统计信息等异常。下面是我常用的一种实现方式。
#创建自己的网络
docker network create --subnet=172.18.0.0/16 my-net
# 我们使用 TZ(timezone的缩写)指定时区参数,使用--network指定ip。(如果docker中存在多个容器,服务重启的时候,你大概率会发现容器的ip变了。)--restart always docker启动的时候,自动启动容器 --volume指定空间。
docker run -d -e TZ="Asia/Shanghai" --network my-net --ip 172.18.0.2 --publish 6379:6379 --restart always --volume /data/redis:/data/redis --volume /data/redis/redis.conf:/etc/redis/redis.conf --name my-redis redis redis-server /etc/redis/redis.conf
使用Asia/Shanghai时区作为服务器的默认时区。
# Set timezone
rm -rf /etc/localtime
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime