github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/strutil/quantity/quantity.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package quantity
    21  
    22  import (
    23  	"fmt"
    24  	"math"
    25  
    26  	"github.com/snapcore/snapd/i18n"
    27  )
    28  
    29  // these are taken from github.com/chipaca/quantity with permission :-)
    30  
    31  func FormatAmount(amount uint64, width int) string {
    32  	if width < 0 {
    33  		width = 5
    34  	}
    35  	max := uint64(5000)
    36  	maxFloat := 999.5
    37  
    38  	if width < 4 {
    39  		width = 3
    40  		max = 999
    41  		maxFloat = 99.5
    42  	}
    43  
    44  	if amount <= max {
    45  		pad := ""
    46  		if width > 5 {
    47  			pad = " "
    48  		}
    49  		return fmt.Sprintf("%*d%s", width-len(pad), amount, pad)
    50  	}
    51  	var prefix rune
    52  	r := float64(amount)
    53  	// zetta and yotta are me being pedantic: maxuint64 is ~18EB
    54  	for _, prefix = range "kMGTPEZY" {
    55  		r /= 1000
    56  		if r < maxFloat {
    57  			break
    58  		}
    59  	}
    60  
    61  	width--
    62  	digits := 3
    63  	if r < 99.5 {
    64  		digits--
    65  		if r < 9.5 {
    66  			digits--
    67  			if r < .95 {
    68  				digits--
    69  			}
    70  		}
    71  	}
    72  	precision := 0
    73  	if (width - digits) > 1 {
    74  		precision = width - digits - 1
    75  	}
    76  
    77  	s := fmt.Sprintf("%*.*f%c", width, precision, r, prefix)
    78  	if r < .95 {
    79  		return s[1:]
    80  	}
    81  	return s
    82  }
    83  
    84  func FormatBPS(n, sec float64, width int) string {
    85  	if sec < 0 {
    86  		sec = -sec
    87  	}
    88  	return FormatAmount(uint64(n/sec), width-2) + "B/s"
    89  }
    90  
    91  const (
    92  	period = 365.25 // julian years (c.f. the actual orbital period, 365.256363004d)
    93  )
    94  
    95  func divmod(a, b float64) (q, r float64) {
    96  	q = math.Floor(a / b)
    97  	return q, a - q*b
    98  }
    99  
   100  var (
   101  	// TRANSLATORS: this needs to be a single rune that is understood to mean "seconds" in e.g. 1m30s
   102  	//    (I fully expect this to always be "s", given it's a SI unit)
   103  	secs = i18n.G("s")
   104  	// TRANSLATORS: this needs to be a single rune that is understood to mean "minutes" in e.g. 1m30s
   105  	mins = i18n.G("m")
   106  	// TRANSLATORS: this needs to be a single rune that is understood to mean "hours" in e.g. 1h30m
   107  	hours = i18n.G("h")
   108  	// TRANSLATORS: this needs to be a single rune that is understood to mean "days" in e.g. 1d20h
   109  	days = i18n.G("d")
   110  	// TRANSLATORS: this needs to be a single rune that is understood to mean "years" in e.g. 1y45d
   111  	years = i18n.G("y")
   112  )
   113  
   114  // dt is seconds (as in the output of time.Now().Seconds())
   115  func FormatDuration(dt float64) string {
   116  	if dt < 60 {
   117  		if dt >= 9.995 {
   118  			return fmt.Sprintf("%.1f%s", dt, secs)
   119  		} else if dt >= .9995 {
   120  			return fmt.Sprintf("%.2f%s", dt, secs)
   121  		}
   122  
   123  		var prefix rune
   124  		for _, prefix = range "mµn" {
   125  			dt *= 1000
   126  			if dt >= .9995 {
   127  				break
   128  			}
   129  		}
   130  
   131  		if dt > 9.5 {
   132  			return fmt.Sprintf("%3.f%c%s", dt, prefix, secs)
   133  		}
   134  
   135  		return fmt.Sprintf("%.1f%c%s", dt, prefix, secs)
   136  	}
   137  
   138  	if dt < 600 {
   139  		m, s := divmod(dt, 60)
   140  		return fmt.Sprintf("%.f%s%02.f%s", m, mins, s, secs)
   141  	}
   142  
   143  	dt /= 60 // dt now minutes
   144  
   145  	if dt < 99.95 {
   146  		return fmt.Sprintf("%3.1f%s", dt, mins)
   147  	}
   148  
   149  	if dt < 10*60 {
   150  		h, m := divmod(dt, 60)
   151  		return fmt.Sprintf("%.f%s%02.f%s", h, hours, m, mins)
   152  	}
   153  
   154  	if dt < 24*60 {
   155  		if h, m := divmod(dt, 60); m < 10 {
   156  			return fmt.Sprintf("%.f%s%1.f%s", h, hours, m, mins)
   157  		}
   158  
   159  		return fmt.Sprintf("%3.1f%s", dt/60, hours)
   160  	}
   161  
   162  	dt /= 60 // dt now hours
   163  
   164  	if dt < 10*24 {
   165  		d, h := divmod(dt, 24)
   166  		return fmt.Sprintf("%.f%s%02.f%s", d, days, h, hours)
   167  	}
   168  
   169  	if dt < 99.95*24 {
   170  		if d, h := divmod(dt, 24); h < 10 {
   171  			return fmt.Sprintf("%.f%s%.f%s", d, days, h, hours)
   172  		}
   173  		return fmt.Sprintf("%4.1f%s", dt/24, days)
   174  	}
   175  
   176  	dt /= 24 // dt now days
   177  
   178  	if dt < 2*period {
   179  		return fmt.Sprintf("%4.0f%s", dt, days)
   180  	}
   181  
   182  	dt /= period // dt now years
   183  
   184  	if dt < 9.995 {
   185  		return fmt.Sprintf("%4.2f%s", dt, years)
   186  	}
   187  
   188  	if dt < 99.95 {
   189  		return fmt.Sprintf("%4.1f%s", dt, years)
   190  	}
   191  
   192  	if dt < 999.5 {
   193  		return fmt.Sprintf("%4.f%s", dt, years)
   194  	}
   195  
   196  	if dt > math.MaxUint64 || uint64(dt) == 0 {
   197  		// TODO: figure out exactly what overflow causes the ==0
   198  		return "ages!"
   199  	}
   200  
   201  	return FormatAmount(uint64(dt), 4) + years
   202  }