diff --git a/README.md b/README.md index 4427c30..a9ee965 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Golang 中可以使用的常用辅助功能工具箱。主要配备以下功能 - [ ] 1024 位长 - [ ] 2048 位长 - [ ] KeyPair 生成器 + - [ ] RSA 签名算法 - 散列算法。 - [ ] Sha512 散列算法 - [ ] Sha256 散列算法 @@ -30,8 +31,6 @@ Golang 中可以使用的常用辅助功能工具箱。主要配备以下功能 - [ ] 冰雹 ID 生成器(短主机精简日期版雪花 ID) - [ ] UUID 生成器 - [ ] short UUID 生成器 -- 签名算法 - - [ ] RSA 签名算法 - 验证码生成器 - [ ] 随机验证码生成算法 - 序列化算法 diff --git a/go.mod b/go.mod index 634ccce..df1815a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module archgrid.xyz/ag/toolsbox go 1.20 + +require go.uber.org/zap v1.24.0 + +require ( + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3cb5c63 --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= diff --git a/types/date.go b/types/date.go new file mode 100644 index 0000000..76d1da4 --- /dev/null +++ b/types/date.go @@ -0,0 +1,159 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "fmt" + "time" + + "go.uber.org/zap" +) + +var ( + dateLayouts = []string{ + "2006-01-02", "2006-1-2", "2006/01/02", "06-1-2", "6-01-02", "01/02/06", "1/2/06", "2006年01月02日", "06年1月2日", + } +) + +// 封装日期类型。 +type Date struct { + time.Time +} + +// 根据给定的年月日创建一个日期类型实例。 +func NewDate(year int, month time.Month, day int) Date { + return Date{ + Time: time.Date(year, month, day, 0, 0, 0, 0, loc), + } +} + +// 创建一个新的空白日期类型实例。 +func NewEmptyDate() Date { + return Date{ + Time: time.Time{}.In(loc), + } +} + +// 获取当前日期。 +func NowDate() Date { + return Now().Date() +} + +// 从给定的字符串中解析日期。如果无法解析则返回错误。 +func ParseDate(t string) (Date, error) { + if len(t) == 0 { + return NewEmptyDate(), fmt.Errorf("不能解析空白的日期时间。") + } + for _, layout := range dateLayouts { + d, err := time.ParseInLocation(layout, t, loc) + if err == nil { + return Date{ + Time: d, + }, nil + } + } + return NewEmptyDate(), fmt.Errorf("无法解析给定的日期,格式不正确。") +} + +// 尝试从字符串中解析日期,如果无法解析则返回给定的默认值。 +func ParseDateWithDefault(t string, defaultDate Date) Date { + if len(t) == 0 { + return defaultDate + } + d, err := ParseDate(t) + if err != nil { + return defaultDate + } + return d +} + +var _ driver.Valuer = (*Date)(nil) + +func (dt Date) Value() (driver.Value, error) { + return dt.In(loc).Format("2006-01-02"), nil +} + +var _ sql.Scanner = (*Date)(nil) + +func (d *Date) Scan(src interface{}) (err error) { + switch src := src.(type) { + case time.Time: + d.Time = src + case string: + t, err := time.ParseInLocation("2006-01-02", src, loc) + if err != nil { + return err + } + *d = Date{Time: t} + case []byte: + d.Time, err = time.ParseInLocation("2006-01-02", string(src), loc) + return err + case nil: + d = nil + default: + return fmt.Errorf("该数据类型不支持解析到日期: %T", src) + } + return nil +} + +var _ json.Marshaler = (*Date)(nil) + +func (d Date) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Format("2006-01-02")) +} + +var _ json.Unmarshaler = (*Date)(nil) + +func (d *Date) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return fmt.Errorf("不能解析指定的日期时间值: %w", err) + } + t, err := time.ParseInLocation("2006-01-02", str, loc) + d.Time = t + return err +} + +// 计算两个日期之间的月份差。 +func (d Date) DifferenceInMonth(d2 *Date) int { + var differYear, differMonth int + differYear = d.Year() - d2.Year() + differMonth = int(d.Month() - d2.Month()) + return differYear*12 + differMonth +} + +// 判断给定日期是否是当前日期的下一个月。 +func (d Date) IsNextMonth(d2 *Date) bool { + return d.DifferenceInMonth(d2) == 1 +} + +// 获取当前日期的最开始时间。 +func (d Date) ToBeginningOfDate() DateTime { + return FromTime(time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, loc)) +} + +// 获取当前日期的最末尾时间。 +func (d Date) ToEndingOfDate() DateTime { + return FromTime(time.Date(d.Year(), d.Month(), d.Day(), 23, 59, 59, 999999, loc)) +} + +// 判断当前日期是否为空白日期。 +func (d Date) IsEmpty() bool { + return d.Time.IsZero() +} + +// 使用`YYYY-MM-DD`格式输出日期。 +func (d Date) ToString() string { + return d.Time.Format("2006-01-02") +} + +// 将当前日期转换为日期时间类型。 +func (d Date) ToDateTime() DateTime { + return FromTime(d.Time) +} + +// 在Zap日志中以给定的`fieldName`为字段名输出日期。 +func (d Date) Log(fieldName string) zap.Field { + return zap.String(fieldName, d.ToString()) +} diff --git a/types/datetime.go b/types/datetime.go new file mode 100644 index 0000000..ab5715b --- /dev/null +++ b/types/datetime.go @@ -0,0 +1,167 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "fmt" + "time" + + "go.uber.org/zap" +) + +var ( + loc *time.Location = time.FixedZone("+0800", 8*60*60) + datetimeLayouts = []string{ + "2006-01-02 15:04:05", "2006-1-2 15:04:05", "2006/01/02 15:04:05", "06-1-2 15:04:05", "06-01-02 15:04:05", "01/02/06 15:04:05", "1/2/06 15:04:05", "2006年01月02日 15:04:05", "06年1月2日 15:04:05", + "2006-01-02 15:04", "2006-1-2 15:04", "2006/01/02 15:04", "06-1-2 15:04", "06-01-02 15:04", "01/02/06 15:04", "1/2/06 15:04", "2006年01月02日 15:04", "06年1月2日 15:04", + } +) + +// 封装日期时间类型。 +type DateTime struct { + time.Time +} + +// 获取当前的日期时间。 +func Now() DateTime { + return DateTime{ + Time: time.Now().In(loc), + } +} + +// 创建一个空白的日期时间类型实例。 +func NewEmptyDateTime() DateTime { + return DateTime{ + Time: time.Time{}.In(loc), + } +} + +// 获取一个自`2022-02-22 22:22:22.0`以来的秒数时间戳。 +func Timestamp() int64 { + startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() + return Now().Unix() - startline +} + +// 从指定的字符串中解析日期时间。如果无法解析则返回错误。 +func ParseDateTime(t string) (DateTime, error) { + if len(t) == 0 { + return NewEmptyDateTime(), fmt.Errorf("不能解析空白的日期时间。") + } + for _, layout := range datetimeLayouts { + fmt.Printf("Parse: %s, Try layout: %s\n", t, layout) + d, err := time.ParseInLocation(layout, t, loc) + if err == nil { + return DateTime{ + Time: d, + }, nil + } + } + return NewEmptyDateTime(), fmt.Errorf("无法解析给定的日期时间,格式不正确。") +} + +// 封装一个标准库中的时间类型。 +func FromTime(t time.Time) DateTime { + return DateTime{ + Time: t, + } +} + +// 解析一个Unix时间戳。 +func FromUnixMicro(sec int64) DateTime { + return DateTime{ + Time: time.UnixMicro(sec).In(loc), + } +} + +var _ driver.Valuer = (*DateTime)(nil) + +func (dt DateTime) Value() (driver.Value, error) { + return dt.In(loc).Format("2006-01-02 15:04:05"), nil +} + +var _ sql.Scanner = (*DateTime)(nil) + +func (dt *DateTime) Scan(src interface{}) (err error) { + switch src := src.(type) { + case time.Time: + dt.Time = src + case string: + t, err := time.ParseInLocation("2006-01-02 15:04:05", src, loc) + if err != nil { + return err + } + *dt = DateTime{Time: t} + case []byte: + dt.Time, err = time.ParseInLocation("2006-01-02 15:04:05", string(src), loc) + return err + case nil: + dt = nil + default: + return fmt.Errorf("该数据类型不支持解析到日期时间: %T", src) + } + return nil +} + +var _ json.Marshaler = (*DateTime)(nil) + +func (dt DateTime) MarshalJSON() ([]byte, error) { + return json.Marshal(dt.Format("2006-01-02 15:04:05")) +} + +var _ json.Unmarshaler = (*DateTime)(nil) + +func (dt *DateTime) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return fmt.Errorf("不能解析指定的日期时间值: %w", err) + } + t, err := time.ParseInLocation("2006-01-02 15:04:05", str, loc) + dt.Time = t + return err +} + +// 计算给定日期时间与当前日期时间的月份差值。 +func (dt DateTime) DifferenceInMonth(d DateTime) int { + var differYear, differMonth int + differYear = dt.Year() - d.Year() + differMonth = int(dt.Month() - d.Month()) + return differYear*12 + differMonth +} + +// 判断给定日期时间是否是当前日期时间的下一个月。 +func (dt DateTime) IsNextMonth(target DateTime) bool { + return dt.DifferenceInMonth(target) == 1 +} + +// 将当前日期时间调整为当天的开始时间。 +func (dt *DateTime) ToBeginningOfDate() { + dt.Time = time.Date(dt.Year(), dt.Month(), dt.Day(), 0, 0, 0, 0, loc) +} + +// 将当前日期时间调整为当天的结束时间。 +func (dt *DateTime) ToEndingOfDate() { + dt.Time = time.Date(dt.Year(), dt.Month(), dt.Day(), 23, 59, 59, 999999, loc) +} + +// 从当前日期时间中获取日期部分。 +func (dt DateTime) Date() Date { + return Date{ + Time: time.Date(dt.Year(), dt.Month(), dt.Day(), 0, 0, 0, 0, loc), + } +} + +// 判断当前日期时间是否为空。 +func (dt DateTime) IsEmpty() bool { + return dt.Time.IsZero() +} + +// 以`YYYY-MM-DD HH:mm:ss`格式输出日期时间。 +func (dt DateTime) ToString() string { + return dt.Time.Format("2006-01-02 15:04:05") +} + +// 在Zap日志中以给定`fieldName`为字段名输出日期时间。 +func (dt DateTime) Log(fieldName string) zap.Field { + return zap.String(fieldName, dt.ToString()) +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..129ff7c --- /dev/null +++ b/types/types.go @@ -0,0 +1,2 @@ +// 定义常用的数据类型,包括数据类型在数据库内的存储和读取,以及数据类型在JSON格式中的输出与解析。 +package types