interval.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. package util
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strconv"
  6. "time"
  7. "github.com/opencost/opencost/core/pkg/util/timeutil"
  8. )
  9. var intervalRegex = regexp.MustCompile(`^(\d+)(s|m|h|d|w)$`)
  10. // Interval is a time period defined by a string with a integer followed by a letter (ex: 5d = 5 days)
  11. type Interval interface {
  12. // Add adds the interval multiplied by the given int to the given time. (A 10m interval called with 3 would add 30
  13. // minutes to the given time)
  14. Add(time.Time, int) time.Time
  15. // Truncate returns the start of the interval that the given time is a part off
  16. Truncate(time time.Time) time.Time
  17. }
  18. func NewInterval(def string) (Interval, error) {
  19. match := intervalRegex.FindStringSubmatch(def)
  20. if match == nil {
  21. return nil, fmt.Errorf("failed to parse interval '%s'", def)
  22. }
  23. num, err := strconv.ParseInt(match[1], 10, 64)
  24. // This should not happen
  25. if err != nil {
  26. panic(fmt.Sprintf("NewInterval: regex failure on int '%s'", def))
  27. }
  28. switch match[2] {
  29. case "s":
  30. return &durationInterval{time.Duration(num) * time.Second}, nil
  31. case "m":
  32. return &durationInterval{time.Duration(num) * time.Minute}, nil
  33. case "h":
  34. return &durationInterval{time.Duration(num) * time.Hour}, nil
  35. case "d":
  36. return &durationInterval{time.Duration(num) * timeutil.Day}, nil
  37. case "w":
  38. return &weekInterval{int(num)}, nil
  39. default:
  40. panic(fmt.Sprintf("NewInterval: regex failure on unit '%s'", def))
  41. }
  42. }
  43. type durationInterval struct {
  44. duration time.Duration
  45. }
  46. func (d *durationInterval) Add(t time.Time, i int) time.Time {
  47. return t.Add(d.duration * time.Duration(i))
  48. }
  49. func (d *durationInterval) Truncate(time time.Time) time.Time {
  50. return time.UTC().Truncate(d.duration)
  51. }
  52. // weekInterval is an interval that tracks multiples of weeks with the week starting on Sunday
  53. type weekInterval struct {
  54. count int
  55. }
  56. func (w *weekInterval) Add(t time.Time, num int) time.Time {
  57. return t.Add(timeutil.Week * time.Duration(num*w.count))
  58. }
  59. // Truncate to the nearest Sunday that is a multiple of the count starting from 0000-31-12
  60. func (w *weekInterval) Truncate(t time.Time) time.Time {
  61. // add a day to Sundays to prevent times that would truncate to themselves from going to the previous step
  62. if t.UTC().Weekday() == time.Sunday {
  63. t = t.UTC().AddDate(0, 0, 1)
  64. }
  65. // truncate to monday using a weekly duration multiple (0001-01-01 was a monday) then subtract a day
  66. return t.UTC().Truncate(timeutil.Week * time.Duration(w.count)).Add(-timeutil.Day)
  67. }