aboutsummaryrefslogtreecommitdiff
path: root/internal/config/units.go
blob: ea61deec0c08117f379d86d6f8e1ed138db7d656 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// SPDX-License-Identifier: GPL-3.0-or-later

package config

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"time"
)

// ParseSize reads a size: a whole number with an optional K, M, G or T
// suffix, in powers of 1024.
func ParseSize(s string) (int64, error) {
	num, mult := s, int64(1)
	if n := len(s); n > 0 {
		switch s[n-1] {
		case 'K':
			mult = 1 << 10
		case 'M':
			mult = 1 << 20
		case 'G':
			mult = 1 << 30
		case 'T':
			mult = 1 << 40
		}
		if mult > 1 {
			num = s[:n-1]
		}
	}
	v, err := parseCount(num)
	if err != nil || v > math.MaxInt64/mult {
		return 0, fmt.Errorf("bad size %q: want a whole number with an optional K, M, G or T, like 50M", s)
	}
	return v * mult, nil
}

var durationUnits = map[byte]time.Duration{
	's': time.Second, 'm': time.Minute, 'h': time.Hour, 'd': 24 * time.Hour, 'w': 7 * 24 * time.Hour,
}

// ParseDuration reads a duration: a whole number followed by s, m, h, d or w.
func ParseDuration(s string) (time.Duration, error) {
	bad := fmt.Errorf("bad duration %q: want a whole number followed by s, m, h, d or w, like 30d", s)
	if len(s) < 2 {
		return 0, bad
	}
	unit, ok := durationUnits[s[len(s)-1]]
	if !ok {
		return 0, bad
	}
	v, err := parseCount(s[:len(s)-1])
	if err != nil || v > int64(math.MaxInt64/unit) {
		return 0, bad
	}
	return time.Duration(v) * unit, nil
}

// parseCount reads a non-empty run of ASCII digits.
func parseCount(s string) (int64, error) {
	if s == "" {
		return 0, errors.New("empty number")
	}
	for _, c := range s {
		if c < '0' || c > '9' {
			return 0, errors.New("not a whole number")
		}
	}
	return strconv.ParseInt(s, 10, 64)
}