github.com/gogf/gf/v2@v2.7.4/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 "context" 14 "fmt" 15 "regexp" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/gogf/gf/v2/errors/gcode" 21 "github.com/gogf/gf/v2/errors/gerror" 22 "github.com/gogf/gf/v2/internal/intlog" 23 "github.com/gogf/gf/v2/internal/utils" 24 "github.com/gogf/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 _, localOffset := time.Now().Zone() 281 zoneOffset := h*3600 + m*60 + s 282 if operation == "-" { 283 zoneOffset = -zoneOffset 284 } 285 // Comparing in seconds. 286 if localOffset != zoneOffset { 287 local = time.FixedZone("", zoneOffset) 288 } 289 } 290 } 291 if month <= 0 || day <= 0 { 292 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid time string "%s"`, str) 293 } 294 return NewFromTime(time.Date(year, time.Month(month), day, hour, min, sec, nsec, local)), nil 295 } 296 297 // ConvertZone converts time in string `strTime` from `fromZone` to `toZone`. 298 // The parameter `fromZone` is unnecessary, it is current time zone in default. 299 func ConvertZone(strTime string, toZone string, fromZone ...string) (*Time, error) { 300 t, err := StrToTime(strTime) 301 if err != nil { 302 return nil, err 303 } 304 var l *time.Location 305 if len(fromZone) > 0 { 306 if l, err = time.LoadLocation(fromZone[0]); err != nil { 307 err = gerror.WrapCodef(gcode.CodeInvalidParameter, err, `time.LoadLocation failed for name "%s"`, fromZone[0]) 308 return nil, err 309 } else { 310 t.Time = time.Date(t.Year(), time.Month(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l) 311 } 312 } 313 if l, err = time.LoadLocation(toZone); err != nil { 314 err = gerror.WrapCodef(gcode.CodeInvalidParameter, err, `time.LoadLocation failed for name "%s"`, toZone) 315 return nil, err 316 } else { 317 return t.ToLocation(l), nil 318 } 319 } 320 321 // StrToTimeFormat parses string `str` to *Time object with given format `format`. 322 // The parameter `format` is like "Y-m-d H:i:s". 323 func StrToTimeFormat(str string, format string) (*Time, error) { 324 return StrToTimeLayout(str, formatToStdLayout(format)) 325 } 326 327 // StrToTimeLayout parses string `str` to *Time object with given format `layout`. 328 // The parameter `layout` is in stdlib format like "2006-01-02 15:04:05". 329 func StrToTimeLayout(str string, layout string) (*Time, error) { 330 if t, err := time.ParseInLocation(layout, str, time.Local); err == nil { 331 return NewFromTime(t), nil 332 } else { 333 return nil, gerror.WrapCodef( 334 gcode.CodeInvalidParameter, err, 335 `time.ParseInLocation failed for layout "%s" and value "%s"`, 336 layout, str, 337 ) 338 } 339 } 340 341 // ParseTimeFromContent retrieves time information for content string, it then parses and returns it 342 // as *Time object. 343 // It returns the first time information if there are more than one time string in the content. 344 // It only retrieves and parses the time information with given first matched `format` if it's passed. 345 func ParseTimeFromContent(content string, format ...string) *Time { 346 var ( 347 err error 348 match []string 349 ) 350 if len(format) > 0 { 351 for _, item := range format { 352 match, err = gregex.MatchString(formatToRegexPattern(item), content) 353 if err != nil { 354 intlog.Errorf(context.TODO(), `%+v`, err) 355 } 356 if len(match) > 0 { 357 return NewFromStrFormat(match[0], item) 358 } 359 } 360 } else { 361 if match = timeRegex1.FindStringSubmatch(content); len(match) >= 1 { 362 return NewFromStr(strings.Trim(match[0], "./_- \n\r")) 363 } else if match = timeRegex2.FindStringSubmatch(content); len(match) >= 1 { 364 return NewFromStr(strings.Trim(match[0], "./_- \n\r")) 365 } else if match = timeRegex3.FindStringSubmatch(content); len(match) >= 1 { 366 return NewFromStr(strings.Trim(match[0], "./_- \n\r")) 367 } 368 } 369 return nil 370 } 371 372 // ParseDuration parses a duration string. 373 // A duration string is a possibly signed sequence of 374 // decimal numbers, each with optional fraction and a unit suffix, 375 // such as "300ms", "-1.5h", "1d" or "2h45m". 376 // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d". 377 // 378 // Very note that it supports unit "d" more than function time.ParseDuration. 379 func ParseDuration(s string) (duration time.Duration, err error) { 380 var ( 381 num int64 382 ) 383 if utils.IsNumeric(s) { 384 num, err = strconv.ParseInt(s, 10, 64) 385 if err != nil { 386 err = gerror.WrapCodef(gcode.CodeInvalidParameter, err, `strconv.ParseInt failed for string "%s"`, s) 387 return 0, err 388 } 389 return time.Duration(num), nil 390 } 391 match, err := gregex.MatchString(`^([\-\d]+)[dD](.*)$`, s) 392 if err != nil { 393 return 0, err 394 } 395 if len(match) == 3 { 396 num, err = strconv.ParseInt(match[1], 10, 64) 397 if err != nil { 398 err = gerror.WrapCodef(gcode.CodeInvalidParameter, err, `strconv.ParseInt failed for string "%s"`, match[1]) 399 return 0, err 400 } 401 s = fmt.Sprintf(`%dh%s`, num*24, match[2]) 402 duration, err = time.ParseDuration(s) 403 if err != nil { 404 err = gerror.WrapCodef(gcode.CodeInvalidParameter, err, `time.ParseDuration failed for string "%s"`, s) 405 } 406 return 407 } 408 duration, err = time.ParseDuration(s) 409 err = gerror.WrapCodef(gcode.CodeInvalidParameter, err, `time.ParseDuration failed for string "%s"`, s) 410 return 411 } 412 413 // FuncCost calculates the cost time of function `f` in nanoseconds. 414 func FuncCost(f func()) time.Duration { 415 t := time.Now() 416 f() 417 return time.Since(t) 418 } 419 420 // isTimestampStr checks and returns whether given string a timestamp string. 421 func isTimestampStr(s string) bool { 422 length := len(s) 423 if length == 0 { 424 return false 425 } 426 for i := 0; i < len(s); i++ { 427 if s[i] < '0' || s[i] > '9' { 428 return false 429 } 430 } 431 return true 432 }