bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/opentsdb/duration.go (about)

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package opentsdb
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"time"
    11  )
    12  
    13  const (
    14  	Millisecond Duration = Duration(time.Millisecond)
    15  	Second               = 1000 * Millisecond
    16  	Minute               = 60 * Second
    17  	Hour                 = 60 * Minute
    18  	Day                  = Hour * 24
    19  	Week                 = Day * 7
    20  	Month                = Day * 30
    21  	Year                 = Day * 365
    22  )
    23  
    24  // Duration extends time.Duration to support OpenTSDB time-format specifiers:
    25  // http://opentsdb.net/docs/build/html/user_guide/query/dates.html#relative.
    26  type Duration time.Duration
    27  
    28  var unitMap = map[string]float64{
    29  	"ms": float64(time.Millisecond),
    30  	"s":  float64(time.Second),
    31  	"m":  float64(time.Minute),
    32  	"h":  float64(time.Hour),
    33  	"d":  float64(time.Hour) * 24,
    34  	"w":  float64(time.Hour) * 24 * 7,
    35  	"n":  float64(time.Hour) * 24 * 30,
    36  	"y":  float64(time.Hour) * 24 * 365,
    37  }
    38  
    39  // ParseDuration is equivalent to time.ParseDuration, but supports time units specified at http://opentsdb.net/docs/build/html/user_guide/query/dates.html.
    40  func ParseDuration(s string) (Duration, error) {
    41  	// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
    42  	orig := s
    43  	f := float64(0)
    44  	neg := false
    45  
    46  	// Consume [-+]?
    47  	if s != "" {
    48  		c := s[0]
    49  		if c == '-' || c == '+' {
    50  			neg = c == '-'
    51  			s = s[1:]
    52  		}
    53  	}
    54  	// Special case: if all that is left is "0", this is zero.
    55  	if s == "0" {
    56  		return 0, nil
    57  	}
    58  	if s == "" {
    59  		return 0, errors.New("time: invalid duration " + orig)
    60  	}
    61  	for s != "" {
    62  		g := float64(0) // this element of the sequence
    63  
    64  		var x int64
    65  		var err error
    66  
    67  		// The next character must be [0-9.]
    68  		if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) {
    69  			return 0, errors.New("time: invalid duration " + orig)
    70  		}
    71  		// Consume [0-9]*
    72  		pl := len(s)
    73  		x, s, err = leadingInt(s)
    74  		if err != nil {
    75  			return 0, errors.New("time: invalid duration " + orig)
    76  		}
    77  		g = float64(x)
    78  		pre := pl != len(s) // whether we consumed anything before a period
    79  
    80  		// Consume (\.[0-9]*)?
    81  		post := false
    82  		if s != "" && s[0] == '.' {
    83  			s = s[1:]
    84  			pl := len(s)
    85  			x, s, err = leadingInt(s)
    86  			if err != nil {
    87  				return 0, errors.New("time: invalid duration " + orig)
    88  			}
    89  			scale := 1.0
    90  			for n := pl - len(s); n > 0; n-- {
    91  				scale *= 10
    92  			}
    93  			g += float64(x) / scale
    94  			post = pl != len(s)
    95  		}
    96  		if !pre && !post {
    97  			// no digits (e.g. ".s" or "-.s")
    98  			return 0, errors.New("time: invalid duration " + orig)
    99  		}
   100  
   101  		// Consume unit.
   102  		i := 0
   103  		for ; i < len(s); i++ {
   104  			c := s[i]
   105  			if c == '.' || ('0' <= c && c <= '9') {
   106  				break
   107  			}
   108  		}
   109  		if i == 0 {
   110  			return 0, errors.New("time: missing unit in duration " + orig)
   111  		}
   112  		u := s[:i]
   113  		s = s[i:]
   114  		unit, ok := unitMap[u]
   115  		if !ok {
   116  			return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
   117  		}
   118  
   119  		f += g * unit
   120  	}
   121  
   122  	if neg {
   123  		f = -f
   124  	}
   125  	return Duration(f), nil
   126  }
   127  
   128  var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
   129  
   130  // leadingInt consumes the leading [0-9]* from s.
   131  func leadingInt(s string) (x int64, rem string, err error) {
   132  	i := 0
   133  	for ; i < len(s); i++ {
   134  		c := s[i]
   135  		if c < '0' || c > '9' {
   136  			break
   137  		}
   138  		if x >= (1<<63-10)/10 {
   139  			// overflow
   140  			return 0, "", errLeadingInt
   141  		}
   142  		x = x*10 + int64(c) - '0'
   143  	}
   144  	return x, s[i:], nil
   145  }
   146  
   147  func (d Duration) String() string {
   148  	return fmt.Sprintf("%dms", d/Millisecond)
   149  }
   150  
   151  func (d Duration) HumanString() string {
   152  	if d >= Year && d%Year == 0 {
   153  		return fmt.Sprintf("%dy", d/Year)
   154  	}
   155  	if d >= Week && d%Week == 0 {
   156  		return fmt.Sprintf("%dw", d/Week)
   157  	}
   158  	if d >= Day && d%Day == 0 {
   159  		return fmt.Sprintf("%dd", d/Day)
   160  	}
   161  	if d >= Hour && d%Hour == 0 {
   162  		return fmt.Sprintf("%dh", d/Hour)
   163  	}
   164  	if d >= Minute && d%Minute == 0 {
   165  		return fmt.Sprintf("%dm", d/Minute)
   166  	}
   167  	if d >= Second && d%Second == 0 {
   168  		return fmt.Sprintf("%ds", d/Second)
   169  	}
   170  	return fmt.Sprintf("%dms", d/Millisecond)
   171  }
   172  
   173  // Seconds returns the duration as a floating point number of seconds.
   174  func (d Duration) Seconds() float64 {
   175  	return time.Duration(d).Seconds()
   176  }
   177  
   178  func (d *Duration) UnmarshalText(text []byte) error {
   179  	duration, err := ParseDuration(string(text))
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	*d = duration
   185  	return nil
   186  }