times.go raw

   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  }
 118