如何获取本地时区的完整名称?

huangapple go评论69阅读模式
英文:

How to get the full name of the local timezone?

问题

例如,服务器的时区是"Europe/Madrid",我执行以下操作:

now := time.Now()
fmt.Printf("%v", now.Location().String()) // 输出"Local"

zone, _ := now.Zone()
fmt.Printf("%v", zone) // 输出"CEST"

但我想要"Europe/Madrid"。

时区列表:https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

编辑

提供了适用于所有平台(Windows、Linux、Mac)的完整答案:https://github.com/thlib/go-timezone-local

所有的功劳归功于colm.anseoMrFuppes,他们提供了以下答案:

英文:

For example the server timezone is "Europe/Madrid" and I do this:

now := time.Now()
fmt.Printf("%v", now.Location().String()) // prints "Local"

zone, _ := now.Zone()
fmt.Printf("%v", zone) // prints "CEST"

But I want "Europe/Madrid"

Timezones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

Edit

Full cross platform (windows, linux, mac) answer provided here: https://github.com/thlib/go-timezone-local

All credit goes to colm.anseo and MrFuppes for providing the following answers:

答案1

得分: 2

IANA时区在大多数操作系统上都是可用的(*)。Go标准库也包含了它:

runtime.GOROOT() + "/lib/time/zoneinfo.zip"

选择时区的方式和是否在任何地方记录该名称由操作系统决定:

ls -al /etc/localtime

# MacOS
/etc/localtime -> /var/db/timezone/zoneinfo/America/New_York

# Linux
/etc/localtime -> ../usr/share/zoneinfo/America/New_York

因此,在上述情况下,可以推断出名称。

注意:Go默认使用/etc/localtime作为本地操作系统时间(timezone.go)- 但可以通过TZ环境变量进行覆盖。

因此,可以通过符号链接目标路径推断出本地操作系统时区的名称,如下所示:

// tz.go

//go:build !windows
// +build !windows

const localZoneFile = "/etc/localtime" // 符号链接文件 - 由操作系统设置
var ValidateLocationName = true        // 设置为false以禁用time.LoadLocation验证检查

func LocalTZLocationName() (name string, err error) {

    var ok bool
    if name, ok = os.LookupEnv("TZ"); ok {
	    if name == "" { // 如果为空白 - Go将其视为UTC
		    return "UTC", nil
	    }
	    if ValidateLocationName {
		    _, err = time.LoadLocation(name) // 可选的名称验证
	    }
	    return
    }

	fi, err := os.Lstat(localZoneFile)
	if err != nil {
		err = fmt.Errorf("failed to stat %q: %w", localZoneFile, err)
		return
	}

	if (fi.Mode() & os.ModeSymlink) == 0 {
		err = fmt.Errorf("%q不是符号链接 - 无法推断名称", localZoneFile)
		return
	}

	p, err := os.Readlink(localZoneFile)
	if err != nil {
		return
	}

	name, err = inferFromPath(p) // 处理1和2部分的时区名称
	return
}

func inferFromPath(p string) (name string, err error) {

	dir, lname := path.Split(p)

	if len(dir) == 0 || len(lname) == 0 {
		err = fmt.Errorf("无法从路径推断时区名称:%q", p)
		return
	}

	_, fname := path.Split(dir[:len(dir)-1])

	if fname == "zoneinfo" {
		name = lname // 例如:/usr/share/zoneinfo/Japan
	} else {
		name = fname + string(os.PathSeparator) + lname // 例如:/usr/share/zoneinfo/Asia/Tokyo
	}

    if ValidateLocationName {
		_, err = time.LoadLocation(name) // 可选的名称验证
	}

	return
}

// tz_windows.go
//go:build windows
// +build windows

func LocalTZLocationName() (string, error) {
    return "", fmt.Errorf("无法在Windows上推断本地时区名称")
}

(*)根据Go源代码(zoneinfo_windows.go),对于Windows,时区信息是从Go标准库加载的,原因是:

// BUG(brainman,rsc): On Windows, the operating system does not provide complete
// time zone information.
// The implementation assumes that this year's rules for daylight savings
// time apply to all previous and future years as well.

Windows注册表键:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation

存储了本地时区,但似乎没有存储完整的位置名称:

TimeZoneKeyName=Eastern Standard Time
英文:

IANA timezones are available on most OSes (*). The Go standard library ships it as well:

runtime.GOROOT() + "/lib/time/zoneinfo.zip"

The selection of a timezone by name and whether this name is recorded anywhere is left up to the OS:

ls -al /etc/localtime

# MacOS
/etc/localtime -> /var/db/timezone/zoneinfo/America/New_York

# Linux
/etc/localtime -> ../usr/share/zoneinfo/America/New_York

so in the above cases the name may be inferred.

Note: Go uses /etc/localtime by default for the local OS time (timezone.go) - but this can be overridden with the TZ environment variable.

So one could infer the name of the local OS timezone, via the symlink target path like so:

// tz.go

//go:build !windows
// +build !windows

const localZoneFile = "/etc/localtime" // symlinked file - set by OS
var ValidateLocationName = true        // set to false to disable time.LoadLocation validation check

func LocalTZLocationName() (name string, err error) {

    var ok bool
    if name, ok = os.LookupEnv("TZ"); ok {
	    if name == "" { // if blank - Go treats this as UTC
		    return "UTC", nil
	    }
	    if ValidateLocationName {
		    _, err = time.LoadLocation(name) // optional validation of name
	    }
	    return
    }

	fi, err := os.Lstat(localZoneFile)
	if err != nil {
		err = fmt.Errorf("failed to stat %q: %w", localZoneFile, err)
		return
	}

	if (fi.Mode() & os.ModeSymlink) == 0 {
		err = fmt.Errorf("%q is not a symlink - cannot infer name", localZoneFile)
		return
	}

	p, err := os.Readlink(localZoneFile)
	if err != nil {
		return
	}

	name, err = inferFromPath(p) // handles 1 & 2 part zone names
	return
}

func inferFromPath(p string) (name string, err error) {

	dir, lname := path.Split(p)

	if len(dir) == 0 || len(lname) == 0 {
		err = fmt.Errorf("cannot infer timezone name from path: %q", p)
		return
	}

	_, fname := path.Split(dir[:len(dir)-1])

	if fname == "zoneinfo" {
		name = lname // e.g. /usr/share/zoneinfo/Japan
	} else {
		name = fname + string(os.PathSeparator) + lname // e.g. /usr/share/zoneinfo/Asia/Tokyo
	}

    if ValidateLocationName {
		_, err = time.LoadLocation(name) // optional validation of name
	}

	return
}

// tz_windows.go
//go:build windows
// +build windows

func LocalTZLocationName() (string, error) {
    return "", fmt.Errorf("local timezone name inference not available on Windows")
}

(*) for Windows according to the Go source (zoneinfo_windows.go), zone info is loaded from the Go standard library, due to:

// BUG(brainman,rsc): On Windows, the operating system does not provide complete
// time zone information.
// The implementation assumes that this year's rules for daylight savings
// time apply to all previous and future years as well.

the Window's registry key:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation

stores the local timezone - but does not appear to store the long form location name:

TimeZoneKeyName=Eastern Standard Time

答案2

得分: 1

在Windows上,该过程也不是过于复杂的。如在stackoverflow上提到的@colm.anseo,你可以读取相应的注册表键来获取Windows使用的时区名称,然后将其映射到适当的IANA时区名称。

从注册表中读取tz:

package main

import (
	"log"

	"golang.org/x/sys/windows/registry"
)

var winTZtoIANA = map[string]string{
    // 只包括我的时区;可以扩展...
	"W. Europe Standard Time": "Europe/Berlin",
}

func main() {

	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\TimeZoneInformation`, registry.QUERY_VALUE)
	if err != nil {
		log.Fatal(err)
	}
	defer k.Close()

	winTZname, _, err := k.GetStringValue("TimeZoneKeyName")
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Windows本地时区:%v\n", winTZname)

	if IANATZname, ok := winTZtoIANA[winTZname]; ok {
		log.Printf("IANA时区名称:%v\n", IANATZname)
	} else {
		log.Fatalf("无法找到%v的IANA时区名称", winTZname)
	}

}

或者,你可以读取tzutil /g的输出:

cmd := exec.Command("tzutil", "/g")
winTZname, err := cmd.Output() // 注意:winTZname是[]byte类型,需要转换为字符串
if err != nil {
	// 处理错误
}

你可以在这里找到一个全面的映射。它基于Lennart Regebro的tzlocal包,该包解决了Python中的给定问题。

英文:

On Windows, the procedure also isn't overly complex. As mentioned by @colm.anseo, you can read the respective registry key to get the time zone name that Windows uses. Then map that to the appropriate IANA time zone name.

read tz from registry

package main

import (
	"log"

	"golang.org/x/sys/windows/registry"
)

var winTZtoIANA = map[string]string{
    // just includes my time zone; to be extended...
	"W. Europe Standard Time": "Europe/Berlin",
}

func main() {

	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\TimeZoneInformation`, registry.QUERY_VALUE)
	if err != nil {
		log.Fatal(err)
	}
	defer k.Close()

	winTZname, _, err := k.GetStringValue("TimeZoneKeyName")
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("local time zone in Windows: %v\n", winTZname)

	if IANATZname, ok := winTZtoIANA[winTZname]; ok {
		log.Printf("local time zone IANA name: %v\n", IANATZname)
	} else {
		log.Fatalf("could not find IANA tz name for %v", winTZname)
	}

}

Alternatively, you can read the output of tzutil /g:

cmd := exec.Command("tzutil", "/g")
winTZname, err := cmd.Output() // note: winTZname is []byte, need to convert to string
if err != nil {
	// handle error
}

You can find a comprehensive mapping here. It's based on Lennart Regebro's tzlocal package that solves the given question in Python.

huangapple
  • 本文由 发表于 2021年8月26日 20:44:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/68938751.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定