github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/args/fmt_duration.go (about)

     1  // Copyright 2020 Insolar Network Ltd.
     2  // All rights reserved.
     3  // This material is licensed under the Insolar License version 1.0,
     4  // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md.
     5  
     6  package args
     7  
     8  import (
     9  	"fmt"
    10  	"math"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  /*
    16  	NB! "µs" require 3 bytes, not 2 bytes, hence the difference with "ms" on a number of decimal positions
    17  */
    18  func DurationFixedLen(d time.Duration, expectedLen int) string {
    19  	return durationFixedLen(d, d, expectedLen)
    20  }
    21  
    22  func durationFixedLen(d, base time.Duration, expectedLen int) string {
    23  
    24  	base, baseUnit := metricDurationBase(base)
    25  	if base < time.Minute {
    26  		return fmtMetric(d, base, baseUnit, expectedLen)
    27  	}
    28  
    29  	return fmtAboveSeconds(d, expectedLen)
    30  }
    31  
    32  func fmtMetric(d time.Duration, base time.Duration, baseUnit string, expectedLen int) string {
    33  	w := expectedLen - len(baseUnit)
    34  	v := float64(d) / float64(base)
    35  
    36  	if w < 1 {
    37  		w = 1
    38  	}
    39  	vRounded := math.Round(v)
    40  	if vRounded >= math.Pow10(w) {
    41  		return durationFixedLen(d, base*1000, expectedLen)
    42  	}
    43  
    44  	if w < 3 || vRounded >= math.Pow10(w-2) {
    45  		return fmt.Sprintf("%.0f%s", v, baseUnit)
    46  	}
    47  
    48  	b := strings.Builder{}
    49  	b.Grow(w + len(baseUnit))
    50  
    51  	vInt, vFrac := math.Modf(v)
    52  
    53  	b.WriteString(fmt.Sprintf("%d.", uint64(vInt)))
    54  	decimalCount := w - b.Len()
    55  	decimals := uint64(vFrac * math.Pow10(decimalCount))
    56  	b.WriteString(fmt.Sprintf("%0*d", decimalCount, decimals))
    57  	b.WriteString(baseUnit)
    58  	return b.String()
    59  }
    60  
    61  func metricDurationBase(d time.Duration) (time.Duration, string) {
    62  	if d < 10*time.Minute {
    63  		switch {
    64  		case d > 500*time.Millisecond:
    65  			return time.Second, "s"
    66  		case d > 500*time.Microsecond:
    67  			return time.Millisecond, "ms"
    68  		default:
    69  			return time.Microsecond, "µs"
    70  		}
    71  	}
    72  	return time.Minute, "m"
    73  }
    74  
    75  func fmtAboveSeconds(d time.Duration, expectedLen int) string {
    76  	minutes := d / time.Minute
    77  	seconds := (d - minutes*time.Minute) / time.Second
    78  
    79  	if minutes < 600 {
    80  		return fmtPortions(uint64(seconds), "s", uint64(minutes), "m", expectedLen)
    81  	}
    82  
    83  	hours := minutes / 60
    84  	minutes -= hours * 60
    85  	return fmtPortions(uint64(minutes), "m", uint64(hours), "h", expectedLen)
    86  }
    87  
    88  func fmtPortions(valueLo uint64, unitLo string, valueHi uint64, unitHi string, expectedLen int) string {
    89  	if valueLo > 59 {
    90  		panic("illegal value")
    91  	}
    92  
    93  	switch {
    94  	case valueHi > 0:
    95  		break
    96  	case expectedLen-len(unitLo) < 2:
    97  		if valueLo > 9 {
    98  			if valueLo > 30 {
    99  				valueLo = 0
   100  				valueHi++
   101  			}
   102  			break
   103  		}
   104  		fallthrough
   105  	default:
   106  		return fmt.Sprintf("%d%s", valueLo, unitLo)
   107  	}
   108  
   109  	vHi := fmt.Sprintf("%d%s", valueHi, unitHi)
   110  	if len(vHi)+2+len(unitLo) > expectedLen {
   111  		if valueLo > 30 {
   112  			return fmt.Sprintf("%d%s", valueHi+1, unitHi)
   113  		}
   114  		return vHi
   115  	}
   116  	return vHi + fmt.Sprintf("%02d%s", valueLo, unitLo)
   117  }
   118  
   119  func BitCountToMaxDecimalCount(bitLen int) int {
   120  
   121  	if bitLen == 0 {
   122  		return 0
   123  	}
   124  
   125  	const k = 3321928 // 3.3219280948873623478703194294894 = Ln(10) / Ln(2)
   126  	return int(1 + (uint64(bitLen-1)*1000000)/k)
   127  }