gtime.go 14 KB

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