package humanize import ( "bytes" "fmt" "git.nspix.com/golang/kos/util/bs" "math" "sort" "time" ) const ( Day = 24 * time.Hour Week = 7 * Day Month = 30 * Day Year = 12 * Month LongTime = 37 * Year ) // 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}, {Month, "%d weeks %s", Week}, {2 * Month, "1 month %s", 1}, {Year, "%d months %s", Month}, {18 * Month, "1 year %s", 1}, {2 * Year, "2 years %s", 1}, {LongTime, "%d years %s", Year}, {math.MaxInt64, "a long while %s", 1}, } // RelTime formats a time into a relative string. // // It takes two times and two labels. 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. // // 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.Sub(a) if a.After(b) { lbl = blbl diff = a.Sub(b) } n := sort.Search(len(magnitudes), func(i int) bool { 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 { if escaped { switch ch { case 's': args = append(args, lbl) case 'd': args = append(args, diff/mag.DivBy) } escaped = false } else { escaped = ch == '%' } } return fmt.Sprintf(mag.Format, args...) } type Time struct { tm time.Time } func Now() Time { return Time{tm: time.Now()} } func WrapTime(t time.Time) Time { return Time{tm: t} } func (t Time) Add(d Duration) Time { t.tm = t.tm.Add(d.Duration()) return t } func (t Time) AddDuration(d time.Duration) Time { t.tm = t.tm.Add(d) return t } func (t Time) After(u Time) bool { return t.tm.After(u.tm) } func (t Time) AfterTime(u time.Time) bool { return t.tm.After(u) } func (t Time) Sub(u Time) Duration { return Duration(t.tm.Sub(u.tm)) } func (t Time) SubTime(u time.Time) Duration { return Duration(t.tm.Sub(u)) } func (t Time) Time() time.Time { return t.tm } func (t Time) String() string { return t.tm.Format(time.DateTime) } func (t Time) MarshalJSON() ([]byte, error) { s := `"` + t.tm.Format(time.DateTime) + `"` return bs.StringToBytes(s), nil } func (t Time) Ago() string { return RelTime(t.tm, time.Now(), "ago", "from now") } func (t *Time) UnmarshalJSON(buf []byte) (err error) { buf = bytes.TrimFunc(buf, func(r rune) bool { if r == '"' { return true } return false }) t.tm, err = time.ParseInLocation(time.DateTime, bs.BytesToString(buf), time.Local) return err }