mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-03 05:33:30 -04:00
The goutil that OpenCloud currently uses is one version from the release that adds FreeBSD support, this now compiles successfully on FreeBSD.
240 lines
5.4 KiB
Go
240 lines
5.4 KiB
Go
package strutil
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/gookit/goutil/byteutil"
|
|
)
|
|
|
|
var regNumVersion = regexp.MustCompile(`[0-9][\d.]+([_-]\d+)?`)
|
|
|
|
// NumVersion parse input string, get valid number version. eg: go-1.22.3 -> 1.22.3
|
|
func NumVersion(s string) string {
|
|
return regNumVersion.FindString(s)
|
|
}
|
|
|
|
// MustToTime convert date string to time.Time
|
|
func MustToTime(s string, layouts ...string) time.Time {
|
|
t, err := ToTime(s, layouts...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return t
|
|
}
|
|
|
|
// auto match uses some common layouts.
|
|
// key is layout length.
|
|
var layoutMap = map[int][]string{
|
|
6: {"200601", "060102", time.Kitchen},
|
|
8: {"20060102", "06-01-02"},
|
|
10: {"2006-01-02"},
|
|
13: {"2006-01-02 15"},
|
|
15: {time.Stamp},
|
|
16: {"2006-01-02 15:04"},
|
|
19: {"2006-01-02 15:04:05", time.RFC822, time.StampMilli},
|
|
20: {"2006-01-02 15:04:05Z"},
|
|
21: {time.RFC822Z},
|
|
22: {time.StampMicro},
|
|
23: {"2006-01-02 15:04:05.000", "2006-01-02 15:04:05.999"},
|
|
24: {time.ANSIC},
|
|
25: {time.RFC3339, time.StampNano},
|
|
// time.Layout}, // must go >= 1.19
|
|
26: {"2006-01-02 15:04:05.000000"},
|
|
28: {time.UnixDate},
|
|
29: {time.RFC1123, "2006-01-02 15:04:05.000000000"},
|
|
30: {time.RFC850},
|
|
31: {time.RFC1123Z},
|
|
35: {time.RFC3339Nano},
|
|
}
|
|
|
|
// ToTime convert date string to time.Time
|
|
//
|
|
// NOTE: always use local timezone.
|
|
func ToTime(s string, layouts ...string) (t time.Time, err error) {
|
|
// custom layout
|
|
if len(layouts) > 0 {
|
|
if len(layouts[0]) > 0 {
|
|
return time.ParseInLocation(layouts[0], s, time.Local)
|
|
}
|
|
|
|
err = ErrDateLayout
|
|
return
|
|
}
|
|
|
|
// auto match use some commonly layouts.
|
|
strLn := len(s)
|
|
maybeLayouts, ok := layoutMap[strLn]
|
|
if !ok {
|
|
err = ErrInvalidParam
|
|
return
|
|
}
|
|
|
|
var hasAlphaT bool
|
|
if pos := strings.IndexByte(s, 'T'); pos > 0 && pos < 12 {
|
|
hasAlphaT = true
|
|
}
|
|
|
|
hasSlashR := strings.IndexByte(s, '/') > 0
|
|
for _, layout := range maybeLayouts {
|
|
// date string has "T". eg: "2006-01-02T15:04:05"
|
|
if hasAlphaT {
|
|
layout = strings.Replace(layout, " ", "T", 1)
|
|
}
|
|
|
|
// date string has "/". eg: "2006/01/02 15:04:05"
|
|
if hasSlashR {
|
|
layout = strings.Replace(layout, "-", "/", -1)
|
|
}
|
|
|
|
t, err = time.ParseInLocation(layout, s, time.Local)
|
|
if err == nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// t, err = time.ParseInLocation(layout, s, time.Local)
|
|
return
|
|
}
|
|
|
|
// ParseSizeOpt parse size expression options
|
|
type ParseSizeOpt struct {
|
|
// OneAsMax if only one size value, use it as max size. default is false
|
|
OneAsMax bool
|
|
// SepChar is the separator char for time range string. default is '~'
|
|
SepChar byte
|
|
// KeywordFn is the function for parse keyword time string.
|
|
KeywordFn func(string) (min, max uint64, err error)
|
|
}
|
|
|
|
func ensureOpt(opt *ParseSizeOpt) *ParseSizeOpt {
|
|
if opt == nil {
|
|
opt = &ParseSizeOpt{SepChar: '~'}
|
|
} else {
|
|
if opt.SepChar == 0 {
|
|
opt.SepChar = '~'
|
|
}
|
|
}
|
|
return opt
|
|
}
|
|
|
|
// ErrInvalidSizeExpr invalid size expression error
|
|
var ErrInvalidSizeExpr = errors.New("invalid size expr")
|
|
|
|
// ParseSizeRange parse range size expression to min and max size.
|
|
//
|
|
// Expression format:
|
|
//
|
|
// "1KB~2MB" => 1KB to 2MB
|
|
// "-1KB" => <1KB
|
|
// "~1MB" => <1MB
|
|
// "< 1KB" => <1KB
|
|
// "1KB" => >1KB
|
|
// "1KB~" => >1KB
|
|
// ">1KB" => >1KB
|
|
// "+1KB" => >1KB
|
|
func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error) {
|
|
opt = ensureOpt(opt)
|
|
expr = strings.TrimSpace(expr)
|
|
if expr == "" {
|
|
err = ErrInvalidSizeExpr
|
|
return
|
|
}
|
|
|
|
// parse size range. eg: "1KB~2MB"
|
|
if strings.IndexByte(expr, '~') > -1 {
|
|
s1, s2 := TrimCut(expr, "~")
|
|
if s1 != "" {
|
|
min, err = ToByteSize(s1)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if s2 != "" {
|
|
max, err = ToByteSize(s2)
|
|
}
|
|
return
|
|
}
|
|
|
|
// parse single size. eg: "1KB"
|
|
if byteutil.IsNumChar(expr[0]) {
|
|
min, err = ToByteSize(expr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if opt.OneAsMax {
|
|
max = min
|
|
}
|
|
return
|
|
}
|
|
|
|
// parse with prefix. eg: "<1KB", ">= 1KB", "-1KB", "+1KB"
|
|
switch expr[0] {
|
|
case '<', '-':
|
|
max, err = ToByteSize(strings.Trim(expr[1:], " ="))
|
|
case '>', '+':
|
|
min, err = ToByteSize(strings.Trim(expr[1:], " ="))
|
|
default:
|
|
// parse keyword. eg: "small", "large"
|
|
if opt.KeywordFn != nil {
|
|
min, max, err = opt.KeywordFn(expr)
|
|
} else {
|
|
err = ErrInvalidSizeExpr
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// SafeByteSize converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes
|
|
func SafeByteSize(sizeStr string) uint64 {
|
|
size, _ := ToByteSize(sizeStr)
|
|
return size
|
|
}
|
|
|
|
// ToByteSize converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes
|
|
func ToByteSize(sizeStr string) (uint64, error) {
|
|
sizeStr = strings.TrimSpace(sizeStr)
|
|
lastPos := len(sizeStr) - 1
|
|
if lastPos < 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
if sizeStr[lastPos] == 'b' || sizeStr[lastPos] == 'B' {
|
|
// last second char is k,m,g,t
|
|
lastSec := sizeStr[lastPos-1]
|
|
if lastSec > 'A' {
|
|
lastPos--
|
|
}
|
|
} else if IsNumChar(sizeStr[lastPos]) { // not unit suffix. eg: 346
|
|
return strconv.ParseUint(sizeStr, 10, 32)
|
|
}
|
|
|
|
multiplier := float64(1)
|
|
switch unicode.ToLower(rune(sizeStr[lastPos])) {
|
|
case 'k':
|
|
multiplier = 1 << 10
|
|
case 'm':
|
|
multiplier = 1 << 20
|
|
case 'g':
|
|
multiplier = 1 << 30
|
|
case 't':
|
|
multiplier = 1 << 40
|
|
case 'p':
|
|
multiplier = 1 << 50
|
|
default: // b
|
|
multiplier = 1
|
|
}
|
|
|
|
sizeNum := strings.TrimSpace(sizeStr[:lastPos])
|
|
size, err := strconv.ParseFloat(sizeNum, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint64(size * multiplier), nil
|
|
}
|