package main import ( "os" "regexp" "strings" ) // tzName is the shape of an IANA zone name the console will accept (spec // ยง6.2 step 1). Anything else is not sent: the console then falls back to // the request's IP, then the home. var tzName = regexp.MustCompile(`^[A-Za-z0-9_+\-]+(/[A-Za-z0-9_+\-]+){0,2}$`) var notAZone = map[string]bool{"localtime": true, "posixrules": true, "Local": true} // localTimezone finds this machine's IANA zone name, portably (no build // tags; the 20-cross gate builds it for darwin, windows and linux-arm64): // $TZ, else the /etc/localtime symlink target, else /etc/timezone, else "". // Windows has none of these, so only a valid $TZ is ever sent there. func localTimezone(getenv func(string) string, readlink func(string) (string, error), readFile func(string) ([]byte, error)) string { if tz := clean(strings.TrimPrefix(strings.TrimSpace(getenv("TZ")), ":")); tz != "" { return tz } if target, err := readlink("/etc/localtime"); err == nil { if tz := clean(target); tz != "" { return tz } } if b, err := readFile("/etc/timezone"); err == nil { line, _, _ := strings.Cut(string(b), "\n") return clean(line) } return "" } func clean(s string) string { s = strings.TrimSpace(s) if _, after, ok := strings.Cut(s, "zoneinfo/"); ok { s = after } if s == "" || strings.HasPrefix(s, "/") || notAZone[s] || len(s) > 64 || !tzName.MatchString(s) { return "" } return s } func detectTimezone() string { return localTimezone(os.Getenv, os.Readlink, os.ReadFile) }