From 972a08a2d49012c5c91744f27cff419dc81b9736 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 3 Oct 2023 09:46:43 +0700 Subject: [PATCH 1/2] add some time util --- utils/time_util.go | 37 ++++++++++++++++++- utils/time_util_test.go | 78 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/utils/time_util.go b/utils/time_util.go index 727271c..e472624 100644 --- a/utils/time_util.go +++ b/utils/time_util.go @@ -1,6 +1,10 @@ package utils -import "time" +import ( + "fmt" + "math" + "time" +) // NowS returns the current epoch seconds func NowS() int64 { @@ -21,3 +25,34 @@ func DiffS(previous int64) int64 { func DiffMs(previous int64) int64 { return NowMs() - previous } + +// GetLocationFromUtcTimezone returns location corresponding to specified UTC-based timezone +func GetLocationFromUtcTimezone(utcTimezone int) *time.Location { + ensureUtcTimezone(utcTimezone) + return time.FixedZone(GetUtcName(utcTimezone), utcTimezone*60*60) +} + +// GetUtcName returns naming convention of UTC timezone. Eg: 7 => UTC+0700 +func GetUtcName(utcTimezone int) string { + ensureUtcTimezone(utcTimezone) + return fmt.Sprintf("UTC%s", getTimezoneSuffix(utcTimezone)) +} + +// ensureUtcTimezone will panic if timezone is out of range from -12 to 14 +func ensureUtcTimezone(utcTimezone int) { + if utcTimezone < -12 || utcTimezone > 14 { + panic(fmt.Errorf("UTC timezone must be in range -12 to 14")) + } +} + +func getTimezoneSuffix(timezone int) string { + if timezone > 9 { + return fmt.Sprintf("+%d00", timezone) + } else if timezone >= 0 { + return fmt.Sprintf("+0%d00", timezone) + } else if timezone >= -9 { + return fmt.Sprintf("-0%d00", int(math.Abs(float64(timezone)))) + } else { + return fmt.Sprintf("%d00", timezone) + } +} diff --git a/utils/time_util_test.go b/utils/time_util_test.go index c3137ff..f0d3e59 100644 --- a/utils/time_util_test.go +++ b/utils/time_util_test.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + "github.com/EscanBE/go-lib/test_utils" "testing" "time" ) @@ -45,3 +46,80 @@ func TestNowS(t *testing.T) { t.Errorf("NowS() = %v, expected %v +-1!", nowS1, nowS2) } } + +func TestGetLocationFromUtcTimezone(t *testing.T) { + t.Run("get location for UTC from -12 to 14", func(_ *testing.T) { + for timezone := -12; timezone <= 14; timezone++ { + loc := GetLocationFromUtcTimezone(timezone) + nowUTC := time.Date(2023, 9, 16, 0, 0, 0, 0, time.UTC) + nowWithCustomLoc := nowUTC.In(loc) + diffHours := (nowWithCustomLoc.Hour() + 24*nowWithCustomLoc.Day()) - (nowUTC.Hour() + 24*nowUTC.Day()) + if diffHours != timezone { + t.Errorf("Expected diff timezone %d but got %d", timezone, diffHours) + } + } + }) + + for timezone := -100; timezone <= 100; timezone++ { + wantPanic := timezone < -12 || timezone > 14 + t.Run(fmt.Sprintf("timezone %d %s", timezone, func() string { + if wantPanic { + return "should panic" + } else { + return "should not panic" + } + }()), func(_ *testing.T) { + defer test_utils.DeferWantPanicDepends(t, wantPanic) + _ = GetLocationFromUtcTimezone(timezone) + }) + } +} + +func TestGetUtcName(t *testing.T) { + tests := []struct { + utcTimezone int + want string + }{ + { + utcTimezone: -12, + want: "UTC-1200", + }, + { + utcTimezone: -1, + want: "UTC-0100", + }, + { + utcTimezone: 0, + want: "UTC+0000", + }, + { + utcTimezone: 1, + want: "UTC+0100", + }, + { + utcTimezone: 14, + want: "UTC+1400", + }, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := GetUtcName(tt.utcTimezone); got != tt.want { + t.Errorf("GetUtcName() = %v, want %v", got, tt.want) + } + }) + } + + for timezone := -100; timezone <= 100; timezone++ { + wantPanic := timezone < -12 || timezone > 14 + t.Run(fmt.Sprintf("timezone %d %s", timezone, func() string { + if wantPanic { + return "should panic" + } else { + return "should not panic" + } + }()), func(_ *testing.T) { + defer test_utils.DeferWantPanicDepends(t, wantPanic) + _ = GetUtcName(timezone) + }) + } +} From c6f6c90d05c5ef770272fe0f5de6eced5b51d313 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Tue, 3 Oct 2023 15:13:05 +0700 Subject: [PATCH 2/2] add more utils --- app/exit.go | 7 +++--- utils/error_util.go | 20 +++++++++++++--- utils/hash_util.go | 8 +++++++ utils/hash_util_test.go | 29 +++++++++++++++------- utils/ibc_util.go | 11 +++++++++ utils/ibc_util_test.go | 33 +++++++++++++++++++++++++ utils/time_util.go | 10 ++++++++ utils/time_util_test.go | 53 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 156 insertions(+), 15 deletions(-) create mode 100644 utils/ibc_util.go create mode 100644 utils/ibc_util_test.go diff --git a/app/exit.go b/app/exit.go index 5caaa9e..f9cbb5a 100644 --- a/app/exit.go +++ b/app/exit.go @@ -3,6 +3,7 @@ package app import ( "fmt" "github.com/EscanBE/go-lib/logging" + "github.com/EscanBE/go-lib/utils" ) // AppExitFunction is an alias of function which receives params @@ -36,20 +37,20 @@ func TryRecoverAndExecuteExitFunctionIfRecovered(logger logging.Logger, exitFunc if logger != nil { logger.Error("Panic caught", "error", err) } else { - fmt.Printf("Panic caught, err: %v\n", err) + utils.PrintfStdErr("Panic caught, err: %v\n", err) } panic(err) } else { if logger != nil { logger.Error("Recovered from panic, executing exit function") } else { - fmt.Println("Recovered from panic, executing exit function") + utils.PrintfStdErr("Recovered from panic, executing exit function") } ExecuteExitFunction(exitFuncParams...) if logger != nil { logger.Error("Executed exit function, going to panic using recovered error") } else { - fmt.Println("Executed exit function, going to panic using recovered error") + utils.PrintfStdErr("Executed exit function, going to panic using recovered error") } panic(err) } diff --git a/utils/error_util.go b/utils/error_util.go index 1b5e312..3ae7d8d 100644 --- a/utils/error_util.go +++ b/utils/error_util.go @@ -13,8 +13,7 @@ func ExitIfErr(err error, msg string) { if err == nil { return } - fmt.Printf("Exit with error: %s\n", msg) - fmt.Println(err) + PrintlnStdErr("Exit with error:", msg, "\n", err) osExit(1) } @@ -23,7 +22,7 @@ func PanicIfErr(err error, msg string) { if err == nil { return } - fmt.Printf("Exit with error: %s\n", msg) + PrintlnStdErr("Exit with error:", msg) panic(err) } @@ -34,3 +33,18 @@ func NilOrWrapIfError(err error, msg string) error { } return errors.Wrap(err, msg) } + +// PrintlnStdErr does println to StdErr +func PrintlnStdErr(a ...any) { + fmt.Fprintln(os.Stderr, a...) +} + +// PrintfStdErr does printf to StdErr +func PrintfStdErr(format string, a ...any) { + fmt.Fprintf(os.Stderr, format, a...) +} + +// PrintStdErr does print to StdErr +func PrintStdErr(a ...any) { + fmt.Fprint(os.Stderr, a...) +} diff --git a/utils/hash_util.go b/utils/hash_util.go index 3220c5c..f8e1db6 100644 --- a/utils/hash_util.go +++ b/utils/hash_util.go @@ -1,10 +1,18 @@ package utils import ( + "crypto/sha256" "encoding/hex" + "fmt" "github.com/ethereum/go-ethereum/crypto" ) +// Keccak256Hash computes and returns Keccak-256 value of an input string func Keccak256Hash(input string) string { return hex.EncodeToString(crypto.Keccak256Hash([]byte(input)).Bytes()) } + +// Sha256 returns SHA256 checksum string value of an input string +func Sha256(input string) string { + return fmt.Sprintf("%x", sha256.Sum256([]byte(input))) +} diff --git a/utils/hash_util_test.go b/utils/hash_util_test.go index 64633a1..da24700 100644 --- a/utils/hash_util_test.go +++ b/utils/hash_util_test.go @@ -2,24 +2,35 @@ package utils import "testing" -func TestKeccak256Hash(t *testing.T) { +func Test256Hashing(t *testing.T) { tests := []struct { - input string - want string + input string + wantKeccak256 string + wantSha256 string }{ { - input: "abcd", - want: "48bed44d1bcd124a28c27f343a817e5f5243190d3c52bf347daf876de1dbbf77", + input: "abc", + wantKeccak256: "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", + wantSha256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", }, { - input: "", - want: "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + input: "abcd", + wantKeccak256: "48bed44d1bcd124a28c27f343a817e5f5243190d3c52bf347daf876de1dbbf77", + wantSha256: "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", + }, + { + input: "", + wantKeccak256: "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + wantSha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", }, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { - if got := Keccak256Hash(tt.input); got != tt.want { - t.Errorf("Keccak256Hash() = %v, want %v", got, tt.want) + if got1 := Keccak256Hash(tt.input); got1 != tt.wantKeccak256 { + t.Errorf("Keccak256Hash() = %v, want %v", got1, tt.wantKeccak256) + } + if got2 := Sha256(tt.input); got2 != tt.wantSha256 { + t.Errorf("Sha256() = %v, want %v", got2, tt.wantSha256) } }) } diff --git a/utils/ibc_util.go b/utils/ibc_util.go new file mode 100644 index 0000000..c374826 --- /dev/null +++ b/utils/ibc_util.go @@ -0,0 +1,11 @@ +package utils + +import ( + "fmt" + "strings" +) + +// BuildIbcDenom returns IBC denom based on algorithm provided by IBC docs: Sha256(path/baseDenom) +func BuildIbcDenom(path, baseDenom string) string { + return fmt.Sprintf("ibc/%s", strings.ToUpper(Sha256(fmt.Sprintf("%s/%s", path, baseDenom)))) +} diff --git a/utils/ibc_util_test.go b/utils/ibc_util_test.go new file mode 100644 index 0000000..90e219b --- /dev/null +++ b/utils/ibc_util_test.go @@ -0,0 +1,33 @@ +package utils + +import ( + "fmt" + "testing" +) + +func TestBuildIbcDenom(t *testing.T) { + //goland:noinspection SpellCheckingInspection + tests := []struct { + path string + baseDenom string + want string + }{ + { + path: "transfer/channel-0", + baseDenom: "uosmo", + want: "ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518", + }, + { + path: "transfer/channel-3", + baseDenom: "uatom", + want: "ibc/A4DB47A9D3CF9A068D454513891B526702455D3EF08FB9EB558C561F9DC2B701", + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%s/%s", tt.path, tt.baseDenom), func(t *testing.T) { + if got := BuildIbcDenom(tt.path, tt.baseDenom); got != tt.want { + t.Errorf("BuildIbcDenom() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/utils/time_util.go b/utils/time_util.go index e472624..0a7393f 100644 --- a/utils/time_util.go +++ b/utils/time_util.go @@ -26,6 +26,16 @@ func DiffMs(previous int64) int64 { return NowMs() - previous } +// IsTimeNear returns true when t1 and t2, are the same time (maximum diff is offset), regardless timezone. +// Eg: 03:00:00 UTC+0000 is equals to 10:00:00 UTC+0700 +func IsTimeNear(t1, t2 time.Time, offsetDuration time.Duration) bool { + t1 = t1.UTC() + t2 = t2.UTC() + + diff := t1.Sub(t2) + return offsetDuration.Abs() >= diff.Abs() +} + // GetLocationFromUtcTimezone returns location corresponding to specified UTC-based timezone func GetLocationFromUtcTimezone(utcTimezone int) *time.Location { ensureUtcTimezone(utcTimezone) diff --git a/utils/time_util_test.go b/utils/time_util_test.go index f0d3e59..48bb7e9 100644 --- a/utils/time_util_test.go +++ b/utils/time_util_test.go @@ -123,3 +123,56 @@ func TestGetUtcName(t *testing.T) { }) } } + +func TestIsTimeNear(t *testing.T) { + nowUTC := time.Now().UTC() + nowUS := nowUTC.In(GetLocationFromUtcTimezone(-7)) + + makeTimeUTC := func(shiftSeconds int) time.Time { + return nowUTC.Add(time.Duration(shiftSeconds) * time.Second) + } + + tests := []struct { + shiftSeconds int + timeFrame time.Time + offsetDuration time.Duration + want bool + }{ + { + shiftSeconds: 10, + offsetDuration: 10 * time.Second, + want: true, + }, + { + shiftSeconds: 9, + offsetDuration: 10 * time.Second, + want: true, + }, + { + shiftSeconds: 11, + offsetDuration: 10 * time.Second, + want: false, + }, + } + for i, tt := range tests { + timeFrameUTC := makeTimeUTC(tt.shiftSeconds) + + t.Run(fmt.Sprintf("[%d] UTC %v vs %v", i, nowUTC, timeFrameUTC), func(t *testing.T) { + if got := IsTimeNear(nowUTC, timeFrameUTC, tt.offsetDuration); got != tt.want { + t.Errorf("IsMatchTimeFrame() = %v, want %v", got, tt.want) + } + }) + + t.Run(fmt.Sprintf("[%d] AnyZone %v vs %v", i, nowUS, timeFrameUTC), func(t *testing.T) { + if got := IsTimeNear(nowUS, timeFrameUTC, tt.offsetDuration); got != tt.want { + t.Errorf("IsMatchTimeFrame() = %v, want %v", got, tt.want) + } + }) + + t.Run(fmt.Sprintf("[%d] Change position", i), func(t *testing.T) { + if got := IsTimeNear(timeFrameUTC, nowUS, tt.offsetDuration); got != tt.want { + t.Errorf("IsMatchTimeFrame() = %v, want %v", got, tt.want) + } + }) + } +}