|
|
@@ -9,9 +9,7 @@ import (
|
|
|
|
|
|
// Seconds-based time units
|
|
|
const (
|
|
|
- Minute = 60
|
|
|
- Hour = 60 * Minute
|
|
|
- Day = 24 * Hour
|
|
|
+ Day = 24 * time.Hour
|
|
|
Week = 7 * Day
|
|
|
Month = 30 * Day
|
|
|
Year = 12 * Month
|
|
|
@@ -25,18 +23,35 @@ func Time(then time.Time) string {
|
|
|
return RelTime(then, time.Now(), "ago", "from now")
|
|
|
}
|
|
|
|
|
|
-var magnitudes = []struct {
|
|
|
- d int64
|
|
|
- format string
|
|
|
- divby int64
|
|
|
-}{
|
|
|
- {1, "now", 1},
|
|
|
- {2, "1 second %s", 1},
|
|
|
- {Minute, "%d seconds %s", 1},
|
|
|
- {2 * Minute, "1 minute %s", 1},
|
|
|
- {Hour, "%d minutes %s", Minute},
|
|
|
- {2 * Hour, "1 hour %s", 1},
|
|
|
- {Day, "%d hours %s", Hour},
|
|
|
+// A RelTimeMagnitude struct contains a relative time point at which
|
|
|
+// the relative format of time will switch to a new format string. A
|
|
|
+// slice of these in ascending order by their "D" field is passed to
|
|
|
+// CustomRelTime to format durations.
|
|
|
+//
|
|
|
+// The Format field is a string that may contain a "%s" which will be
|
|
|
+// replaced with the appropriate signed label (e.g. "ago" or "from
|
|
|
+// now") and a "%d" that will be replaced by the quantity.
|
|
|
+//
|
|
|
+// The DivBy field is the amount of time the time difference must be
|
|
|
+// divided by in order to display correctly.
|
|
|
+//
|
|
|
+// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
|
|
+// DivBy should be time.Minute so whatever the duration is will be
|
|
|
+// expressed in minutes.
|
|
|
+type RelTimeMagnitude struct {
|
|
|
+ D time.Duration
|
|
|
+ Format string
|
|
|
+ DivBy time.Duration
|
|
|
+}
|
|
|
+
|
|
|
+var defaultMagnitudes = []RelTimeMagnitude{
|
|
|
+ {time.Second, "now", time.Second},
|
|
|
+ {2 * time.Second, "1 second %s", 1},
|
|
|
+ {time.Minute, "%d seconds %s", time.Second},
|
|
|
+ {2 * time.Minute, "1 minute %s", 1},
|
|
|
+ {time.Hour, "%d minutes %s", time.Minute},
|
|
|
+ {2 * time.Hour, "1 hour %s", 1},
|
|
|
+ {Day, "%d hours %s", time.Hour},
|
|
|
{2 * Day, "1 day %s", 1},
|
|
|
{Week, "%d days %s", Day},
|
|
|
{2 * Week, "1 week %s", 1},
|
|
|
@@ -57,35 +72,46 @@ var magnitudes = []struct {
|
|
|
//
|
|
|
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
|
|
func RelTime(a, b time.Time, albl, blbl string) string {
|
|
|
+ return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
|
|
+}
|
|
|
+
|
|
|
+// CustomRelTime formats a time into a relative string.
|
|
|
+//
|
|
|
+// It takes two times two labels and a table of relative time formats.
|
|
|
+// In addition to the generic time delta string (e.g. 5 minutes), the
|
|
|
+// labels are used applied so that the label corresponding to the
|
|
|
+// smaller time is applied.
|
|
|
+func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
|
|
lbl := albl
|
|
|
- diff := b.Unix() - a.Unix()
|
|
|
+ diff := b.Sub(a)
|
|
|
|
|
|
- after := a.After(b)
|
|
|
- if after {
|
|
|
+ if a.After(b) {
|
|
|
lbl = blbl
|
|
|
- diff = a.Unix() - b.Unix()
|
|
|
+ diff = a.Sub(b)
|
|
|
}
|
|
|
|
|
|
n := sort.Search(len(magnitudes), func(i int) bool {
|
|
|
- return magnitudes[i].d > diff
|
|
|
+ return magnitudes[i].D > diff
|
|
|
})
|
|
|
|
|
|
+ if n >= len(magnitudes) {
|
|
|
+ n = len(magnitudes) - 1
|
|
|
+ }
|
|
|
mag := magnitudes[n]
|
|
|
args := []interface{}{}
|
|
|
escaped := false
|
|
|
- for _, ch := range mag.format {
|
|
|
+ for _, ch := range mag.Format {
|
|
|
if escaped {
|
|
|
switch ch {
|
|
|
- case '%':
|
|
|
case 's':
|
|
|
args = append(args, lbl)
|
|
|
case 'd':
|
|
|
- args = append(args, diff/mag.divby)
|
|
|
+ args = append(args, diff/mag.DivBy)
|
|
|
}
|
|
|
escaped = false
|
|
|
} else {
|
|
|
escaped = ch == '%'
|
|
|
}
|
|
|
}
|
|
|
- return fmt.Sprintf(mag.format, args...)
|
|
|
+ return fmt.Sprintf(mag.Format, args...)
|
|
|
}
|