time.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package util
  2. import (
  3. "errors"
  4. "github.com/olebedev/when"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "time"
  9. )
  10. var (
  11. errUnparsableTime = errors.New("unable to parse time")
  12. durationStrRegex = regexp.MustCompile(`(?i)^(\d+)\s*(d|days?|h|hours?|m|mins?|minutes?|s|secs?|seconds?)$`)
  13. )
  14. const (
  15. timestampFormat = "2006-01-02T15:04:05.999Z07:00" // Like RFC3339, but with milliseconds
  16. )
  17. // FormatTime formats a time.Time in a RFC339-like format that includes milliseconds
  18. func FormatTime(t time.Time) string {
  19. return t.Format(timestampFormat)
  20. }
  21. // NextOccurrenceUTC takes a time of day (e.g. 9:00am), and returns the next occurrence
  22. // of that time from the current time (in UTC).
  23. func NextOccurrenceUTC(timeOfDay, base time.Time) time.Time {
  24. hour, minute, seconds := timeOfDay.UTC().Clock()
  25. now := base.UTC()
  26. next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, seconds, 0, time.UTC)
  27. if next.Before(now) {
  28. next = next.AddDate(0, 0, 1)
  29. }
  30. return next
  31. }
  32. // ParseFutureTime parses a date/time string to a time.Time. It supports unix timestamps, durations
  33. // and natural language dates
  34. func ParseFutureTime(s string, now time.Time) (time.Time, error) {
  35. s = strings.TrimSpace(s)
  36. t, err := parseUnixTime(s, now)
  37. if err == nil {
  38. return t, nil
  39. }
  40. t, err = parseFromDuration(s, now)
  41. if err == nil {
  42. return t, nil
  43. }
  44. t, err = parseNaturalTime(s, now)
  45. if err == nil {
  46. return t, nil
  47. }
  48. return time.Time{}, errUnparsableTime
  49. }
  50. // ParseDuration is like time.ParseDuration, except that it also understands days (d), which
  51. // translates to 24 hours, e.g. "2d" or "20h".
  52. func ParseDuration(s string) (time.Duration, error) {
  53. d, err := time.ParseDuration(s)
  54. if err == nil {
  55. return d, nil
  56. }
  57. matches := durationStrRegex.FindStringSubmatch(s)
  58. if matches != nil {
  59. number, err := strconv.Atoi(matches[1])
  60. if err != nil {
  61. return 0, errUnparsableTime
  62. }
  63. switch unit := matches[2][0:1]; unit {
  64. case "d":
  65. return time.Duration(number) * 24 * time.Hour, nil
  66. case "h":
  67. return time.Duration(number) * time.Hour, nil
  68. case "m":
  69. return time.Duration(number) * time.Minute, nil
  70. case "s":
  71. return time.Duration(number) * time.Second, nil
  72. default:
  73. return 0, errUnparsableTime
  74. }
  75. }
  76. return 0, errUnparsableTime
  77. }
  78. func parseFromDuration(s string, now time.Time) (time.Time, error) {
  79. d, err := ParseDuration(s)
  80. if err == nil {
  81. return now.Add(d), nil
  82. }
  83. return time.Time{}, errUnparsableTime
  84. }
  85. func parseUnixTime(s string, now time.Time) (time.Time, error) {
  86. t, err := strconv.Atoi(s)
  87. if err != nil {
  88. return time.Time{}, err
  89. } else if int64(t) < now.Unix() {
  90. return time.Time{}, errUnparsableTime
  91. }
  92. return time.Unix(int64(t), 0).UTC(), nil
  93. }
  94. func parseNaturalTime(s string, now time.Time) (time.Time, error) {
  95. r, err := when.EN.Parse(s, now) // returns "nil, nil" if no matches!
  96. if err != nil || r == nil {
  97. return time.Time{}, errUnparsableTime
  98. } else if r.Time.After(now) {
  99. return r.Time, nil
  100. }
  101. // Hack: If the time is parsable, but not in the future,
  102. // simply append "tomorrow, " to it.
  103. r, err = when.EN.Parse("tomorrow, "+s, now) // returns "nil, nil" if no matches!
  104. if err != nil || r == nil {
  105. return time.Time{}, errUnparsableTime
  106. } else if r.Time.After(now) {
  107. return r.Time, nil
  108. }
  109. return time.Time{}, errUnparsableTime
  110. }