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