commit 17b49be1367afd20f42d225adf5c657470a66952 Author: upstream Date: Fri Sep 4 09:00:39 2026 +0200 Upstream gookit/goutil state of the touched files before 5ac074d77b diff --git a/strutil/convbase.go b/strutil/convbase.go new file mode 100644 index 0000000..d15fc53 --- /dev/null +++ b/strutil/convbase.go @@ -0,0 +1,108 @@ +package strutil + +import ( + "math/big" + "strconv" + "strings" + + "github.com/gookit/goutil/x/basefn" +) + +// +// -------------------- convert base -------------------- +// + +const ( + Base10Chars = "0123456789" + Base16Chars = "0123456789abcdef" + Base32Chars = "0123456789abcdefghjkmnpqrstvwxyz" + Base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz" + Base48Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL" + Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + Base64Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" +) + +// Base10Conv convert base10 string to new base string. +func Base10Conv(src string, to int) string { return BaseConv(src, 10, to) } + +// BaseConv convert base string by from and to base. +// +// Note: from and to base must be in [2, 64] +// +// Usage: +// +// BaseConv("123", 10, 16) // Output: "7b" +// BaseConv("7b", 16, 10) // Output: "123" +func BaseConv(src string, from, to int) string { + if from > 64 || from < 2 { + from = 10 + } + if to > 64 || to < 2 { + to = 16 + } + return BaseConvByTpl(src, Base64Chars[:from], Base64Chars[:to]) +} + +// BaseConvInt convert base int to new base string. +// +// Usage: +// +// BaseConv(123, 16) // Output: "7b" +func BaseConvInt(src uint64, toBase int) string { + if toBase > 64 || toBase < 2 { + toBase = 16 + } + + // bigInt 支持 2-62 进制转换处理 TODO + if toBase <= 36 { + return strconv.FormatUint(src, toBase) + } + if toBase <= 62 { + bigInt := new(big.Int).SetUint64(src) + return bigInt.Text(toBase) + } + + return BaseConvIntByTpl(src, Base64Chars[:toBase]) +} + +// BaseConvByTpl convert base string by template. +// +// Usage: +// +// BaseConvert("123", Base62Chars, Base16Chars) // Output: "1e" +// BaseConvert("1e", Base16Chars, Base62Chars) // Output: "123" +func BaseConvByTpl(src string, fromBase, toBase string) string { + if fromBase == toBase { + return src + } + + // convert to base 10 + var dec uint64 + if fromBase == Base10Chars { + var err error + dec, err = strconv.ParseUint(src, 10, 0) + if err != nil { + basefn.Panicf("input is not a valid decimal number: %s(%v)", src, err) + } + } else { + fLen := uint64(len(fromBase)) + for _, c := range src { + dec = dec*fLen + uint64(strings.IndexRune(fromBase, c)) + } + } + + // convert to new base + return BaseConvIntByTpl(dec, toBase) +} + +// BaseConvIntByTpl convert base int to new base string. +func BaseConvIntByTpl(dec uint64, toBase string) string { + // convert to new base + var res string + toLen := uint64(len(toBase)) + for dec > 0 { + res = string(toBase[dec%toLen]) + res + dec /= toLen + } + return res +} diff --git a/strutil/convbase_test.go b/strutil/convbase_test.go new file mode 100644 index 0000000..86d9052 --- /dev/null +++ b/strutil/convbase_test.go @@ -0,0 +1,83 @@ +package strutil_test + +import ( + "testing" + + "github.com/gookit/goutil/strutil" + "github.com/gookit/goutil/x/assert" +) + +func TestBaseConv(t *testing.T) { + tests := []struct { + give string + from int + to int + want string + }{ + // 10 -> 16 + {"123", 10, 16, "7b"}, + {"7b", 16, 10, "123"}, + // 10 -> 32 + {"123", 10, 32, "3r"}, + {"3r", 32, 10, "123"}, + // 10 -> 62 + {"123", 10, 62, "1Z"}, + {"1Z", 62, 10, "123"}, + // 10 -> 64 + {"123", 10, 64, "1X"}, + {"1X", 64, 10, "123"}, + } + + for _, tt := range tests { + t.Run(tt.give, func(t *testing.T) { + got := strutil.BaseConv(tt.give, tt.from, tt.to) + if got != tt.want { + t.Errorf("BaseConv() got = %v, want %v; give=%s", got, tt.want, tt.give) + } + }) + } + + assert.Eq(t, "7b", strutil.BaseConvInt(123, 16)) + assert.Eq(t, "2f", strutil.BaseConvInt(123, 54)) + assert.Eq(t, "1X", strutil.BaseConvInt(123, 64)) +} + +func TestBase10Conv(t *testing.T) { + // fmt.Println(time.Now().Format("20060102150405")) + date := "20230829194900" // seconds + t.Run("date sec", func(t *testing.T) { + base16 := strutil.Base10Conv(date, 16) + assert.Eq(t, "12665b633e94", base16) + + base32 := strutil.Base10Conv(date, 32) + assert.Eq(t, "icpdm6fkk", base32) + + base62 := strutil.Base10Conv(date, 62) + assert.Eq(t, "5KaR3zRW", base62) + + base64 := strutil.Base10Conv(date, 64) + assert.Eq(t, "4CproPWk", base64) + }) + + // fmt.Println(time.Now().Format("20060102150405.000")) + msDate := "20230829195105843" + t.Run("date ms", func(t *testing.T) { + base16 := strutil.Base10Conv(msDate, 16) + assert.Eq(t, "47dfd4fbaf9633", base16) + + base32 := strutil.Base10Conv(msDate, 32) + assert.Eq(t, "huvqjtqv5hj", base32) + + base62 := strutil.Base10Conv(msDate, 62) + assert.Eq(t, "1uEL5LJptx", base62) + + base64 := strutil.Base10Conv(msDate, 64) + assert.Eq(t, "17TZjXHVoP", base64) + }) + + t.Run("panic", func(t *testing.T) { + assert.Panics(t, func() { + strutil.Base10Conv("invalid", 16) + }) + }) +}