github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/util/duration/parse.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  // This file is a rough copy of ParseDuration function from go standard library
     6  // It adds support for day and year durations
     7  
     8  package duration
     9  
    10  import (
    11  	"errors"
    12  	"time"
    13  )
    14  
    15  var unitMap = map[string]int64{
    16  	"ns": int64(time.Nanosecond),
    17  	"us": int64(time.Microsecond),
    18  	"µs": int64(time.Microsecond), // U+00B5 = micro symbol
    19  	"μs": int64(time.Microsecond), // U+03BC = Greek letter mu
    20  	"ms": int64(time.Millisecond),
    21  	"s":  int64(time.Second),
    22  	"m":  int64(time.Minute),
    23  	"h":  int64(time.Hour),
    24  	// these are extra units we (pyroscope) added:
    25  	"d": int64(24) * int64(time.Hour),
    26  	"M": int64(24*30) * int64(time.Hour),
    27  	"y": int64(24*365) * int64(time.Hour),
    28  }
    29  
    30  var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
    31  
    32  // leadingInt consumes the leading [0-9]* from s.
    33  func leadingInt(s string) (x int64, rem string, err error) {
    34  	i := 0
    35  	for ; i < len(s); i++ {
    36  		c := s[i]
    37  		if c < '0' || c > '9' {
    38  			break
    39  		}
    40  		if x > (1<<63-1)/10 {
    41  			// overflow
    42  			return 0, "", errLeadingInt
    43  		}
    44  		x = x*10 + int64(c) - '0'
    45  		if x < 0 {
    46  			// overflow
    47  			return 0, "", errLeadingInt
    48  		}
    49  	}
    50  	return x, s[i:], nil
    51  }
    52  
    53  // leadingFraction consumes the leading [0-9]* from s.
    54  // It is used only for fractions, so does not return an error on overflow,
    55  // it just stops accumulating precision.
    56  func leadingFraction(s string) (x int64, scale float64, rem string) {
    57  	i := 0
    58  	scale = 1
    59  	overflow := false
    60  	for ; i < len(s); i++ {
    61  		c := s[i]
    62  		if c < '0' || c > '9' {
    63  			break
    64  		}
    65  		if overflow {
    66  			continue
    67  		}
    68  		if x > (1<<63-1)/10 {
    69  			// It's possible for overflow to give a positive number, so take care.
    70  			overflow = true
    71  			continue
    72  		}
    73  		y := x*10 + int64(c) - '0'
    74  		if y < 0 {
    75  			overflow = true
    76  			continue
    77  		}
    78  		x = y
    79  		scale *= 10
    80  	}
    81  	return x, scale, s[i:]
    82  }
    83  
    84  func quote(s string) string {
    85  	return "\"" + s + "\""
    86  }
    87  
    88  // ParseDuration parses a duration string.
    89  // A duration string is a possibly signed sequence of
    90  // decimal numbers, each with optional fraction and a unit suffix,
    91  // such as "300ms", "-1.5h" or "2h45m".
    92  // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
    93  func ParseDuration(s string) (time.Duration, error) {
    94  	// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
    95  	orig := s
    96  	var d int64
    97  	neg := false
    98  
    99  	// Consume [-+]?
   100  	if s != "" {
   101  		c := s[0]
   102  		if c == '-' || c == '+' {
   103  			neg = c == '-'
   104  			s = s[1:]
   105  		}
   106  	}
   107  	// Special case: if all that is left is "0", this is zero.
   108  	if s == "0" {
   109  		return 0, nil
   110  	}
   111  	if s == "" {
   112  		return 0, errors.New("time: invalid duration " + quote(orig))
   113  	}
   114  	for s != "" {
   115  		var (
   116  			v, f  int64       // integers before, after decimal point
   117  			scale float64 = 1 // value = v + f/scale
   118  		)
   119  
   120  		var err error
   121  
   122  		// The next character must be [0-9.]
   123  		if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
   124  			return 0, errors.New("time: invalid duration " + quote(orig))
   125  		}
   126  		// Consume [0-9]*
   127  		pl := len(s)
   128  		v, s, err = leadingInt(s)
   129  		if err != nil {
   130  			return 0, errors.New("time: invalid duration " + quote(orig))
   131  		}
   132  		pre := pl != len(s) // whether we consumed anything before a period
   133  
   134  		// Consume (\.[0-9]*)?
   135  		post := false
   136  		if s != "" && s[0] == '.' {
   137  			s = s[1:]
   138  			pl := len(s)
   139  			f, scale, s = leadingFraction(s)
   140  			post = pl != len(s)
   141  		}
   142  		if !pre && !post {
   143  			// no digits (e.g. ".s" or "-.s")
   144  			return 0, errors.New("time: invalid duration " + quote(orig))
   145  		}
   146  
   147  		// Consume unit.
   148  		i := 0
   149  		for ; i < len(s); i++ {
   150  			c := s[i]
   151  			if c == '.' || '0' <= c && c <= '9' {
   152  				break
   153  			}
   154  		}
   155  		if i == 0 {
   156  			return 0, errors.New("time: missing unit in duration " + quote(orig))
   157  		}
   158  		u := s[:i]
   159  		s = s[i:]
   160  		unit, ok := unitMap[u]
   161  		if !ok {
   162  			return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
   163  		}
   164  		if v > (1<<63-1)/unit {
   165  			// overflow
   166  			return 0, errors.New("time: invalid duration " + quote(orig))
   167  		}
   168  		v *= unit
   169  		if f > 0 {
   170  			// float64 is needed to be nanosecond accurate for fractions of hours.
   171  			// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
   172  			v += int64(float64(f) * (float64(unit) / scale))
   173  			if v < 0 {
   174  				// overflow
   175  				return 0, errors.New("time: invalid duration " + quote(orig))
   176  			}
   177  		}
   178  		d += v
   179  		if d < 0 {
   180  			// overflow
   181  			return 0, errors.New("time: invalid duration " + quote(orig))
   182  		}
   183  	}
   184  
   185  	if neg {
   186  		d = -d
   187  	}
   188  	return time.Duration(d), nil
   189  }