You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.2 KiB

  1. package humanize
  2. import (
  3. "fmt"
  4. "math"
  5. "sort"
  6. "time"
  7. )
  8. // Seconds-based time units
  9. const (
  10. Day = 24 * time.Hour
  11. Week = 7 * Day
  12. Month = 30 * Day
  13. Year = 12 * Month
  14. LongTime = 37 * Year
  15. )
  16. // Time formats a time into a relative string.
  17. //
  18. // Time(someT) -> "3 weeks ago"
  19. func Time(then time.Time) string {
  20. return RelTime(then, time.Now(), "ago", "from now")
  21. }
  22. // A RelTimeMagnitude struct contains a relative time point at which
  23. // the relative format of time will switch to a new format string. A
  24. // slice of these in ascending order by their "D" field is passed to
  25. // CustomRelTime to format durations.
  26. //
  27. // The Format field is a string that may contain a "%s" which will be
  28. // replaced with the appropriate signed label (e.g. "ago" or "from
  29. // now") and a "%d" that will be replaced by the quantity.
  30. //
  31. // The DivBy field is the amount of time the time difference must be
  32. // divided by in order to display correctly.
  33. //
  34. // e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
  35. // DivBy should be time.Minute so whatever the duration is will be
  36. // expressed in minutes.
  37. type RelTimeMagnitude struct {
  38. D time.Duration
  39. Format string
  40. DivBy time.Duration
  41. }
  42. var defaultMagnitudes = []RelTimeMagnitude{
  43. {time.Second, "now", time.Second},
  44. {2 * time.Second, "1 second %s", 1},
  45. {time.Minute, "%d seconds %s", time.Second},
  46. {2 * time.Minute, "1 minute %s", 1},
  47. {time.Hour, "%d minutes %s", time.Minute},
  48. {2 * time.Hour, "1 hour %s", 1},
  49. {Day, "%d hours %s", time.Hour},
  50. {2 * Day, "1 day %s", 1},
  51. {Week, "%d days %s", Day},
  52. {2 * Week, "1 week %s", 1},
  53. {Month, "%d weeks %s", Week},
  54. {2 * Month, "1 month %s", 1},
  55. {Year, "%d months %s", Month},
  56. {18 * Month, "1 year %s", 1},
  57. {2 * Year, "2 years %s", 1},
  58. {LongTime, "%d years %s", Year},
  59. {math.MaxInt64, "a long while %s", 1},
  60. }
  61. // RelTime formats a time into a relative string.
  62. //
  63. // It takes two times and two labels. In addition to the generic time
  64. // delta string (e.g. 5 minutes), the labels are used applied so that
  65. // the label corresponding to the smaller time is applied.
  66. //
  67. // RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
  68. func RelTime(a, b time.Time, albl, blbl string) string {
  69. return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
  70. }
  71. // CustomRelTime formats a time into a relative string.
  72. //
  73. // It takes two times two labels and a table of relative time formats.
  74. // In addition to the generic time delta string (e.g. 5 minutes), the
  75. // labels are used applied so that the label corresponding to the
  76. // smaller time is applied.
  77. func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
  78. lbl := albl
  79. diff := b.Sub(a)
  80. if a.After(b) {
  81. lbl = blbl
  82. diff = a.Sub(b)
  83. }
  84. n := sort.Search(len(magnitudes), func(i int) bool {
  85. return magnitudes[i].D > diff
  86. })
  87. if n >= len(magnitudes) {
  88. n = len(magnitudes) - 1
  89. }
  90. mag := magnitudes[n]
  91. args := []interface{}{}
  92. escaped := false
  93. for _, ch := range mag.Format {
  94. if escaped {
  95. switch ch {
  96. case 's':
  97. args = append(args, lbl)
  98. case 'd':
  99. args = append(args, diff/mag.DivBy)
  100. }
  101. escaped = false
  102. } else {
  103. escaped = ch == '%'
  104. }
  105. }
  106. return fmt.Sprintf(mag.Format, args...)
  107. }