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  }