github.com/gogf/gf@v1.16.9/os/gtime/gtime.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 // Package gtime provides functionality for measuring and displaying time. 8 // 9 // This package should keep much less dependencies with other packages. 10 package gtime 11 12 import ( 13 "fmt" 14 "github.com/gogf/gf/errors/gcode" 15 "github.com/gogf/gf/errors/gerror" 16 "github.com/gogf/gf/internal/utils" 17 "os" 18 "regexp" 19 "strconv" 20 "strings" 21 "time" 22 23 "github.com/gogf/gf/text/gregex" 24 ) 25 26 const ( 27 // Short writes for common usage durations. 28 29 D = 24 * time.Hour 30 H = time.Hour 31 M = time.Minute 32 S = time.Second 33 MS = time.Millisecond 34 US = time.Microsecond 35 NS = time.Nanosecond 36 37 // Regular expression1(datetime separator supports '-', '/', '.'). 38 // Eg: 39 // "2017-12-14 04:51:34 +0805 LMT", 40 // "2017-12-14 04:51:34 +0805 LMT", 41 // "2006-01-02T15:04:05Z07:00", 42 // "2014-01-17T01:19:15+08:00", 43 // "2018-02-09T20:46:17.897Z", 44 // "2018-02-09 20:46:17.897", 45 // "2018-02-09T20:46:17Z", 46 // "2018-02-09 20:46:17", 47 // "2018/10/31 - 16:38:46" 48 // "2018-02-09", 49 // "2018.02.09", 50 timeRegexPattern1 = `(\d{4}[-/\.]\d{1,2}[-/\.]\d{1,2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` 51 52 // Regular expression2(datetime separator supports '-', '/', '.'). 53 // Eg: 54 // 01-Nov-2018 11:50:28 55 // 01/Nov/2018 11:50:28 56 // 01.Nov.2018 11:50:28 57 // 01.Nov.2018:11:50:28 58 timeRegexPattern2 = `(\d{1,2}[-/\.][A-Za-z]{3,}[-/\.]\d{4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` 59 60 // Regular expression3(time). 61 // Eg: 62 // 11:50:28 63 // 11:50:28.897 64 timeRegexPattern3 = `(\d{2}):(\d{2}):(\d{2})\.{0,1}(\d{0,9})` 65 ) 66 67 var ( 68 // It's more high performance using regular expression 69 // than time.ParseInLocation to parse the datetime string. 70 timeRegex1, _ = regexp.Compile(timeRegexPattern1) 71 timeRegex2, _ = regexp.Compile(timeRegexPattern2) 72 timeRegex3, _ = regexp.Compile(timeRegexPattern3) 73 74 // Month words to arabic numerals mapping. 75 monthMap = map[string]int{ 76 "jan": 1, 77 "feb": 2, 78 "mar": 3, 79 "apr": 4, 80 "may": 5, 81 "jun": 6, 82 "jul": 7, 83 "aug": 8, 84 "sep": 9, 85 "sept": 9, 86 "oct": 10, 87 "nov": 11, 88 "dec": 12, 89 "january": 1, 90 "february": 2, 91 "march": 3, 92 "april": 4, 93 "june": 6, 94 "july": 7, 95 "august": 8, 96 "september": 9, 97 "october": 10, 98 "november": 11, 99 "december": 12, 100 } 101 ) 102 103 // SetTimeZone sets the time zone for current whole process. 104 // The parameter <zone> is an area string specifying corresponding time zone, 105 // eg: Asia/Shanghai. 106 // 107 // This should be called before package "time" import. 108 // Please refer to issue: https://github.com/golang/go/issues/34814 109 func SetTimeZone(zone string) error { 110 location, err := time.LoadLocation(zone) 111 if err != nil { 112 return err 113 } 114 return os.Setenv("TZ", location.String()) 115 } 116 117 // Timestamp retrieves and returns the timestamp in seconds. 118 func Timestamp() int64 { 119 return Now().Timestamp() 120 } 121 122 // TimestampMilli retrieves and returns the timestamp in milliseconds. 123 func TimestampMilli() int64 { 124 return Now().TimestampMilli() 125 } 126 127 // TimestampMicro retrieves and returns the timestamp in microseconds. 128 func TimestampMicro() int64 { 129 return Now().TimestampMicro() 130 } 131 132 // TimestampNano retrieves and returns the timestamp in nanoseconds. 133 func TimestampNano() int64 { 134 return Now().TimestampNano() 135 } 136 137 // TimestampStr is a convenience method which retrieves and returns 138 // the timestamp in seconds as string. 139 func TimestampStr() string { 140 return Now().TimestampStr() 141 } 142 143 // TimestampMilliStr is a convenience method which retrieves and returns 144 // the timestamp in milliseconds as string. 145 func TimestampMilliStr() string { 146 return Now().TimestampMilliStr() 147 } 148 149 // TimestampMicroStr is a convenience method which retrieves and returns 150 // the timestamp in microseconds as string. 151 func TimestampMicroStr() string { 152 return Now().TimestampMicroStr() 153 } 154 155 // TimestampNanoStr is a convenience method which retrieves and returns 156 // the timestamp in nanoseconds as string. 157 func TimestampNanoStr() string { 158 return Now().TimestampNanoStr() 159 } 160 161 // Second returns the timestamp in seconds. 162 // Deprecated, use Timestamp instead. 163 func Second() int64 { 164 return Timestamp() 165 } 166 167 // Millisecond returns the timestamp in milliseconds. 168 // Deprecated, use TimestampMilli instead. 169 func Millisecond() int64 { 170 return TimestampMilli() 171 } 172 173 // Microsecond returns the timestamp in microseconds. 174 // Deprecated, use TimestampMicro instead. 175 func Microsecond() int64 { 176 return TimestampMicro() 177 } 178 179 // Nanosecond returns the timestamp in nanoseconds. 180 // Deprecated, use TimestampNano instead. 181 func Nanosecond() int64 { 182 return TimestampNano() 183 } 184 185 // Date returns current date in string like "2006-01-02". 186 func Date() string { 187 return time.Now().Format("2006-01-02") 188 } 189 190 // Datetime returns current datetime in string like "2006-01-02 15:04:05". 191 func Datetime() string { 192 return time.Now().Format("2006-01-02 15:04:05") 193 } 194 195 // ISO8601 returns current datetime in ISO8601 format like "2006-01-02T15:04:05-07:00". 196 func ISO8601() string { 197 return time.Now().Format("2006-01-02T15:04:05-07:00") 198 } 199 200 // RFC822 returns current datetime in RFC822 format like "Mon, 02 Jan 06 15:04 MST". 201 func RFC822() string { 202 return time.Now().Format("Mon, 02 Jan 06 15:04 MST") 203 } 204 205 // parseDateStr parses the string to year, month and day numbers. 206 func parseDateStr(s string) (year, month, day int) { 207 array := strings.Split(s, "-") 208 if len(array) < 3 { 209 array = strings.Split(s, "/") 210 } 211 if len(array) < 3 { 212 array = strings.Split(s, ".") 213 } 214 // Parsing failed. 215 if len(array) < 3 { 216 return 217 } 218 // Checking the year in head or tail. 219 if utils.IsNumeric(array[1]) { 220 year, _ = strconv.Atoi(array[0]) 221 month, _ = strconv.Atoi(array[1]) 222 day, _ = strconv.Atoi(array[2]) 223 } else { 224 if v, ok := monthMap[strings.ToLower(array[1])]; ok { 225 month = v 226 } else { 227 return 228 } 229 year, _ = strconv.Atoi(array[2]) 230 day, _ = strconv.Atoi(array[0]) 231 } 232 return 233 } 234 235 // StrToTime converts string to *Time object. It also supports timestamp string. 236 // The parameter <format> is unnecessary, which specifies the format for converting like "Y-m-d H:i:s". 237 // If <format> is given, it acts as same as function StrToTimeFormat. 238 // If <format> is not given, it converts string as a "standard" datetime string. 239 // Note that, it fails and returns error if there's no date string in <str>. 240 func StrToTime(str string, format ...string) (*Time, error) { 241 if str == "" { 242 return &Time{wrapper{time.Time{}}}, nil 243 } 244 if len(format) > 0 { 245 return StrToTimeFormat(str, format[0]) 246 } 247 if isTimestampStr(str) { 248 timestamp, _ := strconv.ParseInt(str, 10, 64) 249 return NewFromTimeStamp(timestamp), nil 250 } 251 var ( 252 year, month, day int 253 hour, min, sec, nsec int 254 match []string 255 local = time.Local 256 ) 257 if match = timeRegex1.FindStringSubmatch(str); len(match) > 0 && match[1] != "" { 258 //for k, v := range match { 259 // match[k] = strings.TrimSpace(v) 260 //} 261 year, month, day = parseDateStr(match[1]) 262 } else if match = timeRegex2.FindStringSubmatch(str); len(match) > 0 && match[1] != "" { 263 //for k, v := range match { 264 // match[k] = strings.TrimSpace(v) 265 //} 266 year, month, day = parseDateStr(match[1]) 267 } else if match = timeRegex3.FindStringSubmatch(str); len(match) > 0 && match[1] != "" { 268 //for k, v := range match { 269 // match[k] = strings.TrimSpace(v) 270 //} 271 s := strings.Replace(match[2], ":", "", -1) 272 if len(s) < 6 { 273 s += strings.Repeat("0", 6-len(s)) 274 } 275 hour, _ = strconv.Atoi(match[1]) 276 min, _ = strconv.Atoi(match[2]) 277 sec, _ = strconv.Atoi(match[3]) 278 nsec, _ = strconv.Atoi(match[4]) 279 for i := 0; i < 9-len(match[4]); i++ { 280 nsec *= 10 281 } 282 return NewFromTime(time.Date(0, time.Month(1), 1, hour, min, sec, nsec, local)), nil 283 } else { 284 return nil, gerror.NewCode(gcode.CodeInvalidParameter, "unsupported time format") 285 } 286 287 // Time 288 if len(match[2]) > 0 { 289 s := strings.Replace(match[2], ":", "", -1) 290 if len(s) < 6 { 291 s += strings.Repeat("0", 6-len(s)) 292 } 293 hour, _ = strconv.Atoi(s[0:2]) 294 min, _ = strconv.Atoi(s[2:4]) 295 sec, _ = strconv.Atoi(s[4:6]) 296 } 297 // Nanoseconds, check and perform bit filling 298 if len(match[3]) > 0 { 299 nsec, _ = strconv.Atoi(match[3]) 300 for i := 0; i < 9-len(match[3]); i++ { 301 nsec *= 10 302 } 303 } 304 // If there's zone information in the string, 305 // it then performs time zone conversion, which converts the time zone to UTC. 306 if match[4] != "" && match[6] == "" { 307 match[6] = "000000" 308 } 309 // If there's offset in the string, it then firstly processes the offset. 310 if match[6] != "" { 311 zone := strings.Replace(match[6], ":", "", -1) 312 zone = strings.TrimLeft(zone, "+-") 313 if len(zone) <= 6 { 314 zone += strings.Repeat("0", 6-len(zone)) 315 h, _ := strconv.Atoi(zone[0:2]) 316 m, _ := strconv.Atoi(zone[2:4]) 317 s, _ := strconv.Atoi(zone[4:6]) 318 if h > 24 || m > 59 || s > 59 { 319 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, "invalid zone string: %s", match[6]) 320 } 321 // Comparing the given time zone whether equals to current time zone, 322 // it converts it to UTC if they does not equal. 323 _, localOffset := time.Now().Zone() 324 // Comparing in seconds. 325 if (h*3600 + m*60 + s) != localOffset { 326 local = time.UTC 327 // UTC conversion. 328 operation := match[5] 329 if operation != "+" && operation != "-" { 330 operation = "-" 331 } 332 switch operation { 333 case "+": 334 if h > 0 { 335 hour -= h 336 } 337 if m > 0 { 338 min -= m 339 } 340 if s > 0 { 341 sec -= s 342 } 343 case "-": 344 if h > 0 { 345 hour += h 346 } 347 if m > 0 { 348 min += m 349 } 350 if s > 0 { 351 sec += s 352 } 353 } 354 } 355 } 356 } 357 if month <= 0 || day <= 0 { 358 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, "invalid time string:%s", str) 359 } 360 return NewFromTime(time.Date(year, time.Month(month), day, hour, min, sec, nsec, local)), nil 361 } 362 363 // ConvertZone converts time in string <strTime> from <fromZone> to <toZone>. 364 // The parameter <fromZone> is unnecessary, it is current time zone in default. 365 func ConvertZone(strTime string, toZone string, fromZone ...string) (*Time, error) { 366 t, err := StrToTime(strTime) 367 if err != nil { 368 return nil, err 369 } 370 if len(fromZone) > 0 { 371 if l, err := time.LoadLocation(fromZone[0]); err != nil { 372 return nil, err 373 } else { 374 t.Time = time.Date(t.Year(), time.Month(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l) 375 } 376 } 377 if l, err := time.LoadLocation(toZone); err != nil { 378 return nil, err 379 } else { 380 return t.ToLocation(l), nil 381 } 382 } 383 384 // StrToTimeFormat parses string <str> to *Time object with given format <format>. 385 // The parameter <format> is like "Y-m-d H:i:s". 386 func StrToTimeFormat(str string, format string) (*Time, error) { 387 return StrToTimeLayout(str, formatToStdLayout(format)) 388 } 389 390 // StrToTimeLayout parses string <str> to *Time object with given format <layout>. 391 // The parameter <layout> is in stdlib format like "2006-01-02 15:04:05". 392 func StrToTimeLayout(str string, layout string) (*Time, error) { 393 if t, err := time.ParseInLocation(layout, str, time.Local); err == nil { 394 return NewFromTime(t), nil 395 } else { 396 return nil, err 397 } 398 } 399 400 // ParseTimeFromContent retrieves time information for content string, it then parses and returns it 401 // as *Time object. 402 // It returns the first time information if there're more than one time string in the content. 403 // It only retrieves and parses the time information with given <format> if it's passed. 404 func ParseTimeFromContent(content string, format ...string) *Time { 405 if len(format) > 0 { 406 if match, err := gregex.MatchString(formatToRegexPattern(format[0]), content); err == nil && len(match) > 0 { 407 return NewFromStrFormat(match[0], format[0]) 408 } 409 } else { 410 if match := timeRegex1.FindStringSubmatch(content); len(match) >= 1 { 411 return NewFromStr(strings.Trim(match[0], "./_- \n\r")) 412 } else if match := timeRegex2.FindStringSubmatch(content); len(match) >= 1 { 413 return NewFromStr(strings.Trim(match[0], "./_- \n\r")) 414 } else if match := timeRegex3.FindStringSubmatch(content); len(match) >= 1 { 415 return NewFromStr(strings.Trim(match[0], "./_- \n\r")) 416 } 417 } 418 return nil 419 } 420 421 // ParseDuration parses a duration string. 422 // A duration string is a possibly signed sequence of 423 // decimal numbers, each with optional fraction and a unit suffix, 424 // such as "300ms", "-1.5h", "1d" or "2h45m". 425 // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d". 426 // 427 // Very note that it supports unit "d" more than function time.ParseDuration. 428 func ParseDuration(s string) (time.Duration, error) { 429 if utils.IsNumeric(s) { 430 v, err := strconv.ParseInt(s, 10, 64) 431 if err != nil { 432 return 0, err 433 } 434 return time.Duration(v), nil 435 } 436 match, err := gregex.MatchString(`^([\-\d]+)[dD](.*)$`, s) 437 if err != nil { 438 return 0, err 439 } 440 if len(match) == 3 { 441 v, err := strconv.ParseInt(match[1], 10, 64) 442 if err != nil { 443 return 0, err 444 } 445 return time.ParseDuration(fmt.Sprintf(`%dh%s`, v*24, match[2])) 446 } 447 return time.ParseDuration(s) 448 } 449 450 // FuncCost calculates the cost time of function <f> in nanoseconds. 451 func FuncCost(f func()) int64 { 452 t := TimestampNano() 453 f() 454 return TimestampNano() - t 455 } 456 457 // isTimestampStr checks and returns whether given string a timestamp string. 458 func isTimestampStr(s string) bool { 459 length := len(s) 460 if length == 0 { 461 return false 462 } 463 for i := 0; i < len(s); i++ { 464 if s[i] < '0' || s[i] > '9' { 465 return false 466 } 467 } 468 return true 469 }