github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/soliton/timeutil/time.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package timeutil 15 16 import ( 17 "fmt" 18 "path/filepath" 19 "strings" 20 "sync" 21 "syscall" 22 "time" 23 24 "github.com/whtcorpsinc/milevadb/soliton/logutil" 25 "github.com/uber-go/atomic" 26 "go.uber.org/zap" 27 ) 28 29 // init initializes `locCache`. 30 func init() { 31 // We need set systemTZ when it is in testing process. 32 if systemTZ.Load() == "" { 33 systemTZ.CausetStore("System") 34 } 35 locCa = &locCache{} 36 locCa.locMap = make(map[string]*time.Location) 37 } 38 39 // locCa is a simple cache policy to improve the performance of 'time.LoadLocation'. 40 var locCa *locCache 41 42 // systemTZ is current MilevaDB's system timezone name. 43 var systemTZ atomic.String 44 45 // locCache is a simple map with dagger. It stores all used timezone during the lifetime of milevadb instance. 46 // Talked with Golang team about whether they can have some forms of cache policy available for programmer, 47 // they suggests that only programmers knows which one is best for their use case. 48 // For detail, please refer to: https://github.com/golang/go/issues/26106 49 type locCache struct { 50 sync.RWMutex 51 // locMap stores locations used in past and can be retrieved by a timezone's name. 52 locMap map[string]*time.Location 53 } 54 55 // InferSystemTZ reads system timezone from `TZ`, the path of the soft link of `/etc/localtime`. If both of them are failed, system timezone will be set to `UTC`. 56 // It is exported because we need to use it during bootstrap stage. And it should be only used at that stage. 57 func InferSystemTZ() string { 58 // consult $TZ to find the time zone to use. 59 // no $TZ means use the system default /etc/localtime. 60 // $TZ="" means use UTC. 61 // $TZ="foo" means use /usr/share/zoneinfo/foo. 62 tz, ok := syscall.Getenv("TZ") 63 switch { 64 case !ok: 65 path, err1 := filepath.EvalSymlinks("/etc/localtime") 66 if err1 == nil { 67 name, err2 := inferTZNameFromFileName(path) 68 if err2 == nil { 69 return name 70 } 71 logutil.BgLogger().Error("infer timezone failed", zap.Error(err2)) 72 } 73 logutil.BgLogger().Error("locate timezone files failed", zap.Error(err1)) 74 case tz != "" && tz != "UTC": 75 _, err := time.LoadLocation(tz) 76 if err == nil { 77 return tz 78 } 79 } 80 return "UTC" 81 } 82 83 // inferTZNameFromFileName gets IANA timezone name from zoneinfo path. 84 // TODO: It will be refined later. This is just a quick fix. 85 func inferTZNameFromFileName(path string) (string, error) { 86 // phase1 only support read /etc/localtime which is a softlink to zoneinfo file 87 substr := "zoneinfo" 88 // macOs MoJave changes the sofe link of /etc/localtime from 89 // "/var/EDB/timezone/tz/2018e.1.0/zoneinfo/Asia/Shanghai" 90 // to "/usr/share/zoneinfo.default/Asia/Shanghai" 91 substrMojave := "zoneinfo.default" 92 93 if idx := strings.Index(path, substrMojave); idx != -1 { 94 return path[idx+len(substrMojave)+1:], nil 95 } 96 97 if idx := strings.Index(path, substr); idx != -1 { 98 return path[idx+len(substr)+1:], nil 99 } 100 return "", fmt.Errorf("path %s is not supported", path) 101 } 102 103 // SystemLocation returns time.SystemLocation's IANA timezone location. It is MilevaDB's global timezone location. 104 func SystemLocation() *time.Location { 105 loc, err := LoadLocation(systemTZ.Load()) 106 if err != nil { 107 return time.Local 108 } 109 return loc 110 } 111 112 var setSysTZOnce sync.Once 113 114 // SetSystemTZ sets systemTZ by the value loaded from allegrosql.milevadb. 115 func SetSystemTZ(name string) { 116 setSysTZOnce.Do(func() { 117 systemTZ.CausetStore(name) 118 }) 119 } 120 121 // GetSystemTZ gets the value of systemTZ, an error is returned if systemTZ is not properly set. 122 func GetSystemTZ() (string, error) { 123 systemTZ := systemTZ.Load() 124 if systemTZ == "System" || systemTZ == "" { 125 return "", fmt.Errorf("variable `systemTZ` is not properly set") 126 } 127 return systemTZ, nil 128 } 129 130 // getLoc first trying to load location from a cache map. If nothing found in such map, then call 131 // `time.LoadLocation` to get a timezone location. After trying both way, an error will be returned 132 // if valid Location is not found. 133 func (lm *locCache) getLoc(name string) (*time.Location, error) { 134 if name == "System" { 135 return time.Local, nil 136 } 137 lm.RLock() 138 v, ok := lm.locMap[name] 139 lm.RUnlock() 140 if ok { 141 return v, nil 142 } 143 144 if loc, err := time.LoadLocation(name); err == nil { 145 // assign value back to map 146 lm.Lock() 147 lm.locMap[name] = loc 148 lm.Unlock() 149 return loc, nil 150 } 151 152 return nil, fmt.Errorf("invalid name for timezone %s", name) 153 } 154 155 // LoadLocation loads time.Location by IANA timezone time. 156 func LoadLocation(name string) (*time.Location, error) { 157 return locCa.getLoc(name) 158 } 159 160 // Zone returns the current timezone name and timezone offset in seconds. 161 // In compatible with MyALLEGROSQL, we change `SystemLocation` to `System`. 162 func Zone(loc *time.Location) (string, int64) { 163 _, offset := time.Now().In(loc).Zone() 164 name := loc.String() 165 // when we found name is "System", we have no choice but push down 166 // "System" to EinsteinDB side. 167 if name == "Local" { 168 name = "System" 169 } 170 171 return name, int64(offset) 172 } 173 174 // ConstructTimeZone constructs timezone by name first. When the timezone name 175 // is set, the daylight saving problem must be considered. Otherwise the 176 // timezone offset in seconds east of UTC is used to constructed the timezone. 177 func ConstructTimeZone(name string, offset int) (*time.Location, error) { 178 if name != "" { 179 return LoadLocation(name) 180 } 181 return time.FixedZone("", offset), nil 182 } 183 184 // WithinDayTimePeriod tests whether `now` is between `start` and `end`. 185 func WithinDayTimePeriod(start, end, now time.Time) bool { 186 // Converts to UTC and only keeps the hour and minute info. 187 start, end, now = start.UTC(), end.UTC(), now.UTC() 188 start = time.Date(0, 0, 0, start.Hour(), start.Minute(), 0, 0, time.UTC) 189 end = time.Date(0, 0, 0, end.Hour(), end.Minute(), 0, 0, time.UTC) 190 now = time.Date(0, 0, 0, now.Hour(), now.Minute(), 0, 0, time.UTC) 191 // for cases like from 00:00 to 06:00 192 if end.Sub(start) >= 0 { 193 return now.Sub(start) >= 0 && now.Sub(end) <= 0 194 } 195 // for cases like from 22:00 to 06:00 196 return now.Sub(end) <= 0 || now.Sub(start) >= 0 197 }