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 }