time.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package humanize
  2. import (
  3. "bytes"
  4. "fmt"
  5. "git.nspix.com/golang/kos/util/bs"
  6. "math"
  7. "sort"
  8. "time"
  9. )
  10. const (
  11. Day = 24 * time.Hour
  12. Week = 7 * Day
  13. Month = 30 * Day
  14. Year = 12 * Month
  15. LongTime = 37 * Year
  16. )
  17. // A RelTimeMagnitude struct contains a relative time point at which
  18. // the relative format of time will switch to a new format string. A
  19. // slice of these in ascending order by their "D" field is passed to
  20. // CustomRelTime to format durations.
  21. //
  22. // The Format field is a string that may contain a "%s" which will be
  23. // replaced with the appropriate signed label (e.g. "ago" or "from
  24. // now") and a "%d" that will be replaced by the quantity.
  25. //
  26. // The DivBy field is the amount of time the time difference must be
  27. // divided by in order to display correctly.
  28. //
  29. // e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
  30. // DivBy should be time.Minute so whatever the duration is will be
  31. // expressed in minutes.
  32. type RelTimeMagnitude struct {
  33. D time.Duration
  34. Format string
  35. DivBy time.Duration
  36. }
  37. var defaultMagnitudes = []RelTimeMagnitude{
  38. {time.Second, "now", time.Second},
  39. {2 * time.Second, "1 second %s", 1},
  40. {time.Minute, "%d seconds %s", time.Second},
  41. {2 * time.Minute, "1 minute %s", 1},
  42. {time.Hour, "%d minutes %s", time.Minute},
  43. {2 * time.Hour, "1 hour %s", 1},
  44. {Day, "%d hours %s", time.Hour},
  45. {2 * Day, "1 day %s", 1},
  46. {Week, "%d days %s", Day},
  47. {2 * Week, "1 week %s", 1},
  48. {Month, "%d weeks %s", Week},
  49. {2 * Month, "1 month %s", 1},
  50. {Year, "%d months %s", Month},
  51. {18 * Month, "1 year %s", 1},
  52. {2 * Year, "2 years %s", 1},
  53. {LongTime, "%d years %s", Year},
  54. {math.MaxInt64, "a long while %s", 1},
  55. }
  56. // RelTime formats a time into a relative string.
  57. //
  58. // It takes two times and two labels. In addition to the generic time
  59. // delta string (e.g. 5 minutes), the labels are used applied so that
  60. // the label corresponding to the smaller time is applied.
  61. //
  62. // RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
  63. func RelTime(a, b time.Time, albl, blbl string) string {
  64. return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
  65. }
  66. // CustomRelTime formats a time into a relative string.
  67. //
  68. // It takes two times two labels and a table of relative time formats.
  69. // In addition to the generic time delta string (e.g. 5 minutes), the
  70. // labels are used applied so that the label corresponding to the
  71. // smaller time is applied.
  72. func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
  73. lbl := albl
  74. diff := b.Sub(a)
  75. if a.After(b) {
  76. lbl = blbl
  77. diff = a.Sub(b)
  78. }
  79. n := sort.Search(len(magnitudes), func(i int) bool {
  80. return magnitudes[i].D > diff
  81. })
  82. if n >= len(magnitudes) {
  83. n = len(magnitudes) - 1
  84. }
  85. mag := magnitudes[n]
  86. args := []interface{}{}
  87. escaped := false
  88. for _, ch := range mag.Format {
  89. if escaped {
  90. switch ch {
  91. case 's':
  92. args = append(args, lbl)
  93. case 'd':
  94. args = append(args, diff/mag.DivBy)
  95. }
  96. escaped = false
  97. } else {
  98. escaped = ch == '%'
  99. }
  100. }
  101. return fmt.Sprintf(mag.Format, args...)
  102. }
  103. type Time struct {
  104. tm time.Time
  105. }
  106. func Now() Time {
  107. return Time{tm: time.Now()}
  108. }
  109. func WrapTime(t time.Time) Time {
  110. return Time{tm: t}
  111. }
  112. func (t Time) Add(d Duration) Time {
  113. t.tm = t.tm.Add(d.Duration())
  114. return t
  115. }
  116. func (t Time) AddDuration(d time.Duration) Time {
  117. t.tm = t.tm.Add(d)
  118. return t
  119. }
  120. func (t Time) After(u Time) bool {
  121. return t.tm.After(u.tm)
  122. }
  123. func (t Time) AfterTime(u time.Time) bool {
  124. return t.tm.After(u)
  125. }
  126. func (t Time) Sub(u Time) Duration {
  127. return Duration(t.tm.Sub(u.tm))
  128. }
  129. func (t Time) SubTime(u time.Time) Duration {
  130. return Duration(t.tm.Sub(u))
  131. }
  132. func (t Time) Time() time.Time {
  133. return t.tm
  134. }
  135. func (t Time) String() string {
  136. return t.tm.Format(time.DateTime)
  137. }
  138. func (t Time) MarshalJSON() ([]byte, error) {
  139. s := `"` + t.tm.Format(time.DateTime) + `"`
  140. return bs.StringToBytes(s), nil
  141. }
  142. func (t Time) Ago() string {
  143. return RelTime(t.tm, time.Now(), "ago", "from now")
  144. }
  145. func (t *Time) UnmarshalJSON(buf []byte) (err error) {
  146. buf = bytes.TrimFunc(buf, func(r rune) bool {
  147. if r == '"' {
  148. return true
  149. }
  150. return false
  151. })
  152. t.tm, err = time.ParseInLocation(time.DateTime, bs.BytesToString(buf), time.Local)
  153. return err
  154. }