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

     1  // Copyright (c) 2016 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  package time
    22  
    23  import (
    24  	"errors"
    25  	"sort"
    26  	"time"
    27  )
    28  
    29  // Different time units that are supported.
    30  const (
    31  	// None is a place holder for time units, it doesn't represent an actual time unit. The ordering
    32  	// here is used for comparisons betweens units and should not be changed.
    33  	None Unit = iota
    34  	Second
    35  	Millisecond
    36  	Microsecond
    37  	Nanosecond
    38  	Minute
    39  	Hour
    40  	Day
    41  	Year
    42  )
    43  
    44  var (
    45  	errUnrecognizedTimeUnit  = errors.New("unrecognized time unit")
    46  	errConvertDurationToUnit = errors.New("unable to convert from duration to time unit")
    47  	errConvertUnitToDuration = errors.New("unable to convert from time unit to duration")
    48  	errNegativeDuraton       = errors.New("duration cannot be negative")
    49  )
    50  
    51  // Unit represents a time unit.
    52  type Unit uint16
    53  
    54  // Value is the time duration of the time unit.
    55  func (tu Unit) Value() (time.Duration, error) {
    56  	if tu < 1 || int(tu) >= unitCount {
    57  		return 0, errUnrecognizedTimeUnit
    58  	}
    59  	return unitsToDuration[tu], nil
    60  }
    61  
    62  // Count returns the number of units contained within the duration.
    63  func (tu Unit) Count(d time.Duration) (int, error) {
    64  	if d < 0 {
    65  		return 0, errNegativeDuraton
    66  	}
    67  
    68  	if tu < 1 || int(tu) >= unitCount {
    69  		return 0, errUnrecognizedTimeUnit
    70  	}
    71  
    72  	dur := unitsToDuration[tu]
    73  	return int(d / dur), nil
    74  }
    75  
    76  // MustCount is like Count but panics if d is negative or if tu is not
    77  // a valid Unit.
    78  func (tu Unit) MustCount(d time.Duration) int {
    79  	c, err := tu.Count(d)
    80  	if err != nil {
    81  		panic(err)
    82  	}
    83  
    84  	return c
    85  }
    86  
    87  // IsValid returns whether the given time unit is valid / supported.
    88  func (tu Unit) IsValid() bool {
    89  	return tu > 0 && int(tu) < unitCount
    90  }
    91  
    92  // Validate will validate the time unit.
    93  func (tu Unit) Validate() error {
    94  	if !tu.IsValid() {
    95  		return errUnrecognizedTimeUnit
    96  	}
    97  	return nil
    98  }
    99  
   100  // String returns the string representation for the time unit
   101  func (tu Unit) String() string {
   102  	if tu < 1 || int(tu) >= unitCount {
   103  		return "unknown"
   104  	}
   105  
   106  	return unitStrings[tu]
   107  }
   108  
   109  // UnitFromDuration creates a time unit from a time duration.
   110  func UnitFromDuration(d time.Duration) (Unit, error) {
   111  	i := 0
   112  	// TODO: remove this once we're on go 1.16+, as for loops prevent inlining with older compilers
   113  For:
   114  	if i >= len(unitLookupArray) {
   115  		return None, errConvertDurationToUnit
   116  	}
   117  
   118  	if unitLookupArray[i].duration == d {
   119  		return unitLookupArray[i].unit, nil
   120  	}
   121  	i++
   122  	goto For
   123  }
   124  
   125  // DurationFromUnit creates a time duration from a time unit.
   126  func DurationFromUnit(u Unit) (time.Duration, error) {
   127  	if u < 1 || int(u) >= unitCount {
   128  		return 0, errConvertUnitToDuration
   129  	}
   130  
   131  	return unitsToDuration[u], nil
   132  }
   133  
   134  // MaxUnitForDuration determines the maximum unit for which
   135  // the input duration is a multiple of.
   136  func MaxUnitForDuration(d time.Duration) (int64, Unit) {
   137  	var (
   138  		currMultiple int64
   139  		currUnit     = Nanosecond
   140  		dUnixNanos   = d.Nanoseconds()
   141  		isNegative   bool
   142  	)
   143  	if dUnixNanos < 0 {
   144  		dUnixNanos = -dUnixNanos
   145  		isNegative = true
   146  	}
   147  	for _, u := range unitsByDurationDesc {
   148  		// The unit is guaranteed to be valid so it's safe to ignore error here.
   149  		duration, _ := u.Value()
   150  		if dUnixNanos < duration.Nanoseconds() {
   151  			continue
   152  		}
   153  		durationUnixNanos := int64(duration)
   154  		quotient := dUnixNanos / durationUnixNanos
   155  		remainder := dUnixNanos - quotient*durationUnixNanos
   156  		if remainder != 0 {
   157  			continue
   158  		}
   159  		currMultiple = quotient
   160  		currUnit = u
   161  		break
   162  	}
   163  	if isNegative {
   164  		currMultiple = -currMultiple
   165  	}
   166  	return currMultiple, currUnit
   167  }
   168  
   169  var (
   170  	unitStrings = []string{
   171  		"unknown",
   172  		"s",
   173  		"ms",
   174  		"us",
   175  		"ns",
   176  		"m",
   177  		"h",
   178  		"d",
   179  		"y",
   180  	}
   181  
   182  	// Using an array here to avoid map access cost.
   183  	unitsToDuration = []time.Duration{
   184  		None:        time.Duration(0),
   185  		Second:      time.Second,
   186  		Millisecond: time.Millisecond,
   187  		Microsecond: time.Microsecond,
   188  		Nanosecond:  time.Nanosecond,
   189  		Minute:      time.Minute,
   190  		Hour:        time.Hour,
   191  		Day:         time.Hour * 24,
   192  		Year:        time.Hour * 24 * 365,
   193  	}
   194  
   195  	unitCount = len(unitsToDuration)
   196  
   197  	unitsByDurationDesc []Unit
   198  )
   199  
   200  type unitLookupEntry struct {
   201  	duration time.Duration
   202  	unit     Unit
   203  }
   204  
   205  var unitLookupArray []unitLookupEntry
   206  
   207  // byDurationDesc sorts time units by their durations in descending order.
   208  // The order is undefined if the units are invalid.
   209  type byDurationDesc []Unit
   210  
   211  func (b byDurationDesc) Len() int      { return len(b) }
   212  func (b byDurationDesc) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   213  
   214  func (b byDurationDesc) Less(i, j int) bool {
   215  	vi, _ := b[i].Value()
   216  	vj, _ := b[j].Value()
   217  	return vi > vj
   218  }
   219  
   220  func init() {
   221  	unitsByDurationDesc = make([]Unit, 0, unitCount)
   222  	unitLookupArray = make([]unitLookupEntry, 0, unitCount)
   223  
   224  	for u, d := range unitsToDuration {
   225  		unit := Unit(u)
   226  		if unit == None {
   227  			continue
   228  		}
   229  
   230  		unitLookupArray = append(unitLookupArray, unitLookupEntry{unit: unit, duration: d})
   231  		unitsByDurationDesc = append(unitsByDurationDesc, unit)
   232  	}
   233  	sort.Sort(byDurationDesc(unitsByDurationDesc))
   234  }
   235  
   236  // UnitCount returns the total number of unit types.
   237  func UnitCount() int {
   238  	return unitCount
   239  }