time.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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. func parseFromDuration(s string, now time.Time) (time.Time, error) {
  44. d, err := parseDuration(s)
  45. if err == nil {
  46. return now.Add(d), nil
  47. }
  48. return time.Time{}, errUnparsableTime
  49. }
  50. func parseDuration(s string) (time.Duration, error) {
  51. d, err := time.ParseDuration(s)
  52. if err == nil {
  53. return d, nil
  54. }
  55. matches := durationStrRegex.FindStringSubmatch(s)
  56. if matches != nil {
  57. number, err := strconv.Atoi(matches[1])
  58. if err != nil {
  59. return 0, errUnparsableTime
  60. }
  61. switch unit := matches[2][0:1]; unit {
  62. case "d":
  63. return time.Duration(number) * 24 * time.Hour, nil
  64. case "h":
  65. return time.Duration(number) * time.Hour, nil
  66. case "m":
  67. return time.Duration(number) * time.Minute, nil
  68. case "s":
  69. return time.Duration(number) * time.Second, nil
  70. default:
  71. return 0, errUnparsableTime
  72. }
  73. }
  74. return 0, errUnparsableTime
  75. }
  76. func parseUnixTime(s string, now time.Time) (time.Time, error) {
  77. t, err := strconv.Atoi(s)
  78. if err != nil {
  79. return time.Time{}, err
  80. } else if int64(t) < now.Unix() {
  81. return time.Time{}, errUnparsableTime
  82. }
  83. return time.Unix(int64(t), 0).UTC(), nil
  84. }
  85. func parseNaturalTime(s string, now time.Time) (time.Time, error) {
  86. r, err := when.EN.Parse(s, now) // returns "nil, nil" if no matches!
  87. if err != nil || r == nil {
  88. return time.Time{}, errUnparsableTime
  89. } else if r.Time.After(now) {
  90. return r.Time, nil
  91. }
  92. // Hack: If the time is parsable, but not in the future,
  93. // simply append "tomorrow, " to it.
  94. r, err = when.EN.Parse("tomorrow, "+s, now) // returns "nil, nil" if no matches!
  95. if err != nil || r == nil {
  96. return time.Time{}, errUnparsableTime
  97. } else if r.Time.After(now) {
  98. return r.Time, nil
  99. }
  100. return time.Time{}, errUnparsableTime
  101. }