Compare commits

...

9 Commits

Author SHA1 Message Date
Bobo
3f713e489b feat: time util 2025-05-29 00:15:37 +08:00
Bobo
ace6bd4237 feat: time util 2025-05-29 00:07:27 +08:00
Bobo
3ec9bbbd27 feat: time util 2025-05-28 23:58:08 +08:00
Bobo
faa5817857 feat: time util 2025-05-28 23:55:43 +08:00
Bobo
89a69a9d4d feat: entgo 2025-05-28 23:29:21 +08:00
Bobo
63789d090d feat: entgo 2025-05-28 23:08:18 +08:00
Bobo
06c29c57d2 feat: copier util 2025-05-28 23:06:30 +08:00
Bobo
5faa4e06f7 feat: time util 2025-05-28 22:38:29 +08:00
Bobo
5abe88a0d9 feat: jwt util 2025-05-23 14:31:59 +08:00
14 changed files with 616 additions and 35 deletions

View File

@@ -42,11 +42,15 @@ var TimestamppbToTimeConverter = copier.TypeConverter{
},
}
func TimeToString(tm *time.Time) *string {
return timeutil.TimeToString(tm, timeutil.ISO8601)
}
func NewTimeStringConverterPair() []copier.TypeConverter {
srcType := &time.Time{}
dstType := trans.Ptr("")
fromFn := timeutil.TimeToTimeString
fromFn := TimeToString
toFn := timeutil.StringTimeToTime
return NewGenericTypeConverterPair(srcType, dstType, fromFn, toFn)

View File

@@ -6,7 +6,7 @@ toolchain go1.24.3
require (
github.com/jinzhu/copier v0.4.0
github.com/tx7do/go-utils v1.1.22
github.com/tx7do/go-utils v1.1.27
)
require (

View File

@@ -11,7 +11,7 @@ require (
github.com/go-kratos/kratos/v2 v2.8.4
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.22
github.com/tx7do/go-utils v1.1.27
go.opentelemetry.io/otel v1.36.0
google.golang.org/protobuf v1.36.6
)
@@ -23,7 +23,7 @@ require (
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/inflect v0.21.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect

View File

@@ -23,6 +23,8 @@ github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYq
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=

View File

@@ -5,13 +5,16 @@ go 1.23.0
toolchain go1.24.1
require (
github.com/go-kratos/kratos/v2 v2.8.4
github.com/golang-jwt/jwt/v5 v5.1.0
github.com/stretchr/testify v1.10.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,12 +1,28 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU=
github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -5,7 +5,7 @@ import (
"net/http"
"time"
"encoding/json"
"github.com/go-kratos/kratos/v2/encoding"
"github.com/golang-jwt/jwt/v5"
)
@@ -40,14 +40,16 @@ func ParseJWTClaimsToStruct[T any](tokenString string) (*T, error) {
return nil, fmt.Errorf("invalid token claims")
}
codec := encoding.GetCodec("json")
// 将 claims 转换为目标类型
claimsBytes, err := json.Marshal(claims)
claimsBytes, err := codec.Marshal(claims)
if err != nil {
return nil, fmt.Errorf("failed to marshal claims: %v", err)
}
var ret T
err = json.Unmarshal(claimsBytes, &ret)
err = codec.Unmarshal(claimsBytes, &ret)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal claims: %v", err)
}
@@ -132,13 +134,15 @@ func GenerateGenericJWT[T any](payload T, secretKey []byte, signingMethod jwt.Si
// ToMapClaims 将泛型 payload 转换为 jwt.MapClaims
func ToMapClaims[T any](payload T) (jwt.MapClaims, error) {
payloadBytes, err := json.Marshal(payload)
codec := encoding.GetCodec("json")
payloadBytes, err := codec.Marshal(payload)
if err != nil {
return nil, err
}
var claims jwt.MapClaims
err = json.Unmarshal(payloadBytes, &claims)
err = codec.Unmarshal(payloadBytes, &claims)
if err != nil {
return nil, err
}

View File

@@ -8,6 +8,9 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
_ "github.com/go-kratos/kratos/v2/encoding/json"
_ "github.com/go-kratos/kratos/v2/encoding/proto"
)
func TestParseJWTPayload(t *testing.T) {

View File

@@ -1,12 +1,12 @@
git tag v1.1.23
git tag v1.1.27
git tag bank_card/v1.1.5
git tag geoip/v1.1.5
git tag translator/v1.1.2
git tag copierutil/v0.0.3
git tag jwtutil/v0.0.1
git tag copierutil/v0.0.5
git tag jwtutil/v0.0.2
git tag entgo/v1.1.28
git tag entgo/v1.1.30
git tag gorm/v1.1.6
git push origin --tags

View File

@@ -10,4 +10,52 @@ const (
DefaultTimeLocationName = "Asia/Shanghai"
)
// More predefined layouts for use in Time.Format and time.Parse.
const (
DT14 = "20060102150405"
DT8 = "20060102"
DT8MDY = "01022006"
DT6 = "200601"
MonthDay = "1/2"
DIN5008FullDate = "02.01.2006" // German DIN 5008 standard
DIN5008Date = "02.01.06"
RFC3339FullDate = time.DateOnly
RFC3339Milli = "2006-01-02T15:04:05.999Z07:00"
RFC3339Dash = "2006-01-02T15-04-05Z07-00"
ISO8601 = "2006-01-02T15:04:05Z0700"
ISO8601TZHour = "2006-01-02T15:04:05Z07"
ISO8601NoTZ = "2006-01-02T15:04:05"
ISO8601MilliNoTZ = "2006-01-02T15:04:05.999"
ISO8601Milli = "2006-01-02T15:04:05.999Z0700"
ISO8601CompactZ = "20060102T150405Z0700"
ISO8601CompactNoTZ = "20060102T150405"
ISO8601YM = "2006-01"
ISO9075 = time.DateTime // ISO/IEC 9075 used by MySQL, BigQuery, etc.
ISO9075MicroTZ = "2006-01-02 15:04:05.999999-07" // ISO/IEC 9075 used by PostgreSQL
RFC5322 = "Mon, 2 Jan 2006 15:04:05 -0700" // RFC5322 = "Mon Jan 02 15:04:05 -0700 2006"
SQLTimestamp = ISO9075
SQLTimestampMinutes = "2006-01-02 15:04"
Ruby = "2006-01-02 15:04:05 -0700" // Ruby Time.now.to_s
InsightlyAPIQuery = "_1/_2/2006 _3:04:05 PM"
DateMDY = "1/2/2006" // an underscore results in a space.
DateMDYSlash = "01/02/2006"
DateDMYDash = "_2-01-2006" // Jira XML Date format
DateDMYHM2 = "02:01:06 15:04" // GMT time in format dd:mm:yy hh:mm
DateYMD = RFC3339FullDate
DateTextUS = "January 2, 2006"
DateTextUSAbbr3 = "Jan 2, 2006"
DateTextEU = "2 January 2006"
DateTextEUAbbr3 = "2 Jan 2006"
MonthAbbrYear = "Jan 2006"
MonthYear = "January 2006"
)
const (
RFC3339Min = "0000-01-01T00:00:00Z"
RFC3339Max = "9999-12-31T23:59:59Z"
RFC3339Zero = "0001-01-01T00:00:00Z" // Golang zero value
RFC3339ZeroUnix = "1970-01-01T00:00:00Z"
RFC3339YMDZeroUnix = int64(-62135596800)
)
var ReferenceTimeValue time.Time = time.Date(2006, 1, 2, 15, 4, 5, 999999999, time.FixedZone("MST", -7*60*60))

View File

@@ -1,7 +1,9 @@
package timeutil
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
)
@@ -43,3 +45,291 @@ func DurationHMS(d time.Duration) (int, int, int) {
return int(h), int(m), int(s)
}
// Reformat a time string from one format to another
// Deprecated...
func FromTo(value, fromLayout, toLayout string) (string, error) {
t, err := time.Parse(fromLayout, strings.TrimSpace(value))
if err != nil {
return "", err
}
return t.Format(toLayout), nil
}
// Reformat a time string from one format to another
func FromTo2(fromLayout, toLayout, value string) (string, error) {
t, err := time.Parse(fromLayout, strings.TrimSpace(value))
if err != nil {
return "", err
}
return t.Format(toLayout), nil
}
func FromToFirstValueOrEmpty(fromLayout, toLayout string, values []string) string {
dtString, err := FromToFirstValue(fromLayout, toLayout, values)
if err != nil {
return ""
}
return dtString
}
func FromToFirstValue(fromLayout, toLayout string, values []string) (string, error) {
for _, val := range values {
dt, err := time.Parse(fromLayout, val)
if err == nil {
return dt.Format(toLayout), nil
}
}
return "", errors.New("no match")
}
func ParseFirstValueOrZero(layout string, values []string) time.Time {
dt, err := ParseFirstValue(layout, values)
if err != nil {
return TimeZeroRFC3339()
}
return dt
}
func ParseFirstValue(layout string, values []string) (time.Time, error) {
for _, val := range values {
try, err := time.Parse(layout, val)
if err == nil {
return try, nil
}
}
numVals := len(values)
if numVals == 0 {
return time.Now(), errors.New("no time values supplied")
}
return time.Now(), fmt.Errorf("no valid string of [%v] supplied values", strconv.Itoa(numVals))
}
// ParseOrZero returns a parsed time.Time or the RFC-3339 zero time.
func ParseOrZero(layout, value string) time.Time {
t, err := time.Parse(layout, value)
if err != nil {
return TimeZeroRFC3339()
}
return t
}
// ParseFirst attempts to parse a string with a set of layouts.
func ParseFirst(layouts []string, value string) (time.Time, error) {
value = strings.TrimSpace(value)
if len(value) == 0 || len(layouts) == 0 {
return time.Now(), fmt.Errorf(
"requires value [%v] and at least one layout [%v]", value, strings.Join(layouts, ","))
}
for _, layout := range layouts {
layout = strings.TrimSpace(layout)
if len(layout) == 0 {
continue
}
if dt, err := time.Parse(layout, value); err == nil {
return dt, nil
}
}
return time.Now(), fmt.Errorf("cannot parse time [%v] with layouts [%v]",
value, strings.Join(layouts, ","))
}
var FormatMap = map[string]string{
"RFC3339": time.RFC3339,
"RFC3339YMD": RFC3339FullDate,
"ISO8601YM": ISO8601YM,
}
func GetFormat(formatName string) (string, error) {
format, ok := FormatMap[strings.TrimSpace(formatName)]
if !ok {
return "", fmt.Errorf("format Not Found: %v", format)
}
return format, nil
}
func TimeMinRFC3339() time.Time {
t0, _ := time.Parse(time.RFC3339, RFC3339Min)
return t0
}
func TimeZeroRFC3339() time.Time {
t0, _ := time.Parse(time.RFC3339, RFC3339Zero)
return t0
}
func TimeZeroUnix() time.Time {
t0, _ := time.Parse(time.RFC3339, RFC3339ZeroUnix)
return t0
}
func isZeroAny(u time.Time) bool {
return u.Equal(TimeZeroRFC3339()) ||
u.Equal(TimeMinRFC3339()) ||
u.Equal(TimeZeroUnix())
}
type RFC3339YMDTime struct{ time.Time }
type ISO8601NoTzMilliTime struct{ time.Time }
func (t *RFC3339YMDTime) UnmarshalJSON(buf []byte) error {
tt, isNil, err := timeUnmarshalJSON(buf, RFC3339FullDate)
if err != nil || isNil {
return err
}
t.Time = tt
return nil
}
func (t RFC3339YMDTime) MarshalJSON() ([]byte, error) {
return timeMarshalJSON(t.Time, RFC3339FullDate)
}
func (t *ISO8601NoTzMilliTime) UnmarshalJSON(buf []byte) error {
tt, isNil, err := timeUnmarshalJSON(buf, ISO8601MilliNoTZ)
if err != nil || isNil {
return err
}
t.Time = tt
return nil
}
func (t ISO8601NoTzMilliTime) MarshalJSON() ([]byte, error) {
return timeMarshalJSON(t.Time, ISO8601MilliNoTZ)
}
func timeUnmarshalJSON(buf []byte, layout string) (time.Time, bool, error) {
str := string(buf)
isNil := true
if str == "null" || str == "\"\"" {
return time.Time{}, isNil, nil
}
tt, err := time.Parse(layout, strings.Trim(str, `"`))
if err != nil {
return time.Time{}, false, err
}
return tt, false, nil
}
func timeMarshalJSON(t time.Time, layout string) ([]byte, error) {
return []byte(`"` + t.Format(layout) + `"`), nil
}
func ParseSlice(layout string, strings []string) ([]time.Time, error) {
var times []time.Time
for _, raw := range strings {
t, err := time.Parse(layout, raw)
if err != nil {
return times, err
}
times = append(times, t)
}
return times, nil
}
// FormatTimeMulti formats a `time.Time` object or
// an epoch number. It is adapted from `github.com/wcharczuk/go-chart`.
func FormatTimeMulti(dateFormat string, v any) string {
if typed, isTyped := v.(time.Time); isTyped {
return typed.Format(dateFormat)
}
if typed, isTyped := v.(int64); isTyped {
return time.Unix(0, typed).Format(dateFormat)
}
if typed, isTyped := v.(float64); isTyped {
return time.Unix(0, int64(typed)).Format(dateFormat)
}
return ""
}
func FormatTimeToString(format string) func(time.Time) string {
return func(dt time.Time) string {
return dt.Format(format)
}
}
// OffsetFormat converts an integer offset value to a string
// value to use in string time formats. Note: RFC-3339 times
// use colons and the UTC "Z" offset.
func OffsetFormat(offset int, useColon, useZ bool) string {
offsetStr := "+0000"
if offset == 0 {
if useZ {
offsetStr = "Z"
} else if useColon {
offsetStr = "+00:00"
}
} else if offset > 0 {
if useColon {
hr := offset / 100
mn := offset - (hr * 100)
offsetStr = "+" + fmt.Sprintf("%02d:%02d", hr, mn)
} else {
offsetStr = "+" + fmt.Sprintf("%04d", offset)
}
} else if offset < 0 {
if useColon {
offsetPositive := -1 * offset
hr := offsetPositive / 100
mn := offsetPositive - (hr * 100)
offsetStr = "-" + fmt.Sprintf("%02d:%02d", hr, mn)
} else {
offsetStr = "-" + fmt.Sprintf("%04d", -1*offset)
}
}
return offsetStr
}
// var rxSQLTimestamp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$`)
func ParseTimeUsingOffset(format, raw, sep string, offset int, useColon, useZ bool) (time.Time, error) {
return time.Parse(format, raw+sep+OffsetFormat(offset, useColon, useZ))
}
// ParseTimeSQLTimestampUsingOffset converts a SQL timestamp without timezone
// adding in a manual integer timezone.
func ParseTimeSQLTimestampUsingOffset(timeStr string, offset int) (time.Time, error) {
return ParseTimeUsingOffset(Ruby, timeStr, " ", offset, false, false)
/*
timeStr = strings.TrimSpace(timeStr)
if !rxSQLTimestamp.MatchString(timeStr) {
return time.Now(), fmt.Errorf("E_INVALID_SQL_TIMESTAMP [%v]", timeStr)
}
offsetStr := OffsetFormat(offset, useColon, useZ)
timeStr += " " + offsetStr
dt, err := time.Parse(Ruby, timeStr)
return dt, err
*/
}
const (
LayoutNameDT4 = "dt4"
LayoutNameDT6 = "dt6"
LayoutNameDT8 = "dt8"
LayoutNameDT14 = "dt14"
)
// IsDTX returns the dtx format if conformant to various DTX values (dt4, dt6, dt8, dt14).
func IsDTX(d int) (string, error) {
switch len(strconv.Itoa(d)) {
case 4:
return LayoutNameDT4, nil
case 6:
m := d - ((d / 100) * 100)
if m < 1 || m > 12 {
return LayoutNameDT6, errors.New("dt6 length month value is out of bounds")
}
return LayoutNameDT6, nil
case 8:
dy := d - ((d / 100) * 100)
if dy < 1 || dy > 31 {
return LayoutNameDT6, errors.New("dt8 day value is out of bounds")
}
return LayoutNameDT8, nil
case 14:
return LayoutNameDT14, nil
default:
return "", errors.New("length mismatch")
}
}

132
timeutil/format_test.go Normal file
View File

@@ -0,0 +1,132 @@
package timeutil
import (
"encoding/json"
"testing"
"time"
)
var dmyhm2ParseTests = []struct {
v string
want string
}{
{"02:01:06 15:04", "2006-01-02T15:04:00Z"},
}
// TestDMYHM2ParseTime ensures timeutil.DateDMYHM2 is parsed to GMT timezone.
func TestDMYHM2ParseTime(t *testing.T) {
for _, tt := range dmyhm2ParseTests {
got, err := FromTo(tt.v, DateDMYHM2, time.RFC3339)
if err != nil {
t.Errorf("time.Parse(DateDMYHM2) Error: with %v, want %v, err %v", tt.v, tt.want, err)
}
if got != tt.want {
t.Errorf("time.Parse(\"%v\", DateDMYHM2) Mismatch: want %v, got %v", tt.v, tt.want, got)
}
}
}
var rfc3339YMDTimeTests = []struct {
v string
want string
}{
{`{"MyTime":"2001-02-03"}`, `{"MyTime":"2001-02-03"}`},
{`{"MyTime":"0001-01-01"}`, `{"MyTime":"0001-01-01"}`},
{`{"MyTime":""}`, `{"MyTime":"0001-01-01"}`},
{`{}`, `{"MyTime":"0001-01-01"}`}}
type myStruct struct {
MyTime RFC3339YMDTime
}
func TestRfc3339YMDTime(t *testing.T) {
for _, tt := range rfc3339YMDTimeTests {
my := myStruct{}
//fmt.Println(tt.v)
err := json.Unmarshal([]byte(tt.v), &my)
if err != nil {
t.Errorf("Rfc3339YMDTime.Unmarshal: with %v, want %v, err %v", tt.v, tt.want, err)
}
bytes, err := json.Marshal(my)
if err != nil {
t.Errorf("Rfc3339YMDTime.Marshal: with %v, want %v, err %v", tt.v, tt.want, err)
}
got := string(bytes)
if got != tt.want {
t.Errorf("Rfc3339YMDTime(%v): want %v, got %v", tt.v, tt.want, got)
}
}
}
var offsetFormatTests = []struct {
input int
useColon bool
useZ bool
want string
}{
{0, false, false, "+0000"},
{0, true, false, "+00:00"},
{0, true, true, "Z"},
{400, false, false, "+0400"},
{-400, false, false, "-0400"},
{530, false, false, "+0530"},
{-530, false, false, "-0530"},
{700, true, false, "+07:00"},
{-700, true, false, "-07:00"},
{845, true, false, "+08:45"},
{-845, true, false, "-08:45"},
}
func TestOffsetFormat(t *testing.T) {
for _, tt := range offsetFormatTests {
got := OffsetFormat(tt.input, tt.useColon, tt.useZ)
if got != tt.want {
t.Errorf("time.OffsetFormat(\"%v\",%v,%v) Mismatch: want [%v], got [%v]", tt.input, tt.useColon, tt.useZ, tt.want, got)
}
}
}
var isDTXTests = []struct {
v int
want string
}{
{2004, LayoutNameDT4},
{200401, LayoutNameDT6},
}
func TestIsDTX(t *testing.T) {
for _, tt := range isDTXTests {
got, err := IsDTX(tt.v)
if err != nil {
t.Errorf("time.IsDTX(%d) Error: want (%s), err (%v)", tt.v, tt.want, err)
}
if got != tt.want {
t.Errorf("time.IsDTX(%d) Mismatch: want (%s), got (%s)", tt.v, tt.want, got)
}
}
}
var formatsTests = []struct {
v string
format string
want string
}{
{"2023-06-18T00:00:00Z", DateTextUS, "June 18, 2023"},
{"2023-06-18T00:00:00Z", DateTextUSAbbr3, "Jun 18, 2023"},
}
func TestFormats(t *testing.T) {
for _, tt := range formatsTests {
dt, err := time.Parse(time.RFC3339, tt.v)
if err != nil {
t.Errorf("time.Parse(time.RFC3339, %s) Error: err (%s)", tt.v, err.Error())
}
got := dt.Format(tt.format)
if got != tt.want {
t.Errorf("time.Format(%s) dt (%s) Mismatch: want (%s), got (%s)", tt.format, tt.v, tt.want, got)
}
}
}

View File

@@ -26,11 +26,14 @@ func GetDefaultTimeLocation() *time.Location {
}
// UnixMilliToStringPtr 毫秒时间戳 -> 字符串
func UnixMilliToStringPtr(tm *int64) *string {
if tm == nil {
func UnixMilliToStringPtr(milli *int64) *string {
if milli == nil {
return nil
}
str := time.UnixMilli(*tm).Format(TimeLayout)
tm := time.UnixMilli(*milli)
str := tm.In(GetDefaultTimeLocation()).Format(TimeLayout)
return &str
}
@@ -44,17 +47,18 @@ func StringToUnixMilliInt64Ptr(tm *string) *int64 {
if theTime == nil {
return nil
}
unixTime := theTime.UnixMilli()
return &unixTime
}
// UnixMilliToTimePtr 毫秒时间戳 -> 时间
func UnixMilliToTimePtr(tm *int64) *time.Time {
if tm == nil {
func UnixMilliToTimePtr(milli *int64) *time.Time {
if milli == nil {
return nil
}
unixMilli := time.UnixMilli(*tm)
unixMilli := time.UnixMilli(*milli)
return &unixMilli
}
@@ -69,12 +73,13 @@ func TimeToUnixMilliInt64Ptr(tm *time.Time) *int64 {
}
// UnixSecondToTimePtr 秒时间戳 -> 时间
func UnixSecondToTimePtr(tm *int64) *time.Time {
if tm == nil {
func UnixSecondToTimePtr(second *int64) *time.Time {
if second == nil {
return nil
}
unixMilli := time.Unix(*tm, 0)
unixMilli := time.Unix(*second, 0)
return &unixMilli
}
@@ -100,18 +105,19 @@ func StringTimeToTime(str *string) *time.Time {
var err error
var theTime time.Time
theTime, err = time.ParseInLocation(TimeLayout, *str, GetDefaultTimeLocation())
if err == nil {
if theTime, err = time.ParseInLocation(TimeLayout, *str, GetDefaultTimeLocation()); err == nil {
return &theTime
}
theTime, err = time.ParseInLocation(DateLayout, *str, GetDefaultTimeLocation())
if err == nil {
if theTime, err = time.ParseInLocation(DateLayout, *str, GetDefaultTimeLocation()); err == nil {
return &theTime
}
theTime, err = time.ParseInLocation(ClockLayout, *str, GetDefaultTimeLocation())
if err == nil {
if theTime, err = time.ParseInLocation(ClockLayout, *str, GetDefaultTimeLocation()); err == nil {
return &theTime
}
if theTime, err = time.ParseInLocation(ISO9075MicroTZ, *str, GetDefaultTimeLocation()); err == nil {
return &theTime
}
@@ -123,7 +129,8 @@ func TimeToTimeString(tm *time.Time) *string {
if tm == nil {
return nil
}
return trans.String(tm.Format(TimeLayout))
return trans.String(tm.In(GetDefaultTimeLocation()).Format(TimeLayout))
}
// StringDateToTime 字符串 -> 时间
@@ -161,13 +168,41 @@ func TimeToDateString(tm *time.Time) *string {
if tm == nil {
return nil
}
return trans.String(tm.Format(DateLayout))
return trans.String(tm.In(GetDefaultTimeLocation()).Format(DateLayout))
}
func StringToTime(str *string, layout string) *time.Time {
if str == nil {
return nil
}
if len(*str) == 0 {
return nil
}
var err error
var theTime time.Time
theTime, err = time.ParseInLocation(layout, *str, GetDefaultTimeLocation())
if err == nil {
return &theTime
}
return nil
}
func TimeToString(tm *time.Time, layout string) *string {
if tm == nil {
return nil
}
return trans.String(tm.Format(layout))
}
// TimestamppbToTime timestamppb.Timestamp -> time.Time
func TimestamppbToTime(tm *timestamppb.Timestamp) *time.Time {
if tm != nil {
return trans.Ptr(tm.AsTime())
func TimestamppbToTime(timestamp *timestamppb.Timestamp) *time.Time {
if timestamp != nil {
return trans.Ptr(timestamp.AsTime())
}
return nil
}

View File

@@ -2,13 +2,14 @@ package timeutil
import (
"fmt"
"google.golang.org/protobuf/types/known/timestamppb"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/trans"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestUnixMilliToStringPtr(t *testing.T) {
@@ -148,6 +149,49 @@ func TestTimeToDateString(t *testing.T) {
assert.Nil(t, result)
}
func TestTimeToString(t *testing.T) {
now := time.Now()
fmt.Println(*TimeToString(&now, DateLayout))
fmt.Println(*TimeToString(&now, ISO8601))
fmt.Println(*TimeToString(&now, ISO8601TZHour))
fmt.Println(*TimeToString(&now, ISO8601NoTZ))
layout := "2006-01-02"
expected := now.Format(layout)
result := TimeToString(&now, layout)
assert.NotNil(t, result)
assert.Equal(t, expected, *result)
// 测试空输入
result = TimeToString(nil, layout)
assert.Nil(t, result)
}
func TestStringToTime(t *testing.T) {
// 测试有效时间字符串输入
input := "2023-03-09 12:34:56"
layout := "2006-01-02 15:04:05"
expected := time.Date(2023, 3, 9, 12, 34, 56, 0, GetDefaultTimeLocation())
result := StringToTime(&input, layout)
assert.NotNil(t, result)
assert.Equal(t, expected, *result)
// 测试无效时间字符串输入
invalidInput := "invalid-date"
result = StringToTime(&invalidInput, layout)
assert.Nil(t, result)
// 测试空字符串输入
emptyInput := ""
result = StringToTime(&emptyInput, layout)
assert.Nil(t, result)
// 测试空指针输入
result = StringToTime(nil, layout)
assert.Nil(t, result)
}
func TestTimestamppbToTime(t *testing.T) {
// 测试有效输入
timestamp := timestamppb.Now()