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