Skip to content

Commit f6ddce3

Browse files
muXxeraldas
authored andcommitted
Rename binary units according to IEC 60027 and add decimal units (SI)
1 parent 8c098ec commit f6ddce3

File tree

2 files changed

+375
-75
lines changed

2 files changed

+375
-75
lines changed

bytes/bytes.go

Lines changed: 160 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,66 +10,151 @@ import (
1010
type (
1111
// Bytes struct
1212
Bytes struct{}
13+
14+
// PrefixType is the type of the unit prefix (binary/decimal)
15+
PrefixType byte
16+
)
17+
18+
const (
19+
// IEC 60027
20+
PrefixTypeBinary PrefixType = iota
21+
// SI international system of units
22+
PrefixTypeDecimal
1323
)
1424

25+
// binary units (IEC 60027)
1526
const (
1627
_ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier
17-
KB
18-
MB
19-
GB
20-
TB
21-
PB
22-
EB
28+
KiB
29+
MiB
30+
GiB
31+
TiB
32+
PiB
33+
EiB
34+
)
35+
36+
// decimal units (SI international system of units)
37+
const (
38+
KB = 1000
39+
MB = KB * 1000
40+
GB = MB * 1000
41+
TB = GB * 1000
42+
PB = TB * 1000
43+
EB = PB * 1000
2344
)
2445

2546
var (
26-
pattern = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)\s?([KMGTPE]B?|B?)$`)
27-
global = New()
47+
patternBinary = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)\s?([KMGTPE]iB?)$`)
48+
patternDecimal = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)\s?([KMGTPE]B?|B?)$`)
49+
global = New()
2850
)
2951

3052
// New creates a Bytes instance.
3153
func New() *Bytes {
3254
return &Bytes{}
3355
}
3456

35-
// Format formats bytes integer to human readable string.
57+
// Format formats bytes integer to human readable string according to the given prefix type.
58+
// If prefixType is not passed, binary prefix is used.
59+
func (b *Bytes) Format(value int64, prefixType ...PrefixType) string {
60+
61+
if len(prefixType) > 0 {
62+
switch prefixType[0] {
63+
case PrefixTypeBinary:
64+
return b.FormatBinary(value)
65+
case PrefixTypeDecimal:
66+
return b.FormatDecimal(value)
67+
}
68+
}
69+
70+
return b.FormatBinary(value)
71+
}
72+
73+
// FormatBinary formats bytes integer to human readable string according to IEC 60027.
3674
// For example, 31323 bytes will return 30.59KB.
37-
func (*Bytes) Format(b int64) string {
75+
func (*Bytes) FormatBinary(value int64) string {
3876
multiple := ""
39-
value := float64(b)
77+
val := float64(value)
4078

4179
switch {
42-
case b >= EB:
43-
value /= EB
80+
case value >= EiB:
81+
val /= EiB
82+
multiple = "EiB"
83+
case value >= PiB:
84+
val /= PiB
85+
multiple = "PiB"
86+
case value >= TiB:
87+
val /= TiB
88+
multiple = "TiB"
89+
case value >= GiB:
90+
val /= GiB
91+
multiple = "GiB"
92+
case value >= MiB:
93+
val /= MiB
94+
multiple = "MiB"
95+
case value >= KiB:
96+
val /= KiB
97+
multiple = "KiB"
98+
case value == 0:
99+
return "0"
100+
default:
101+
return strconv.FormatInt(value, 10) + "B"
102+
}
103+
104+
return fmt.Sprintf("%.2f%s", val, multiple)
105+
}
106+
107+
// FormatDecimal formats bytes integer to human readable string according to SI international system of units.
108+
// For example, 31323 bytes will return 31.32KB.
109+
func (*Bytes) FormatDecimal(value int64) string {
110+
multiple := ""
111+
val := float64(value)
112+
113+
switch {
114+
case value >= EB:
115+
val /= EB
44116
multiple = "EB"
45-
case b >= PB:
46-
value /= PB
117+
case value >= PB:
118+
val /= PB
47119
multiple = "PB"
48-
case b >= TB:
49-
value /= TB
120+
case value >= TB:
121+
val /= TB
50122
multiple = "TB"
51-
case b >= GB:
52-
value /= GB
123+
case value >= GB:
124+
val /= GB
53125
multiple = "GB"
54-
case b >= MB:
55-
value /= MB
126+
case value >= MB:
127+
val /= MB
56128
multiple = "MB"
57-
case b >= KB:
58-
value /= KB
129+
case value >= KB:
130+
val /= KB
59131
multiple = "KB"
60-
case b == 0:
132+
case value == 0:
61133
return "0"
62134
default:
63-
return strconv.FormatInt(b, 10) + "B"
135+
return strconv.FormatInt(value, 10) + "B"
64136
}
65137

66-
return fmt.Sprintf("%.2f%s", value, multiple)
138+
return fmt.Sprintf("%.2f%s", val, multiple)
67139
}
68140

69141
// Parse parses human readable bytes string to bytes integer.
70-
// For example, 6GB (6G is also valid) will return 6442450944.
71-
func (*Bytes) Parse(value string) (i int64, err error) {
72-
parts := pattern.FindStringSubmatch(value)
142+
// For example, 6GiB (6Gi is also valid) will return 6442450944, and
143+
// 6GB (6G is also valid) will return 6000000000.
144+
func (b *Bytes) Parse(value string) (int64, error) {
145+
146+
i, err := b.ParseBinary(value)
147+
if err == nil {
148+
return i, err
149+
}
150+
151+
return b.ParseDecimal(value)
152+
}
153+
154+
// ParseBinary parses human readable bytes string to bytes integer.
155+
// For example, 6GiB (6Gi is also valid) will return 6442450944.
156+
func (*Bytes) ParseBinary(value string) (i int64, err error) {
157+
parts := patternBinary.FindStringSubmatch(value)
73158
if len(parts) < 3 {
74159
return 0, fmt.Errorf("error parsing value=%s", value)
75160
}
@@ -81,8 +166,38 @@ func (*Bytes) Parse(value string) (i int64, err error) {
81166
}
82167

83168
switch multiple {
169+
case "KI", "KIB":
170+
return int64(bytes * KiB), nil
171+
case "MI", "MIB":
172+
return int64(bytes * MiB), nil
173+
case "GI", "GIB":
174+
return int64(bytes * GiB), nil
175+
case "TI", "TIB":
176+
return int64(bytes * TiB), nil
177+
case "PI", "PIB":
178+
return int64(bytes * PiB), nil
179+
case "EI", "EIB":
180+
return int64(bytes * EiB), nil
84181
default:
85182
return int64(bytes), nil
183+
}
184+
}
185+
186+
// ParseDecimal parses human readable bytes string to bytes integer.
187+
// For example, 6GB (6G is also valid) will return 6000000000.
188+
func (*Bytes) ParseDecimal(value string) (i int64, err error) {
189+
parts := patternDecimal.FindStringSubmatch(value)
190+
if len(parts) < 3 {
191+
return 0, fmt.Errorf("error parsing value=%s", value)
192+
}
193+
bytesString := parts[1]
194+
multiple := strings.ToUpper(parts[2])
195+
bytes, err := strconv.ParseFloat(bytesString, 64)
196+
if err != nil {
197+
return
198+
}
199+
200+
switch multiple {
86201
case "K", "KB":
87202
return int64(bytes * KB), nil
88203
case "M", "MB":
@@ -95,15 +210,27 @@ func (*Bytes) Parse(value string) (i int64, err error) {
95210
return int64(bytes * PB), nil
96211
case "E", "EB":
97212
return int64(bytes * EB), nil
213+
default:
214+
return int64(bytes), nil
98215
}
99216
}
100217

101218
// Format wraps global Bytes's Format function.
102-
func Format(b int64) string {
103-
return global.Format(b)
219+
func Format(value int64, prefixType ...PrefixType) string {
220+
return global.Format(value, prefixType...)
221+
}
222+
223+
// FormatBinary wraps global Bytes's FormatBinary function.
224+
func FormatBinary(value int64) string {
225+
return global.FormatBinary(value)
226+
}
227+
228+
// FormatDecimal wraps global Bytes's FormatDecimal function.
229+
func FormatDecimal(value int64) string {
230+
return global.FormatDecimal(value)
104231
}
105232

106233
// Parse wraps global Bytes's Parse function.
107-
func Parse(val string) (int64, error) {
108-
return global.Parse(val)
234+
func Parse(value string) (int64, error) {
235+
return global.Parse(value)
109236
}

0 commit comments

Comments
 (0)