github.com/sagernet/sing-box@v1.9.0-rc.20/option/time_unit.go (about)

     1  package option
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  )
     7  
     8  // Copyright 2010 The Go Authors. All rights reserved.
     9  // Use of this source code is governed by a BSD-style
    10  // license that can be found in the LICENSE file.
    11  
    12  const durationDay = 24 * time.Hour
    13  
    14  var unitMap = map[string]uint64{
    15  	"ns": uint64(time.Nanosecond),
    16  	"us": uint64(time.Microsecond),
    17  	"µs": uint64(time.Microsecond), // U+00B5 = micro symbol
    18  	"μs": uint64(time.Microsecond), // U+03BC = Greek letter mu
    19  	"ms": uint64(time.Millisecond),
    20  	"s":  uint64(time.Second),
    21  	"m":  uint64(time.Minute),
    22  	"h":  uint64(time.Hour),
    23  	"d":  uint64(durationDay),
    24  }
    25  
    26  // ParseDuration parses a duration string.
    27  // A duration string is a possibly signed sequence of
    28  // decimal numbers, each with optional fraction and a unit suffix,
    29  // such as "300ms", "-1.5h" or "2h45m".
    30  // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
    31  func ParseDuration(s string) (Duration, error) {
    32  	// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
    33  	orig := s
    34  	var d uint64
    35  	neg := false
    36  
    37  	// Consume [-+]?
    38  	if s != "" {
    39  		c := s[0]
    40  		if c == '-' || c == '+' {
    41  			neg = c == '-'
    42  			s = s[1:]
    43  		}
    44  	}
    45  	// Special case: if all that is left is "0", this is zero.
    46  	if s == "0" {
    47  		return 0, nil
    48  	}
    49  	if s == "" {
    50  		return 0, errors.New("time: invalid duration " + quote(orig))
    51  	}
    52  	for s != "" {
    53  		var (
    54  			v, f  uint64      // integers before, after decimal point
    55  			scale float64 = 1 // value = v + f/scale
    56  		)
    57  
    58  		var err error
    59  
    60  		// The next character must be [0-9.]
    61  		if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
    62  			return 0, errors.New("time: invalid duration " + quote(orig))
    63  		}
    64  		// Consume [0-9]*
    65  		pl := len(s)
    66  		v, s, err = leadingInt(s)
    67  		if err != nil {
    68  			return 0, errors.New("time: invalid duration " + quote(orig))
    69  		}
    70  		pre := pl != len(s) // whether we consumed anything before a period
    71  
    72  		// Consume (\.[0-9]*)?
    73  		post := false
    74  		if s != "" && s[0] == '.' {
    75  			s = s[1:]
    76  			pl := len(s)
    77  			f, scale, s = leadingFraction(s)
    78  			post = pl != len(s)
    79  		}
    80  		if !pre && !post {
    81  			// no digits (e.g. ".s" or "-.s")
    82  			return 0, errors.New("time: invalid duration " + quote(orig))
    83  		}
    84  
    85  		// Consume unit.
    86  		i := 0
    87  		for ; i < len(s); i++ {
    88  			c := s[i]
    89  			if c == '.' || '0' <= c && c <= '9' {
    90  				break
    91  			}
    92  		}
    93  		if i == 0 {
    94  			return 0, errors.New("time: missing unit in duration " + quote(orig))
    95  		}
    96  		u := s[:i]
    97  		s = s[i:]
    98  		unit, ok := unitMap[u]
    99  		if !ok {
   100  			return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
   101  		}
   102  		if v > 1<<63/unit {
   103  			// overflow
   104  			return 0, errors.New("time: invalid duration " + quote(orig))
   105  		}
   106  		v *= unit
   107  		if f > 0 {
   108  			// float64 is needed to be nanosecond accurate for fractions of hours.
   109  			// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
   110  			v += uint64(float64(f) * (float64(unit) / scale))
   111  			if v > 1<<63 {
   112  				// overflow
   113  				return 0, errors.New("time: invalid duration " + quote(orig))
   114  			}
   115  		}
   116  		d += v
   117  		if d > 1<<63 {
   118  			return 0, errors.New("time: invalid duration " + quote(orig))
   119  		}
   120  	}
   121  	if neg {
   122  		return -Duration(d), nil
   123  	}
   124  	if d > 1<<63-1 {
   125  		return 0, errors.New("time: invalid duration " + quote(orig))
   126  	}
   127  	return Duration(d), nil
   128  }
   129  
   130  var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
   131  
   132  // leadingInt consumes the leading [0-9]* from s.
   133  func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) {
   134  	i := 0
   135  	for ; i < len(s); i++ {
   136  		c := s[i]
   137  		if c < '0' || c > '9' {
   138  			break
   139  		}
   140  		if x > 1<<63/10 {
   141  			// overflow
   142  			return 0, rem, errLeadingInt
   143  		}
   144  		x = x*10 + uint64(c) - '0'
   145  		if x > 1<<63 {
   146  			// overflow
   147  			return 0, rem, errLeadingInt
   148  		}
   149  	}
   150  	return x, s[i:], nil
   151  }
   152  
   153  // leadingFraction consumes the leading [0-9]* from s.
   154  // It is used only for fractions, so does not return an error on overflow,
   155  // it just stops accumulating precision.
   156  func leadingFraction(s string) (x uint64, scale float64, rem string) {
   157  	i := 0
   158  	scale = 1
   159  	overflow := false
   160  	for ; i < len(s); i++ {
   161  		c := s[i]
   162  		if c < '0' || c > '9' {
   163  			break
   164  		}
   165  		if overflow {
   166  			continue
   167  		}
   168  		if x > (1<<63-1)/10 {
   169  			// It's possible for overflow to give a positive number, so take care.
   170  			overflow = true
   171  			continue
   172  		}
   173  		y := x*10 + uint64(c) - '0'
   174  		if y > 1<<63 {
   175  			overflow = true
   176  			continue
   177  		}
   178  		x = y
   179  		scale *= 10
   180  	}
   181  	return x, scale, s[i:]
   182  }
   183  
   184  // These are borrowed from unicode/utf8 and strconv and replicate behavior in
   185  // that package, since we can't take a dependency on either.
   186  const (
   187  	lowerhex  = "0123456789abcdef"
   188  	runeSelf  = 0x80
   189  	runeError = '\uFFFD'
   190  )
   191  
   192  func quote(s string) string {
   193  	buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes
   194  	buf[0] = '"'
   195  	for i, c := range s {
   196  		if c >= runeSelf || c < ' ' {
   197  			// This means you are asking us to parse a time.Duration or
   198  			// time.Location with unprintable or non-ASCII characters in it.
   199  			// We don't expect to hit this case very often. We could try to
   200  			// reproduce strconv.Quote's behavior with full fidelity but
   201  			// given how rarely we expect to hit these edge cases, speed and
   202  			// conciseness are better.
   203  			var width int
   204  			if c == runeError {
   205  				width = 1
   206  				if i+2 < len(s) && s[i:i+3] == string(runeError) {
   207  					width = 3
   208  				}
   209  			} else {
   210  				width = len(string(c))
   211  			}
   212  			for j := 0; j < width; j++ {
   213  				buf = append(buf, `\x`...)
   214  				buf = append(buf, lowerhex[s[i+j]>>4])
   215  				buf = append(buf, lowerhex[s[i+j]&0xF])
   216  			}
   217  		} else {
   218  			if c == '"' || c == '\\' {
   219  				buf = append(buf, '\\')
   220  			}
   221  			buf = append(buf, string(c)...)
   222  		}
   223  	}
   224  	buf = append(buf, '"')
   225  	return string(buf)
   226  }