package account import ( "regexp" "strconv" "time" ) // offsetZoneRe matches a fixed UTC offset like "+03:00" or "-05:30" — the form the // Stage 8 profile editor stores (an offset dropdown rather than an IANA name). var offsetZoneRe = regexp.MustCompile(`^([+-])(\d{2}):(\d{2})$`) // parseOffsetZone parses a "±HH:MM" offset into a fixed-offset location, reporting // ok=false when name is not a well-formed offset within ±14:00. func parseOffsetZone(name string) (*time.Location, bool) { m := offsetZoneRe.FindStringSubmatch(name) if m == nil { return nil, false } h, _ := strconv.Atoi(m[2]) min, _ := strconv.Atoi(m[3]) if h > 14 || min > 59 || (h == 14 && min > 0) { return nil, false } secs := h*3600 + min*60 if m[1] == "-" { secs = -secs } return time.FixedZone(name, secs), true } // ResolveZone resolves a stored timezone — a fixed "±HH:MM" offset or an IANA name — // to a *time.Location, falling back to UTC when it is empty or unrecognised, so a // bad profile value never breaks the turn-timeout sweeper or the robot's sleep. func ResolveZone(name string) *time.Location { if name == "" { return time.UTC } if loc, ok := parseOffsetZone(name); ok { return loc } if loc, err := time.LoadLocation(name); err == nil { return loc } return time.UTC } // validZone reports whether name is an acceptable timezone for a profile update — // either a "±HH:MM" offset or a loadable IANA location. func validZone(name string) bool { if _, ok := parseOffsetZone(name); ok { return true } _, err := time.LoadLocation(name) return err == nil }