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  }