Reputation: 55653
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
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:
Upvotes: 4
Views: 3123
Reputation: 25624
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.
Upvotes: 1
Reputation: 22117
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
Upvotes: 3