github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/duration.go (about)

     1  // Copyright 2009 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 https://go.dev/LICENSE file.
     4  //
     5  // Code is borrowed directly from pkg/time.ParseDuration under
     6  // Go's BSD-style license, extended with changes to support
     7  // days, weeks and year as time Duration units.
     8  
     9  // Package cmd provides extended time.Duration implementation
    10  package cmd
    11  
    12  import (
    13  	"errors"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  // Duration is a standard unit of time.
    19  type Duration time.Duration
    20  
    21  // Days returns the duration as a floating point number of days.
    22  func (d Duration) Days() float64 {
    23  	hour := d / Hour
    24  	nsec := d % Hour
    25  	return float64(hour) + float64(nsec)*(1e-9/60/60/24)
    26  }
    27  
    28  // Standard unit of time.
    29  var (
    30  	Nanosecond  = Duration(time.Nanosecond)
    31  	Microsecond = Duration(time.Microsecond)
    32  	Millisecond = Duration(time.Millisecond)
    33  	Second      = Duration(time.Second)
    34  	Minute      = Duration(time.Minute)
    35  	Hour        = Duration(time.Hour)
    36  	Day         = Hour * 24
    37  	Week        = Day * 7
    38  	Fortnight   = Week * 2
    39  	Month       = Day * 30    // Approximation
    40  	Year        = Day * 365   // Approximation
    41  	Decade      = Year * 10   // Approximation
    42  	Century     = Year * 100  // Approximation
    43  	Millennium  = Year * 1000 // Approximation
    44  )
    45  
    46  // leadingInt consumes the leading [0-9]* from s.
    47  // this function is directly copied from
    48  // https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/time/format.go;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;l=1455
    49  // only changes the "error string that's being returned"
    50  func leadingInt(s string) (x int64, rem string, err error) {
    51  	errLeadingInt := errors.New("bad [0-9]*")
    52  
    53  	i := 0
    54  	for ; i < len(s); i++ {
    55  		c := s[i]
    56  		if c < '0' || c > '9' {
    57  			break
    58  		}
    59  		if x > (1<<63-1)/10 {
    60  			// overflow
    61  			return 0, "", errLeadingInt
    62  		}
    63  		x = x*10 + int64(c) - '0'
    64  		if x < 0 {
    65  			// overflow
    66  			return 0, "", errLeadingInt
    67  		}
    68  	}
    69  	return x, s[i:], nil
    70  }
    71  
    72  // unitMap defines 'unit' to 'actual-value'
    73  // translates for calculating duration.
    74  var unitMap = map[string]int64{
    75  	"ns": int64(Nanosecond),
    76  	"us": int64(Microsecond),
    77  	"µs": int64(Microsecond), // U+00B5 = micro symbol
    78  	"μs": int64(Microsecond), // U+03BC = Greek letter mu
    79  	"ms": int64(Millisecond),
    80  	"s":  int64(Second),
    81  	"m":  int64(Minute),
    82  	"h":  int64(Hour),
    83  	"d":  int64(Day),
    84  	"w":  int64(Week),
    85  	"y":  int64(Year), // Approximation
    86  }
    87  
    88  // ParseDuration parses a duration string.
    89  // This implementation is similar to Go's
    90  // https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/time/format.go;l=1546;bpv=1;bpt=1
    91  // extends it to parse days, weeks and years..
    92  //
    93  // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d", "w", "y".
    94  func ParseDuration(s string) (Duration, error) {
    95  	if strings.TrimSpace(s) == "" {
    96  		return 0, errors.New("invalid empty duration")
    97  	}
    98  
    99  	// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
   100  	orig := s
   101  	var d int64
   102  	neg := false
   103  
   104  	// Consume [-+]?
   105  	if s != "" {
   106  		c := s[0]
   107  		if c == '-' || c == '+' {
   108  			neg = c == '-'
   109  			s = s[1:]
   110  		}
   111  	}
   112  	// Special case: if all that is left is "0", this is zero.
   113  	if s == "0" {
   114  		return 0, nil
   115  	}
   116  	if s == "" {
   117  		return 0, errors.New("invalid duration " + orig)
   118  	}
   119  	for s != "" {
   120  		var (
   121  			v, f  int64       // integers before, after decimal point
   122  			scale float64 = 1 // value = v + f/scale
   123  		)
   124  
   125  		var err error
   126  
   127  		// The next character must be [0-9.]
   128  		if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
   129  			return 0, errors.New("invalid duration " + orig)
   130  		}
   131  		// Consume [0-9]*
   132  		pl := len(s)
   133  		v, s, err = leadingInt(s)
   134  		if err != nil {
   135  			return 0, errors.New("invalid duration " + orig)
   136  		}
   137  		pre := pl != len(s) // whether we consumed anything before a period
   138  
   139  		// Consume (\.[0-9]*)?
   140  		post := false
   141  		if s != "" && s[0] == '.' {
   142  			s = s[1:]
   143  			pl := len(s)
   144  			f, s, err = leadingInt(s)
   145  			if err != nil {
   146  				return 0, errors.New("invalid duration " + orig)
   147  			}
   148  			for n := pl - len(s); n > 0; n-- {
   149  				scale *= 10
   150  			}
   151  			post = pl != len(s)
   152  		}
   153  		if !pre && !post {
   154  			// no digits (e.g. ".s" or "-.s")
   155  			return 0, errors.New("invalid duration " + orig)
   156  		}
   157  
   158  		// Consume unit.
   159  		i := 0
   160  		for ; i < len(s); i++ {
   161  			c := s[i]
   162  			if c == '.' || '0' <= c && c <= '9' {
   163  				break
   164  			}
   165  		}
   166  		if i == 0 {
   167  			return 0, errors.New("missing unit in duration " + orig)
   168  		}
   169  		u := s[:i]
   170  		s = s[i:]
   171  		unit, ok := unitMap[u]
   172  		if !ok {
   173  			return 0, errors.New("unknown unit " + u + " in duration " + orig)
   174  		}
   175  		if v > (1<<63-1)/unit {
   176  			// overflow
   177  			return 0, errors.New("invalid duration " + orig)
   178  		}
   179  		v *= unit
   180  		if f > 0 {
   181  			// float64 is needed to be nanosecond accurate for fractions of hours.
   182  			// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
   183  			v += int64(float64(f) * (float64(unit) / scale))
   184  			if v < 0 {
   185  				// overflow
   186  				return 0, errors.New("invalid duration " + orig)
   187  			}
   188  		}
   189  		d += v
   190  		if d < 0 {
   191  			// overflow
   192  			return 0, errors.New("invalid duration " + orig)
   193  		}
   194  	}
   195  
   196  	if neg {
   197  		d = -d
   198  	}
   199  	return Duration(d), nil
   200  }