time.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. // NextOccurrenceUTC takes a time of day (e.g. 9:00am), and returns the next occurrence
  15. // of that time from the current time (in UTC).
  16. func NextOccurrenceUTC(timeOfDay, base time.Time) time.Time {
  17. hour, minute, seconds := timeOfDay.UTC().Clock()
  18. now := base.UTC()
  19. next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, seconds, 0, time.UTC)
  20. if next.Before(now) {
  21. next = next.AddDate(0, 0, 1)
  22. }
  23. return next
  24. }
  25. // ParseFutureTime parses a date/time string to a time.Time. It supports unix timestamps, durations
  26. // and natural language dates
  27. func ParseFutureTime(s string, now time.Time) (time.Time, error) {
  28. s = strings.TrimSpace(s)
  29. t, err := parseUnixTime(s, now)
  30. if err == nil {
  31. return t, nil
  32. }
  33. t, err = parseFromDuration(s, now)
  34. if err == nil {
  35. return t, nil
  36. }
  37. t, err = parseNaturalTime(s, now)
  38. if err == nil {
  39. return t, nil
  40. }
  41. return time.Time{}, errUnparsableTime
  42. }
  43. // ParseDuration is like time.ParseDuration, except that it also understands days (d), which
  44. // translates to 24 hours, e.g. "2d" or "20h".
  45. func ParseDuration(s string) (time.Duration, error) {
  46. d, err := time.ParseDuration(s)
  47. if err == nil {
  48. return d, nil
  49. }
  50. matches := durationStrRegex.FindStringSubmatch(s)
  51. if matches != nil {
  52. number, err := strconv.Atoi(matches[1])
  53. if err != nil {
  54. return 0, errUnparsableTime
  55. }
  56. switch unit := matches[2][0:1]; unit {
  57. case "d":
  58. return time.Duration(number) * 24 * time.Hour, nil
  59. case "h":
  60. return time.Duration(number) * time.Hour, nil
  61. case "m":
  62. return time.Duration(number) * time.Minute, nil
  63. case "s":
  64. return time.Duration(number) * time.Second, nil
  65. default:
  66. return 0, errUnparsableTime
  67. }
  68. }
  69. return 0, errUnparsableTime
  70. }
  71. func parseFromDuration(s string, now time.Time) (time.Time, error) {
  72. d, err := ParseDuration(s)
  73. if err == nil {
  74. return now.Add(d), nil
  75. }
  76. return time.Time{}, errUnparsableTime
  77. }
  78. func parseUnixTime(s string, now time.Time) (time.Time, error) {
  79. t, err := strconv.Atoi(s)
  80. if err != nil {
  81. return time.Time{}, err
  82. } else if int64(t) < now.Unix() {
  83. return time.Time{}, errUnparsableTime
  84. }
  85. return time.Unix(int64(t), 0).UTC(), nil
  86. }
  87. func parseNaturalTime(s string, now time.Time) (time.Time, error) {
  88. r, err := when.EN.Parse(s, now) // returns "nil, nil" if no matches!
  89. if err != nil || r == nil {
  90. return time.Time{}, errUnparsableTime
  91. } else if r.Time.After(now) {
  92. return r.Time, nil
  93. }
  94. // Hack: If the time is parsable, but not in the future,
  95. // simply append "tomorrow, " to it.
  96. r, err = when.EN.Parse("tomorrow, "+s, now) // returns "nil, nil" if no matches!
  97. if err != nil || r == nil {
  98. return time.Time{}, errUnparsableTime
  99. } else if r.Time.After(now) {
  100. return r.Time, nil
  101. }
  102. return time.Time{}, errUnparsableTime
  103. }