From 214580826827b3d2d1c4bf6f7bf7c590fcb2be85 Mon Sep 17 00:00:00 2001 From: dudaodong Date: Mon, 9 Sep 2024 14:33:15 +0800 Subject: [PATCH] feat: add new functions in datetime and strutils --- README.md | 5 ++ README_zh-CN.md | 5 ++ datetime/datetime.go | 60 +++++++++++++++++++++ datetime/datetime_test.go | 111 ++++++++++++++++++++++++++++++++++++++ docs/datetime.md | 106 ++++++++++++++++++++++++++++++++++++ docs/datetime_zh-CN.md | 108 +++++++++++++++++++++++++++++++++++++ docs/strutil.md | 71 +++++++++++++++++++++++- docs/strutil_zh-CN.md | 69 ++++++++++++++++++++++++ strutil/string.go | 29 ++++++++++ strutil/string_test.go | 87 ++++++++++++++++++++++++++++++ 10 files changed, 650 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c5318a..3a4763c 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,9 @@ import "github.com/duke-git/lancet/datetime" - [TimestampMilli](https://github.com/duke-git/lancet/blob/v1/docs/datetime.md#TimestampMilli) - [TimestampMicro](https://github.com/duke-git/lancet/blob/v1/docs/datetime.md#TimestampMicro) - [TimestampNano](https://github.com/duke-git/lancet/blob/v1/docs/datetime.md#TimestampNano) +- [TrackFuncTime](https://github.com/duke-git/lancet/blob/v1/docs/datetime.md#TrackFuncTime) +- [DaysBetween](https://github.com/duke-git/lancet/blob/v1/docs/datetime.md#DaysBetween) +- [GenerateDatetimesBetween](https://github.com/duke-git/lancet/blob/v1/docs/datetime.md#GenerateDatetimesBetween) ### 5. Fileutil package implements some basic functions for file operations. @@ -478,6 +481,8 @@ import "github.com/duke-git/lancet/strutil" - [Ellipsis](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Ellipsis) - [Shuffle](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Shuffle) - [Rotate](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#Rotate) +- [TemplateReplace](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#TemplateReplace) +- [RegexMatchAllGroups](https://github.com/duke-git/lancet/blob/v1/docs/strutil.md#RegexMatchAllGroups) ### 14. System package contain some functions about os, runtime, shell command. diff --git a/README_zh-CN.md b/README_zh-CN.md index c022be1..539db59 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -213,6 +213,9 @@ import "github.com/duke-git/lancet/datetime" - [TimestampMilli](https://github.com/duke-git/lancet/blob/v1/docs/datetime_zh-CN.md#TimestampMilli) - [TimestampMicro](https://github.com/duke-git/lancet/blob/v1/docs/datetime_zh-CN.md#TimestampMicro) - [TimestampNano](https://github.com/duke-git/lancet/blob/v1/docs/datetime_zh-CN.md#TimestampNano) +- [TrackFuncTime](https://github.com/duke-git/lancet/blob/v1/docs/datetime_zh-CN.md#TrackFuncTime) +- [DaysBetween](https://github.com/duke-git/lancet/blob/v1/docs/datetime_zh-CN.md#DaysBetween) +- [GenerateDatetimesBetween](https://github.com/duke-git/lancet/blob/v1/docs/datetime_zh-CN.md#GenerateDatetimesBetween) ### 5. fileutil 包支持文件基本操作。 @@ -480,6 +483,8 @@ import "github.com/duke-git/lancet/strutil" - [Ellipsis](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Ellipsis) - [Shuffle](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Shuffle) - [Rotate](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#Rotate) +- [TemplateReplace](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#TemplateReplace) +- [RegexMatchAllGroups](https://github.com/duke-git/lancet/blob/v1/docs/strutil_zh-CN.md#RegexMatchAllGroups) ### 14. system 包含 os, runtime, shell command 相关函数。 diff --git a/datetime/datetime.go b/datetime/datetime.go index 9c5bcc7..64f98ab 100644 --- a/datetime/datetime.go +++ b/datetime/datetime.go @@ -31,6 +31,7 @@ package datetime import ( "fmt" + "runtime" "strings" "time" ) @@ -321,3 +322,62 @@ func TimestampNano(timezone ...string) int64 { return t.UnixNano() } + +// TrackFuncTime track the time of function execution. +// call it at top of the func like `defer TrackFuncTime(time.Now())()` +// Play: todo +func TrackFuncTime(pre time.Time) func() { + callerName := getCallerName() + return func() { + elapsed := time.Since(pre) + fmt.Printf("Function %s execution time:\t %v", callerName, elapsed) + } +} + +func getCallerName() string { + pc, _, _, ok := runtime.Caller(2) + if !ok { + return "Unknown" + } + fn := runtime.FuncForPC(pc) + if fn == nil { + return "Unknown" + } + + fullName := fn.Name() + if lastDot := strings.LastIndex(fullName, "."); lastDot != -1 { + return fullName[lastDot+1:] + } + + return fullName +} + +// DaysBetween returns the number of days between two times. +func DaysBetween(start, end time.Time) int { + duration := end.Sub(start) + days := int(duration.Hours() / 24) + + return days +} + +// GenerateDatetimesBetween returns a slice of strings between two times. +// layout: the format of the datetime string +// interval: the interval between two datetimes +func GenerateDatetimesBetween(start, end time.Time, layout string, interval string) ([]string, error) { + var result []string + + if start.After(end) { + start, end = end, start + } + + duration, err := time.ParseDuration(interval) + if err != nil { + return nil, err + } + + for current := start; !current.After(end); current = current.Add(duration) { + result = append(result, current.Format(layout)) + } + + return result, nil +} diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go index 781cb43..40dcbab 100644 --- a/datetime/datetime_test.go +++ b/datetime/datetime_test.go @@ -333,3 +333,114 @@ func TestTimestamp(t *testing.T) { ts4 := TimestampNano() t.Log(ts4) } + +func TestTrackFuncTime(t *testing.T) { + defer TrackFuncTime(time.Now())() + + var n int + for i := 0; i < 5000000; i++ { + n++ + } +} + +func TestDaysBetween(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestDaysBetween") + + tests := []struct { + start time.Time + end time.Time + expected int + }{ + { + start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC), + expected: 9, + }, + { + start: time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + expected: -9, + }, + { + start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + expected: 0, + }, + { + start: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.December, 31, 0, 0, 0, 0, time.UTC), + expected: 365, + }, + { + start: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC), + expected: 30, + }, + } + + for _, tt := range tests { + result := DaysBetween(tt.start, tt.end) + assert.Equal(tt.expected, result) + } +} + +func TestGenerateDatetimesBetween(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestGenerateDatetimesBetween") + + tests := []struct { + start time.Time + end time.Time + layout string + interval string + expected []string + }{ + { + start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.September, 1, 2, 0, 0, 0, time.UTC), + layout: "2006-01-02 15:04:05", + interval: "30m", + expected: []string{ + "2024-09-01 00:00:00", + "2024-09-01 00:30:00", + "2024-09-01 01:00:00", + "2024-09-01 01:30:00", + "2024-09-01 02:00:00", + }, + }, + { + start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + layout: "2006-01-02 15:04:05", + interval: "1h", + expected: []string{"2024-09-01 00:00:00"}, + }, + { + start: time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, time.September, 1, 3, 0, 0, 0, time.UTC), + layout: "2006-01-02 15:04:05", + interval: "2h", + expected: []string{ + "2024-09-01 00:00:00", + "2024-09-01 02:00:00", + }, + }, + } + + for _, tt := range tests { + result, err := GenerateDatetimesBetween(tt.start, tt.end, tt.layout, tt.interval) + + assert.Equal(tt.expected, result) + assert.IsNil(err) + } + + t.Run("Invalid interval", func(t *testing.T) { + _, err := GenerateDatetimesBetween(time.Now(), time.Now(), "2006-01-02 15:04:05", "invalid") + if err == nil { + t.Fatal("Expected error, got nil") + } + }) +} diff --git a/docs/datetime.md b/docs/datetime.md index 1ad0f47..55b9b90 100644 --- a/docs/datetime.md +++ b/docs/datetime.md @@ -62,6 +62,9 @@ import ( - [TimestampMilli](#TimestampMilli) - [TimestampMicro](#TimestampMicro) - [TimestampNano](#TimestampNano) +- [TrackFuncTime](#TrackFuncTime) +- [DaysBetween](#DaysBetween) +- [GenerateDatetimesBetween](#GenerateDatetimesBetween)
@@ -1360,3 +1363,106 @@ func main() { // 1690363051331788000 } ``` + +### TrackFuncTime + +

Tracks function execution time.

+ +Signature: + +```go +func TrackFuncTime(pre time.Time) func() +``` + +Example: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/v2/datetime" +) + +func main() { + defer datetime.TrackFuncTime(time.Now())() + + var n int + for i := 0; i < 5000000; i++ { + n++ + } + + fmt.Println(1) // Function main execution time: 1.460287ms +} +``` + +### DaysBetween + +

Returns the number of days between two times.

+ +Signature: + +```go +func DaysBetween(start, end time.Time) int +``` + +Example: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/datetime" +) + +func main() { + start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC) + + result := datetime.DaysBetween(start, end) + + fmt.Println(result) + + // Output: + // 9 +} +``` + +### GenerateDatetimesBetween + +

Returns a slice of strings between two times. `layout`: the format of the datetime string.`interval`: the interval between two datetimes.

+ +Signature: + +```go +func GenerateDatetimesBetween(start, end time.Time, layout string, interval string) ([]string, error) +``` + +Example: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/datetime" +) + +func main() { + start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2024, time.September, 1, 2, 0, 0, 0, time.UTC) + + layout := "2006-01-02 15:04:05" + interval := "1h" + + result, err := datetime.GenerateDatetimesBetween(start, end, layout, interval) + + fmt.Println(result) + fmt.Println(err) + + // Output: + // [2024-09-01 00:00:00 2024-09-01 01:00:00 2024-09-01 02:00:00] + // +} +``` \ No newline at end of file diff --git a/docs/datetime_zh-CN.md b/docs/datetime_zh-CN.md index cd9f35c..0f60594 100644 --- a/docs/datetime_zh-CN.md +++ b/docs/datetime_zh-CN.md @@ -62,6 +62,10 @@ import ( - [TimestampMilli](#TimestampMilli) - [TimestampMicro](#TimestampMicro) - [TimestampNano](#TimestampNano) +- [TrackFuncTime](#TrackFuncTime) +- [DaysBetween](#DaysBetween) +- [GenerateDatetimesBetween](#GenerateDatetimesBetween) +
@@ -1279,4 +1283,108 @@ func main() { // Output: // 1690363051331788000 } +``` + +### TrackFuncTime + +

返回两个日期之间的天数差。

+ +函数签名: + +```go +func TrackFuncTime(pre time.Time) func() +``` + +示例: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/datetime" +) + +func main() { + defer datetime.TrackFuncTime(time.Now())() + + var n int + for i := 0; i < 5000000; i++ { + n++ + } + + // Output: + // Function main execution time: 1.608195ms +} +``` + +### DaysBetween + +

返回两个日期之间的天数差。

+ +函数签名: + +```go +func DaysBetween(start, end time.Time) int +``` + +示例: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/datetime" +) + +func main() { + start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2024, time.September, 10, 0, 0, 0, 0, time.UTC) + + result := datetime.DaysBetween(start, end) + + fmt.Println(result) + + // Output: + // 9 +} +``` + +### GenerateDatetimesBetween + +

生成从start到end的所有日期时间的字符串列表。layout参数表示时间格式,例如"2006-01-02 15:04:05",interval参数表示时间间隔,例如"1h"表示1小时,"30m"表示30分钟。

+ +函数签名: + +```go +func GenerateDatetimesBetween(start, end time.Time, layout string, interval string) ([]string, error) +``` + +示例: + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/datetime" +) + +func main() { + start := time.Date(2024, time.September, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2024, time.September, 1, 2, 0, 0, 0, time.UTC) + + layout := "2006-01-02 15:04:05" + interval := "1h" + + result, err := datetime.GenerateDatetimesBetween(start, end, layout, interval) + + fmt.Println(result) + fmt.Println(err) + + // Output: + // [2024-09-01 00:00:00 2024-09-01 01:00:00 2024-09-01 02:00:00] + // +} ``` \ No newline at end of file diff --git a/docs/strutil.md b/docs/strutil.md index 1aa6ce4..fc9ddbb 100644 --- a/docs/strutil.md +++ b/docs/strutil.md @@ -64,6 +64,8 @@ import ( - [Ellipsis](#Ellipsis) - [Shuffle](#Shuffle) - [Rotate](#Rotate) +- [TemplateReplace](#TemplateReplace) +- [RegexMatchAllGroups](#RegexMatchAllGroups)
@@ -1468,4 +1470,71 @@ func main() { // Hello // oHell // loHel -} \ No newline at end of file +} + +### TemplateReplace + +

Replaces the placeholders in the template string with the corresponding values in the data map.The placeholders are enclosed in curly braces, e.g. {key}. for example, the template string is "Hello, {name}!", and the data map is {"name": "world"}, the result will be "Hello, world!".

+ +Signature: + +```go +func TemplateReplace(template string, data map[string]string string +``` + +example: + +```go +import ( + "fmt" + "github.com/duke-git/lancet/strutil" +) + +func main() { + template := `Hello, my name is {name}, I'm {age} years old.` + data := map[string]string{ + "name": "Bob", + "age": "20", + } + + result := strutil.TemplateReplace(template, data) + + fmt.Println(result) + + // Output: + // Hello, my name is Bob, I'm 20 years old. +} +``` + +### RegexMatchAllGroups + +

Matches all subgroups in a string using a regular expression and returns the result.

+ +Signature: + +```go +func RegexMatchAllGroups(pattern, str string) [][]string +``` + +example: + +```go +import ( + "fmt" + "github.com/duke-git/lancet/strutil" +) + +func main() { + pattern := `(\w+\.+\w+)@(\w+)\.(\w+)` + str := "Emails: john.doe@example.com and jane.doe@example.com" + + result := strutil.RegexMatchAllGroups(pattern, str) + + fmt.Println(result[0]) + fmt.Println(result[1]) + + // Output: + // [john.doe@example.com john.doe example com] + // [jane.doe@example.com jane.doe example com] +} +``` \ No newline at end of file diff --git a/docs/strutil_zh-CN.md b/docs/strutil_zh-CN.md index 71f9910..a33c4af 100644 --- a/docs/strutil_zh-CN.md +++ b/docs/strutil_zh-CN.md @@ -64,6 +64,8 @@ import ( - [Ellipsis](#Ellipsis) - [Shuffle](#Shuffle) - [Rotate](#Rotate) +- [TemplateReplace](#TemplateReplace) +- [RegexMatchAllGroups](#RegexMatchAllGroups)
@@ -1503,4 +1505,71 @@ func main() { // oHell // loHel } +``` + +### TemplateReplace + +

将模板字符串中的占位符替换为数据映射中的相应值。占位符括在花括号中,例如 {key}。例如,模板字符串为“Hello, {name}!”,数据映射为{"name": "world"},结果将为“Hello, world!”。

+ +函数签名: + +```go +func TemplateReplace(template string, data map[string]string) string +``` + +示例: + +```go +import ( + "fmt" + "github.com/duke-git/lancet/strutil" +) + +func main() { + template := `Hello, my name is {name}, I'm {age} years old.` + data := map[string]string{ + "name": "Bob", + "age": "20", + } + + result := strutil.TemplateReplace(template, data) + + fmt.Println(result) + + // Output: + // Hello, my name is Bob, I'm 20 years old. +} +``` + +### RegexMatchAllGroups + +

使用正则表达式匹配字符串中的所有子组并返回结果。

+ +函数签名: + +```go +func RegexMatchAllGroups(pattern, str string) [][]string +``` + +示例: + +```go +import ( + "fmt" + "github.com/duke-git/lancet/strutil" +) + +func main() { + pattern := `(\w+\.+\w+)@(\w+)\.(\w+)` + str := "Emails: john.doe@example.com and jane.doe@example.com" + + result := strutil.RegexMatchAllGroups(pattern, str) + + fmt.Println(result[0]) + fmt.Println(result[1]) + + // Output: + // [john.doe@example.com john.doe example com] + // [jane.doe@example.com jane.doe example com] +} ``` \ No newline at end of file diff --git a/strutil/string.go b/strutil/string.go index 6e4e587..33974c0 100644 --- a/strutil/string.go +++ b/strutil/string.go @@ -642,3 +642,32 @@ func Rotate(str string, shift int) string { return sb.String() } + +// TemplateReplace replaces the placeholders in the template string with the corresponding values in the data map. +// The placeholders are enclosed in curly braces, e.g. {key}. +// for example, the template string is "Hello, {name}!", and the data map is {"name": "world"}, +// the result will be "Hello, world!". +func TemplateReplace(template string, data map[string]string) string { + re := regexp.MustCompile(`\{(\w+)\}`) + + result := re.ReplaceAllStringFunc(template, func(s string) string { + key := strings.Trim(s, "{}") + if val, ok := data[key]; ok { + return val + } + + return s + }) + + result = strings.ReplaceAll(result, "{{", "{") + result = strings.ReplaceAll(result, "}}", "}") + + return result +} + +// RegexMatchAllGroups Matches all subgroups in a string using a regular expression and returns the result. +func RegexMatchAllGroups(pattern, str string) [][]string { + re := regexp.MustCompile(pattern) + matches := re.FindAllStringSubmatch(str, -1) + return matches +} diff --git a/strutil/string_test.go b/strutil/string_test.go index ab50fa3..163c144 100644 --- a/strutil/string_test.go +++ b/strutil/string_test.go @@ -581,3 +581,90 @@ func TestRotate(t *testing.T) { assert.Equal(tt.expected, Rotate(tt.input, tt.shift)) } } + +func TestTemplateReplace(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestTemplateReplace") + + t.Run("basic", func(t *testing.T) { + template := `Hello, my name is {name}, I'm {age} years old.` + data := map[string]string{ + "name": "Bob", + "age": "20", + } + + expected := `Hello, my name is Bob, I'm 20 years old.` + result := TemplateReplace(template, data) + + assert.Equal(expected, result) + }) + + t.Run("not found", func(t *testing.T) { + template := `Hello, my name is {name}, I'm {age} years old.` + data := map[string]string{ + "name": "Bob", + } + + expected := `Hello, my name is Bob, I'm {age} years old.` + result := TemplateReplace(template, data) + + assert.Equal(expected, result) + }) + + t.Run("brackets", func(t *testing.T) { + template := `Hello, my name is {name}, I'm {{age}} years old.` + data := map[string]string{ + "name": "Bob", + "age": "20", + } + + expected := `Hello, my name is Bob, I'm {20} years old.` + result := TemplateReplace(template, data) + + assert.Equal(expected, result) + }) +} + +func TestRegexMatchAllGroups(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestRegexMatchAllGroups") + + tests := []struct { + pattern string + str string + expected [][]string + }{ + { + pattern: `(\w+\.+\w+)@(\w+)\.(\w+)`, + str: "Emails: john.doe@example.com and jane.doe@example.com", + expected: [][]string{{"john.doe@example.com", "john.doe", "example", "com"}, {"jane.doe@example.com", "jane.doe", "example", "com"}}, + }, + { + pattern: `(\d+)`, + str: "No numbers here!", + expected: nil, + }, + { + pattern: `(\d{3})-(\d{2})-(\d{4})`, + str: "My number is 123-45-6789", + expected: [][]string{{"123-45-6789", "123", "45", "6789"}}, + }, + { + pattern: `(\w+)\s(\d+)`, + str: "Item A 123, Item B 456", + expected: [][]string{{"A 123", "A", "123"}, {"B 456", "B", "456"}}, + }, + { + pattern: `(\d{2})-(\d{2})-(\d{4})`, + str: "Dates: 01-01-2020, 12-31-1999", + expected: [][]string{{"01-01-2020", "01", "01", "2020"}, {"12-31-1999", "12", "31", "1999"}}, + }, + } + + for _, tt := range tests { + result := RegexMatchAllGroups(tt.pattern, tt.str) + assert.Equal(tt.expected, result) + } +}