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  }