github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/timex/duration.go (about)

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