github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/time/duration.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  // Permission is hereby granted, free of charge, to any person obtaining a copy
    22  // of this software and associated documentation files (the "Software"), to deal
    23  // in the Software without restriction, including without limitation the rights
    24  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    25  // copies of the Software, and to permit persons to whom the Software is
    26  // furnished to do so, subject to the following conditions:
    27  //
    28  // The above copyright notice and this permission notice shall be included in
    29  // all copies or substantial portions of the Software.
    30  //
    31  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    32  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    33  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    34  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    35  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    36  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    37  // THE SOFTWARE.
    38  
    39  package time
    40  
    41  import (
    42  	"bytes"
    43  	"errors"
    44  	"fmt"
    45  	"strconv"
    46  	"time"
    47  )
    48  
    49  var errDurationEmpty = errors.New("duration empty")
    50  
    51  var durationUnits = map[string]time.Duration{
    52  	"s":   time.Second,
    53  	"min": time.Minute,
    54  	"m":   time.Minute,
    55  	"h":   time.Hour,
    56  	"d":   time.Hour * 24,
    57  	"w":   time.Hour * 24 * 7,
    58  	"mon": time.Hour * 24 * 30,
    59  	"y":   time.Hour * 24 * 365,
    60  	"us":  time.Microsecond,
    61  	"ms":  time.Millisecond,
    62  	"ns":  time.Nanosecond,
    63  }
    64  
    65  func isDigit(c byte) bool { return c >= '0' && c <= '9' }
    66  
    67  // ParseExtendedDuration parses a duration, with the ability to specify time
    68  // units in days, weeks, months, and years.
    69  func ParseExtendedDuration(s string) (time.Duration, error) {
    70  	if len(s) == 0 {
    71  		return 0, errDurationEmpty
    72  	}
    73  	var isNegative bool
    74  	if s[0] == '-' {
    75  		isNegative = true
    76  		s = s[1:]
    77  	}
    78  
    79  	var d time.Duration
    80  	i := 0
    81  
    82  	for i < len(s) {
    83  		if !isDigit(s[i]) {
    84  			return 0, fmt.Errorf(
    85  				"could not parse duration: duration='%s', err='no value'", s)
    86  		}
    87  
    88  		// Consume [0-9]+
    89  		n := 0
    90  		for i < len(s) && isDigit(s[i]) {
    91  			n *= 10
    92  			n += int(s[i]) - int('0')
    93  			i++
    94  		}
    95  
    96  		// Consume [^0-9]+ and convert into a unit
    97  		if i == len(s) {
    98  			return 0, fmt.Errorf(
    99  				"could not parse duration: duration='%s', err='no unit'", s)
   100  		}
   101  
   102  		unitStart := i
   103  		for i < len(s) && !isDigit(s[i]) {
   104  			i++
   105  		}
   106  
   107  		unitText := s[unitStart:i]
   108  		unit, unitExists := durationUnits[unitText]
   109  		if !unitExists {
   110  			return 0, fmt.Errorf(
   111  				"could not parse duration: duration='%s', err_unknown_unit='%s'", s, unitText)
   112  		}
   113  
   114  		d += time.Duration(n) * unit
   115  
   116  	}
   117  
   118  	if isNegative {
   119  		d = -d
   120  	}
   121  	return d, nil
   122  }
   123  
   124  // ToExtendedString converts a duration to an extended string.
   125  func ToExtendedString(d time.Duration) string {
   126  	if d == 0 {
   127  		return d.String()
   128  	}
   129  	var (
   130  		b          bytes.Buffer
   131  		dUnixNanos = d.Nanoseconds()
   132  	)
   133  	if dUnixNanos < 0 {
   134  		dUnixNanos = -dUnixNanos
   135  		b.WriteString("-")
   136  	}
   137  	for _, u := range unitsByDurationDesc {
   138  		// The unit is guaranteed to be valid so it's safe to ignore error here.
   139  		v, _ := u.Value()
   140  		valueNanos := int64(v)
   141  		if dUnixNanos < valueNanos {
   142  			continue
   143  		}
   144  		quotient := dUnixNanos / valueNanos
   145  		dUnixNanos -= quotient * valueNanos
   146  		b.WriteString(strconv.Itoa(int(quotient)))
   147  		b.WriteString(u.String())
   148  		if dUnixNanos == 0 {
   149  			break
   150  		}
   151  	}
   152  	return b.String()
   153  }