time.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  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. // ParseFutureTime parses a date/time string to a time.Time. It supports unix timestamps, durations
  15. // and natural language dates
  16. func ParseFutureTime(s string, now time.Time) (time.Time, error) {
  17. s = strings.TrimSpace(s)
  18. t, err := parseUnixTime(s, now)
  19. if err == nil {
  20. return t, nil
  21. }
  22. t, err = parseFromDuration(s, now)
  23. if err == nil {
  24. return t, nil
  25. }
  26. t, err = parseNaturalTime(s, now)
  27. if err == nil {
  28. return t, nil
  29. }
  30. return time.Time{}, errUnparsableTime
  31. }
  32. func parseFromDuration(s string, now time.Time) (time.Time, error) {
  33. d, err := parseDuration(s)
  34. if err == nil {
  35. return now.Add(d), nil
  36. }
  37. return time.Time{}, errUnparsableTime
  38. }
  39. func parseDuration(s string) (time.Duration, error) {
  40. d, err := time.ParseDuration(s)
  41. if err == nil {
  42. return d, nil
  43. }
  44. matches := durationStrRegex.FindStringSubmatch(s)
  45. if matches != nil {
  46. number, err := strconv.Atoi(matches[1])
  47. if err != nil {
  48. return 0, errUnparsableTime
  49. }
  50. switch unit := matches[2][0:1]; unit {
  51. case "d":
  52. return time.Duration(number) * 24 * time.Hour, nil
  53. case "h":
  54. return time.Duration(number) * time.Hour, nil
  55. case "m":
  56. return time.Duration(number) * time.Minute, nil
  57. case "s":
  58. return time.Duration(number) * time.Second, nil
  59. default:
  60. return 0, errUnparsableTime
  61. }
  62. }
  63. return 0, errUnparsableTime
  64. }
  65. func parseUnixTime(s string, now time.Time) (time.Time, error) {
  66. t, err := strconv.Atoi(s)
  67. if err != nil {
  68. return time.Time{}, err
  69. } else if int64(t) < now.Unix() {
  70. return time.Time{}, errUnparsableTime
  71. }
  72. return time.Unix(int64(t), 0).UTC(), nil
  73. }
  74. func parseNaturalTime(s string, now time.Time) (time.Time, error) {
  75. r, err := when.EN.Parse(s, now) // returns "nil, nil" if no matches!
  76. if err != nil || r == nil {
  77. return time.Time{}, errUnparsableTime
  78. } else if r.Time.After(now) {
  79. return r.Time, nil
  80. }
  81. // Hack: If the time is parsable, but not in the future,
  82. // simply append "tomorrow, " to it.
  83. r, err = when.EN.Parse("tomorrow, "+s, now) // returns "nil, nil" if no matches!
  84. if err != nil || r == nil {
  85. return time.Time{}, errUnparsableTime
  86. } else if r.Time.After(now) {
  87. return r.Time, nil
  88. }
  89. return time.Time{}, errUnparsableTime
  90. }