提交 6d8995d2 authored 作者: mooncake's avatar mooncake

修改xtime

上级 ddf10376
...@@ -47,7 +47,7 @@ require ( ...@@ -47,7 +47,7 @@ require (
golang.org/x/sync v0.16.0 golang.org/x/sync v0.16.0
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.36.9 google.golang.org/protobuf v1.36.9
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.2 gorm.io/driver/mysql v1.5.2
gorm.io/driver/postgres v1.5.4 gorm.io/driver/postgres v1.5.4
gorm.io/driver/sqlite v1.5.4 gorm.io/driver/sqlite v1.5.4
......
...@@ -2,105 +2,279 @@ package xtime ...@@ -2,105 +2,279 @@ package xtime
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"gopkg.in/yaml.v3"
) )
// Date 自定义日期类型,基于 time.Time,但只关心年月日
type Date time.Time type Date time.Time
// 使用常量确保格式一致性,便于维护
const (
dateFormat = "2006-01-02" // 标准ISO格式
)
// Scan 改进:增强健壮性,统一解析路径,优化错误信息
func (d *Date) Scan(value interface{}) error { func (d *Date) Scan(value interface{}) error {
if value == nil {
*d = Date(time.Time{}) // 明确处理数据库NULL值
return nil
}
switch v := value.(type) { switch v := value.(type) {
case []byte: case []byte:
parsed, err := time.Parse("2006-01-02", string(v)) if len(v) == 0 {
if err != nil { *d = Date(time.Time{})
return err return nil
} }
*d = Date(parsed) return d.parseString(string(v))
case string: case string:
parsed, err := time.Parse("2006-01-02", v) if v == "" {
if err != nil { *d = Date(time.Time{})
return err return nil
} }
*d = Date(parsed) return d.parseString(v)
case time.Time: case time.Time:
if v, ok := value.(time.Time); ok { // 提取日期部分,忽略时间组件,同时保留原时区信息
*d = Date(v) y, m, day := v.Date()
} *d = Date(time.Date(y, m, day, 0, 0, 0, 0, v.Location()))
return nil
default: default:
return fmt.Errorf("unsupported type: %T", v) // 更清晰的错误信息,指导使用者
return fmt.Errorf("Date.Scan: 不支持的扫描类型 %T, 期望: string, []byte 或 time.Time", value)
} }
return nil
} }
// Value 改进:更精确的零值处理和返回类型
func (d Date) Value() (driver.Value, error) { func (d Date) Value() (driver.Value, error) {
return d.Time().Format("2006-01-02"), nil t := time.Time(d)
if t.IsZero() {
return nil, nil // 零值对应数据库NULL
}
// 返回格式化的日期字符串,确保数据库接收明确格式
return t.Format(dateFormat), nil
} }
// MarshalJSON 自定义JSON序列化 // MarshalJSON 改进:使用更安全的JSON编码方式
func (d Date) MarshalJSON() ([]byte, error) { func (d Date) MarshalJSON() ([]byte, error) {
return []byte(`"` + d.Time().Format("2006-01-02") + `"`), nil t := time.Time(d)
if t.IsZero() {
return []byte("null"), nil
}
// 使用标准JSON编码器,避免手动拼接可能导致的转义错误
return json.Marshal(t.Format(dateFormat))
} }
// UnmarshalJSON 自定义JSON反序列化 // UnmarshalJSON 改进:增强格式兼容性和错误处理
func (d *Date) UnmarshalJSON(data []byte) error { func (d *Date) UnmarshalJSON(data []byte) error {
str := strings.Trim(string(data), `"`) str := strings.TrimSpace(string(data))
parsed, err := time.Parse("2006-01-02", str)
if err != nil { // 处理显式null和空字符串
return err if str == "null" || str == `""` || str == "" {
} *d = Date(time.Time{})
*d = Date(parsed)
return nil return nil
}
// 安全地去除JSON字符串的引号
var dateStr string
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
dateStr = str[1 : len(str)-1]
} else {
// 如果不是引号包裹的字符串,尝试直接解析
dateStr = str
}
return d.parseString(dateStr)
} }
// MarshalYAML 自定义YAML序列化 // MarshalYAML 改进:符合yaml.v3接口标准
func (d Date) MarshalYAML() (interface{}, error) { func (d Date) MarshalYAML() (interface{}, error) {
return d.Time().Format("2006-01-02"), nil t := time.Time(d)
if t.IsZero() {
return nil, nil
}
return t.Format(dateFormat), nil
}
// UnmarshalYAML 改进:使用正确的yaml.v3 Node接口
func (d *Date) UnmarshalYAML(value *yaml.Node) error {
if value == nil || value.Kind != yaml.ScalarNode {
return fmt.Errorf("Date.UnmarshalYAML: 日期必须为标量值(字符串)")
}
str := strings.TrimSpace(value.Value)
if str == "" || str == "null" || str == "~" {
*d = Date(time.Time{})
return nil
}
return d.parseString(str)
} }
// String 返回日期字符串表示
func (d Date) String() string {
t := time.Time(d)
if t.IsZero() {
return "" // 零值返回空字符串,符合Go惯例
}
return t.Format(dateFormat)
}
// Time 返回time.Time类型(去除时间部分)
func (d Date) Time() time.Time { func (d Date) Time() time.Time {
return time.Time(d) t := time.Time(d)
if t.IsZero() {
return time.Time{}
}
// 确保只返回日期部分,时间设为00:00:00
y, m, day := t.Date()
return time.Date(y, m, day, 0, 0, 0, 0, t.Location())
} }
// UnmarshalYAML 自定义YAML反序列化 // parseString 内部解析方法改进:支持多种格式,优化错误处理
func (d *Date) UnmarshalYAML(value interface{}) error { func (d *Date) parseString(s string) error {
str, ok := value.(string) s = strings.TrimSpace(s)
if !ok { if s == "" {
return fmt.Errorf("date must be a string") *d = Date(time.Time{})
return nil
} }
parsed, err := time.Parse("2006-01-02", str)
if err != nil { // 定义支持的日期格式(按优先级排序)
return err formats := []string{
"2006-01-02", // 标准ISO格式(优先)
"2006/01/02", // 斜杠格式
"20060102", // 紧凑格式
"02-01-2006", // 日-月-年格式
"02/01/2006", // 日/月/年格式
"January 2, 2006", // 英文全写格式
"Jan 2, 2006", // 英文缩写格式
} }
*d = Date(parsed)
var firstErr error
for _, format := range formats {
parsed, err := time.Parse(format, s)
if err == nil {
// 成功解析,提取日期部分
y, m, day := parsed.Date()
*d = Date(time.Date(y, m, day, 0, 0, 0, 0, parsed.Location()))
return nil return nil
}
if firstErr == nil {
firstErr = err
}
}
return fmt.Errorf("Date.parseString: 无法解析日期 %q, 支持的格式示例: 2006-01-02", s)
} }
// String 返回日期字符串表示 // Equal 改进:精确的日期比较(只比较年月日)
func (d Date) String() string { func (d Date) Equal(other Date) bool {
return d.Time().Format("2006-01-02") t1 := time.Time(d).UTC().Truncate(24 * time.Hour)
t2 := time.Time(other).UTC().Truncate(24 * time.Hour)
return t1.Equal(t2)
}
// Before 检查当前日期是否在另一个日期之前
func (d Date) Before(other Date) bool {
if d.Equal(other) {
return false
}
return d.Time().Before(other.Time())
}
// After 检查当前日期是否在另一个日期之后
func (d Date) After(other Date) bool {
if d.Equal(other) {
return false
}
return d.Time().After(other.Time())
}
// IsZero 检查是否为零值
func (d Date) IsZero() bool {
return time.Time(d).IsZero()
}
// AddDays 添加指定天数
func (d Date) AddDays(days int) Date {
t := d.Time().AddDate(0, 0, days)
return Date(t)
}
// DaysBetween 计算两个日期之间的天数差(考虑自然日)
func (d Date) DaysBetween(other Date) int {
// 将两个日期都规范到UTC的零点,消除时间和时区影响
t1 := d.Time().UTC().Truncate(24 * time.Hour)
t2 := other.Time().UTC().Truncate(24 * time.Hour)
// 计算天数差(使用正确的顺序)
hours := t1.Sub(t2).Hours()
days := int(hours / 24)
return days
} }
// Today 返回当前日期 // Today 返回当前日期(忽略时间部分)
func TodayDate() Date { func Today() Date {
now := time.Now() now := time.Now()
return Date(time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())) y, m, day := now.Date()
return Date(time.Date(y, m, day, 0, 0, 0, 0, now.Location()))
} }
// ParseDate 从字符串解析日期 // ParseDate 从字符串解析日期
func ParseDate(s string) (Date, error) { func ParseDate(s string) (Date, error) {
t, err := time.Parse("2006-01-02", s) var d Date
if err != nil { err := d.parseString(s)
return Date{}, err return d, err
}
return Date(t), nil
} }
// MustParseDate 从字符串解析日期,如果解析失败会panic // MustParseDate 从字符串解析日期,解析失败时panic
func MustParseDate(s string) Date { func MustParseDate(s string) Date {
d, err := ParseDate(s) d, err := ParseDate(s)
if err != nil { if err != nil {
panic(err) panic(fmt.Sprintf("MustParseDate 解析失败: %q, 错误: %v", s, err))
} }
return d return d
} }
// 新增便捷方法
// Format 使用自定义格式格式化日期
func (d Date) Format(layout string) string {
return d.Time().Format(layout)
}
// Weekday 返回星期几
func (d Date) Weekday() time.Weekday {
return d.Time().Weekday()
}
// Year 返回年份
func (d Date) Year() int {
return d.Time().Year()
}
// Month 返回月份
func (d Date) Month() time.Month {
return d.Time().Month()
}
// Day 返回日期
func (d Date) Day() int {
return d.Time().Day()
}
// IsWeekend 检查是否为周末
func (d Date) IsWeekend() bool {
weekday := d.Weekday()
return weekday == time.Saturday || weekday == time.Sunday
}
// DaysSince 计算从该日期到现在的天数
func (d Date) DaysSince() int {
return Today().DaysBetween(d)
}
package xtime package xtime
import ( import (
"database/sql/driver" "encoding/json"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "gopkg.in/yaml.v3"
) )
// TestDate_Scan 测试数据库扫描功能
func TestDate_Scan(t *testing.T) { func TestDate_Scan(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input interface{} input interface{}
want Date want string
wantErr bool wantErr bool
}{ }{
{ {
name: "valid byte slice", name: "有效时间类型",
input: []byte("2023-05-15"), input: time.Date(2023, 10, 15, 15, 30, 0, 0, time.UTC),
want: MustParseDate("2023-05-15"), want: "2023-10-15",
wantErr: false, wantErr: false,
}, },
{ {
name: "valid string", name: "有效字节切片",
input: "2023-05-15", input: []byte("2023-10-15"),
want: MustParseDate("2023-05-15"), want: "2023-10-15",
wantErr: false, wantErr: false,
}, },
{ {
name: "invalid type", name: "有效字符串",
input: 123, input: "2023-10-15",
want: Date{}, want: "2023-10-15",
wantErr: true, wantErr: false,
},
{
name: "空值处理",
input: nil,
want: "",
wantErr: false,
}, },
{ {
name: "invalid date format", name: "空字符串",
input: "15-05-2023", input: "",
want: Date{}, want: "",
wantErr: false,
},
{
name: "无效类型",
input: 123, // 数字类型不支持
want: "",
wantErr: true, wantErr: true,
}, },
} }
...@@ -45,257 +58,539 @@ func TestDate_Scan(t *testing.T) { ...@@ -45,257 +58,539 @@ func TestDate_Scan(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var d Date var d Date
err := d.Scan(tt.input) err := d.Scan(tt.input)
if tt.wantErr {
assert.Error(t, err) if (err != nil) != tt.wantErr {
} else { t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr)
assert.NoError(t, err) return
assert.Equal(t, tt.want, d) }
if !tt.wantErr && d.String() != tt.want {
t.Errorf("Scan() = %v, want %v", d.String(), tt.want)
} }
}) })
} }
} }
// TestDate_Value 测试数据库值生成
func TestDate_Value(t *testing.T) { func TestDate_Value(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
date Date input Date
want driver.Value want interface{}
wantErr bool wantErr bool
}{ }{
{ {
name: "valid date", name: "有效日期",
date: MustParseDate("2023-05-15"), input: Date(time.Date(2023, 10, 15, 0, 0, 0, 0, time.UTC)),
want: "2023-05-15", want: "2023-10-15",
wantErr: false, wantErr: false,
}, },
{ {
name: "zero date", name: "零值日期",
date: Date{}, input: Date(time.Time{}),
want: "0001-01-01", want: nil,
wantErr: false, wantErr: false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := tt.date.Value() got, err := tt.input.Value()
if tt.wantErr {
assert.Error(t, err) if (err != nil) != tt.wantErr {
} else { t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr)
assert.NoError(t, err) return
assert.Equal(t, tt.want, got) }
if !tt.wantErr && got != tt.want {
t.Errorf("Value() = %v, want %v", got, tt.want)
} }
}) })
} }
} }
func TestDate_MarshalJSON(t *testing.T) { // TestDate_JSON 测试JSON序列化和反序列化[3](@ref)
func TestDate_JSON(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
date Date input string
want string want string
wantErr bool wantErr bool
}{ }{
{ {
name: "valid date", name: "有效日期JSON",
date: MustParseDate("2023-05-15"), input: `"2023-10-15"`,
want: `"2023-05-15"`, want: "2023-10-15",
wantErr: false, wantErr: false,
}, },
{ {
name: "zero date", name: "空值JSON",
date: Date{}, input: "null",
want: `"0001-01-01"`, want: "",
wantErr: false,
},
{
name: "空字符串JSON",
input: `""`,
want: "",
wantErr: false, wantErr: false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := tt.date.MarshalJSON() // 测试反序列化
if tt.wantErr { var d Date
assert.Error(t, err) err := json.Unmarshal([]byte(tt.input), &d)
} else {
assert.NoError(t, err) if (err != nil) != tt.wantErr {
assert.Equal(t, tt.want, string(got)) t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && d.String() != tt.want {
t.Errorf("UnmarshalJSON() = %v, want %v", d.String(), tt.want)
return
}
// 测试序列化(仅对有效用例)
if !tt.wantErr && tt.want != "" {
bytes, err := json.Marshal(d)
if err != nil {
t.Errorf("MarshalJSON() error = %v", err)
return
}
expectedJSON := `"` + tt.want + `"`
if string(bytes) != expectedJSON {
t.Errorf("MarshalJSON() = %s, want %s", string(bytes), expectedJSON)
}
} }
}) })
} }
} }
func TestDate_UnmarshalJSON(t *testing.T) { // TestDate_YAML 测试YAML序列化和反序列化
func TestDate_YAML(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input string input string
want Date want string
wantErr bool wantErr bool
}{ }{
{ {
name: "valid date", name: "有效日期YAML",
input: `"2023-05-15"`, input: "2023-10-15",
want: MustParseDate("2023-05-15"), want: "2023-10-15",
wantErr: false, wantErr: false,
}, },
{ {
name: "invalid format", name: "空字符串YAML",
input: `"15-05-2023"`, input: "",
want: Date{}, want: "",
wantErr: true, wantErr: false,
},
{
name: "not a string",
input: `123`,
want: Date{},
wantErr: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// 创建YAML节点进行测试[4](@ref)
node := &yaml.Node{
Kind: yaml.ScalarNode,
Value: tt.input,
}
var d Date var d Date
err := d.UnmarshalJSON([]byte(tt.input)) err := d.UnmarshalYAML(node)
if tt.wantErr {
assert.Error(t, err) if (err != nil) != tt.wantErr {
} else { t.Errorf("UnmarshalYAML() error = %v, wantErr %v", err, tt.wantErr)
assert.NoError(t, err) return
assert.Equal(t, tt.want, d) }
if !tt.wantErr && d.String() != tt.want {
t.Errorf("UnmarshalYAML() = %v, want %v", d.String(), tt.want)
} }
}) })
} }
} }
func TestDate_MarshalYAML(t *testing.T) { // TestDate_String 测试字符串表示
func TestDate_String(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
date Date input Date
want string want string
wantErr bool
}{ }{
{ {
name: "valid date", name: "有效日期",
date: MustParseDate("2023-05-15"), input: Date(time.Date(2023, 10, 15, 0, 0, 0, 0, time.UTC)),
want: "2023-05-15", want: "2023-10-15",
wantErr: false,
}, },
{ {
name: "zero date", name: "零值日期",
date: Date{}, input: Date(time.Time{}),
want: "0001-01-01", want: "",
wantErr: false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := tt.date.MarshalYAML() if got := tt.input.String(); got != tt.want {
if tt.wantErr { t.Errorf("String() = %v, want %v", got, tt.want)
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
} }
}) })
} }
} }
func TestDate_UnmarshalYAML(t *testing.T) { // TestParseDate 测试日期解析函数[6](@ref)
func TestParseDate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input interface{} input string
want Date want string
wantErr bool wantErr bool
}{ }{
{ {
name: "valid date string", name: "有效日期解析",
input: "2023-05-15", input: "2023-10-15",
want: MustParseDate("2023-05-15"), want: "2023-10-15",
wantErr: false, wantErr: false,
}, },
{ {
name: "invalid format", name: "斜杠格式日期",
input: "15-05-2023", input: "2023/10/15",
want: Date{}, want: "2023-10-15",
wantErr: true, wantErr: false,
},
{
name: "紧凑格式日期",
input: "20231015",
want: "2023-10-15",
wantErr: false,
}, },
{ {
name: "not a string", name: "无效日期格式",
input: 123, input: "invalid-date",
want: Date{}, want: "",
wantErr: true, wantErr: true,
}, },
{
name: "空字符串",
input: "",
want: "",
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var d Date got, err := ParseDate(tt.input)
err := d.UnmarshalYAML(tt.input)
if tt.wantErr { if (err != nil) != tt.wantErr {
assert.Error(t, err) t.Errorf("ParseDate() error = %v, wantErr %v", err, tt.wantErr)
} else { return
assert.NoError(t, err) }
assert.Equal(t, tt.want, d)
if !tt.wantErr && got.String() != tt.want {
t.Errorf("ParseDate() = %v, want %v", got.String(), tt.want)
} }
}) })
} }
} }
func TestDate_Time(t *testing.T) { // TestDate_Comparison 测试日期比较方法[7,8](@ref)
date := MustParseDate("2023-05-15") func TestDate_Comparison(t *testing.T) {
expected := time.Date(2023, time.May, 15, 0, 0, 0, 0, time.UTC) date1 := MustParseDate("2023-10-15")
assert.Equal(t, expected, date.Time().UTC()) date2 := MustParseDate("2023-10-16")
date3 := MustParseDate("2023-10-15") // 与date1相同
t.Run("After方法", func(t *testing.T) {
if !date2.After(date1) {
t.Error("date2应该在date1之后")
}
if date1.After(date2) {
t.Error("date1不应该在date2之后")
}
})
t.Run("Before方法", func(t *testing.T) {
if !date1.Before(date2) {
t.Error("date1应该在date2之前")
}
if date2.Before(date1) {
t.Error("date2不应该在date1之前")
}
})
t.Run("Equal方法", func(t *testing.T) {
if !date1.Equal(date3) {
t.Error("date1应该等于date3")
}
if date1.Equal(date2) {
t.Error("date1不应该等于date2")
}
})
} }
func TestDate_String(t *testing.T) { // TestDate_Arithmetic 测试日期算术运算
date := MustParseDate("2023-05-15") func TestDate_Arithmetic(t *testing.T) {
assert.Equal(t, "2023-05-15", date.String()) baseDate := MustParseDate("2023-10-15")
t.Run("AddDays方法", func(t *testing.T) {
result := baseDate.AddDays(5)
expected := MustParseDate("2023-10-20")
if !result.Equal(expected) {
t.Errorf("AddDays(5) = %v, want %v", result, expected)
}
result = baseDate.AddDays(-3)
expected = MustParseDate("2023-10-12")
if !result.Equal(expected) {
t.Errorf("AddDays(-3) = %v, want %v", result, expected)
}
})
t.Run("DaysBetween方法", func(t *testing.T) {
date1 := MustParseDate("2023-10-15")
date2 := MustParseDate("2023-10-20")
days := date2.DaysBetween(date1)
if days != 5 {
t.Errorf("DaysBetween() = %v, want 5", days)
}
days = date1.DaysBetween(date2)
if days != -5 {
t.Errorf("DaysBetween() = %v, want -5", days)
}
})
} }
// TestDate_Components 测试日期组件获取
func TestDate_Components(t *testing.T) {
date := MustParseDate("2023-10-15")
t.Run("Year方法", func(t *testing.T) {
if year := date.Year(); year != 2023 {
t.Errorf("Year() = %v, want 2023", year)
}
})
t.Run("Month方法", func(t *testing.T) {
if month := date.Month(); month != time.October {
t.Errorf("Month() = %v, want October", month)
}
})
t.Run("Day方法", func(t *testing.T) {
if day := date.Day(); day != 15 {
t.Errorf("Day() = %v, want 15", day)
}
})
t.Run("Weekday方法", func(t *testing.T) {
// 2023-10-15是星期日
if weekday := date.Weekday(); weekday != time.Sunday {
t.Errorf("Weekday() = %v, want Sunday", weekday)
}
})
}
// TestDate_ZeroValue 测试零值处理
func TestDate_ZeroValue(t *testing.T) {
var zeroDate Date
t.Run("IsZero方法", func(t *testing.T) {
if !zeroDate.IsZero() {
t.Error("IsZero()应该返回true对于零值")
}
validDate := MustParseDate("2023-10-15")
if validDate.IsZero() {
t.Error("IsZero()应该返回false对于有效日期")
}
})
t.Run("零值的字符串表示", func(t *testing.T) {
if zeroDate.String() != "" {
t.Errorf("零值的String()应该返回空字符串, 得到: %s", zeroDate.String())
}
})
t.Run("零值的JSON序列化", func(t *testing.T) {
bytes, err := json.Marshal(zeroDate)
if err != nil {
t.Errorf("零值JSON序列化错误: %v", err)
}
if string(bytes) != "null" {
t.Errorf("零值JSON应该序列化为null, 得到: %s", string(bytes))
}
})
}
// TestToday 测试Today函数[7,8](@ref)
func TestToday(t *testing.T) { func TestToday(t *testing.T) {
today := Today()
now := time.Now() now := time.Now()
expected := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) expected := Date(time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()))
assert.Equal(t, expected, TodayDate().Time())
if !today.Equal(expected) {
t.Errorf("Today() = %v, want %v", today, expected)
}
} }
func TestParseDate(t *testing.T) { // TestMustParseDate 测试MustParseDate函数
tests := []struct { func TestMustParseDate(t *testing.T) {
t.Run("有效日期", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("有效日期不应该引起panic")
}
}()
date := MustParseDate("2023-10-15")
if date.String() != "2023-10-15" {
t.Errorf("MustParseDate() = %v, want 2023-10-15", date.String())
}
})
t.Run("无效日期应该panic", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("无效日期应该引起panic")
}
}()
_ = MustParseDate("invalid-date")
})
}
// BenchmarkDate_Parse 性能测试:日期解析[3](@ref)
func BenchmarkDate_Parse(b *testing.B) {
testCases := []string{
"2023-10-15",
"2023/10/15",
"20231015",
"",
}
for _, tc := range testCases {
b.Run(tc, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = ParseDate(tc)
}
})
}
}
// BenchmarkDate_JSONMarshal 性能测试:JSON序列化
func BenchmarkDate_JSONMarshal(b *testing.B) {
date := MustParseDate("2023-10-15")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = json.Marshal(date)
}
}
// BenchmarkDate_JSONUnmarshal 性能测试:JSON反序列化
func BenchmarkDate_JSONUnmarshal(b *testing.B) {
testCases := []struct {
name string name string
input string data []byte
want Date
wantErr bool
}{ }{
{ {"有效日期", []byte(`"2023-10-15"`)},
name: "valid date", {"空值", []byte(`null`)},
input: "2023-05-15",
want: MustParseDate("2023-05-15"),
wantErr: false,
},
{
name: "invalid format",
input: "15-05-2023",
want: Date{},
wantErr: true,
},
} }
for _, tt := range tests { for _, tc := range testCases {
t.Run(tt.name, func(t *testing.T) { b.Run(tc.name, func(b *testing.B) {
got, err := ParseDate(tt.input) var date Date
if tt.wantErr { b.ResetTimer()
assert.Error(t, err)
} else { for i := 0; i < b.N; i++ {
assert.NoError(t, err) _ = json.Unmarshal(tc.data, &date)
assert.Equal(t, tt.want, got)
} }
}) })
} }
} }
func TestMustParseDate(t *testing.T) { // BenchmarkDate_Value 性能测试:数据库值生成
assert.NotPanics(t, func() { func BenchmarkDate_Value(b *testing.B) {
_ = MustParseDate("2023-05-15") date := MustParseDate("2023-10-15")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = date.Value()
}
}
// TestDate_EdgeCases 测试边界情况[4](@ref)
func TestDate_EdgeCases(t *testing.T) {
t.Run("闰年测试", func(t *testing.T) {
leapDate := MustParseDate("2020-02-29")
if leapDate.String() != "2020-02-29" {
t.Error("闰年日期处理错误")
}
}) })
assert.Panics(t, func() { t.Run("时区处理", func(t *testing.T) {
_ = MustParseDate("invalid-date") utcTime := time.Date(2023, 10, 15, 22, 0, 0, 0, time.UTC)
cstTime := time.Date(2023, 10, 16, 6, 0, 0, 0, time.FixedZone("CST", 8*3600))
utcDate := Date(utcTime)
cstDate := Date(cstTime)
if !utcDate.Equal(cstDate) {
t.Error("不同时区的同一天应该相等")
}
})
t.Run("月份边界", func(t *testing.T) {
lastDay := MustParseDate("2023-01-31")
nextMonth := lastDay.AddDays(1)
if nextMonth.String() != "2023-02-01" {
t.Errorf("月份边界处理错误: %v", nextMonth)
}
})
}
// TestDate_Integration 集成测试[1](@ref)
func TestDate_Integration(t *testing.T) {
t.Run("完整序列化循环", func(t *testing.T) {
original := MustParseDate("2023-10-15")
// JSON序列化 -> 反序列化
jsonData, err := json.Marshal(original)
if err != nil {
t.Fatalf("JSON序列化失败: %v", err)
}
var fromJSON Date
err = json.Unmarshal(jsonData, &fromJSON)
if err != nil {
t.Fatalf("JSON反序列化失败: %v", err)
}
if !original.Equal(fromJSON) {
t.Error("JSON序列化循环失败")
}
// 数据库值循环
dbValue, err := original.Value()
if err != nil {
t.Fatalf("Value()失败: %v", err)
}
var fromDB Date
err = fromDB.Scan(dbValue)
if err != nil {
t.Fatalf("Scan()失败: %v", err)
}
if !original.Equal(fromDB) {
t.Error("数据库循环失败")
}
}) })
} }
...@@ -2,10 +2,14 @@ package xtime ...@@ -2,10 +2,14 @@ package xtime
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json"
"fmt" "fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"time" "time"
"gopkg.in/yaml.v3"
) )
const ( const (
...@@ -27,89 +31,231 @@ var ( ...@@ -27,89 +31,231 @@ var (
LocBeiJing, _ = time.LoadLocation("Asia/Shanghai") LocBeiJing, _ = time.LoadLocation("Asia/Shanghai")
) )
// 定义time.Time的别名 // DateTime 定义基于 time.Time 的自定义日期时间类型
type DateTime time.Time type DateTime time.Time
// 自定义json序列化 // 定义支持的时间格式常量(按优先级排序)
func (x DateTime) MarshalJSON() ([]byte, error) { const (
if x.IsZero() { DateTimeFormat = "2006-01-02 15:04:05" // 标准日期时间格式
return []byte(`""`), nil DateFormat = "2006-01-02" // 仅日期格式
RFC3339Format = time.RFC3339 // RFC3339 标准格式
CompactDateTimeFormat = "20060102150405" // 紧凑格式
)
// MarshalJSON 自定义JSON序列化
func (dt DateTime) MarshalJSON() ([]byte, error) {
t := time.Time(dt)
if t.IsZero() {
return []byte("null"), nil
} }
var jsonTimeStr = fmt.Sprintf(`"%s"`, time.Time(x).Format("2006-01-02 15:04:05")) // 使用标准JSON编码确保引号正确处理
return []byte(jsonTimeStr), nil return json.Marshal(t.Format(DateTimeFormat))
} }
// 自定义json反序列化 // UnmarshalJSON 自定义JSON反序列化,支持多种格式
func (x *DateTime) UnmarshalJSON(data []byte) error { func (dt *DateTime) UnmarshalJSON(data []byte) error {
if len(data) == 0 || string(data) == `""` { str := strings.TrimSpace(string(data))
*x = DateTime{}
// 处理空值和null
if str == "null" || str == `""` || str == "" {
*dt = DateTime(time.Time{})
return nil return nil
} }
t, err := time.Parse(`"2006-01-02 15:04:05"`, string(data))
if err != nil { // 移除JSON字符串的引号
return err if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
str = str[1 : len(str)-1]
} }
*x = DateTime(t)
return dt.parseString(str)
}
// MarshalYAML 自定义YAML序列化
func (dt DateTime) MarshalYAML() (interface{}, error) {
t := time.Time(dt)
if t.IsZero() {
return nil, nil
}
return t.Format(DateTimeFormat), nil
}
// UnmarshalYAML 自定义YAML反序列化
func (dt *DateTime) UnmarshalYAML(value *yaml.Node) error {
if value == nil || value.Kind != yaml.ScalarNode {
return fmt.Errorf("DateTime.UnmarshalYAML: 日期时间必须为字符串值")
}
str := strings.TrimSpace(value.Value)
if str == "" || str == "null" {
*dt = DateTime(time.Time{})
return nil return nil
}
return dt.parseString(str)
} }
func (x DateTime) Time() time.Time { // String 返回日期时间的字符串表示
return time.Time(x) func (dt DateTime) String() string {
t := time.Time(dt)
if t.IsZero() {
return ""
}
return t.Format(DateTimeFormat)
} }
func (x DateTime) IsZero() bool { // parseString 内部解析方法,支持多种时间格式
return x.Time().IsZero() func (dt *DateTime) parseString(s string) error {
s = strings.TrimSpace(s)
if s == "" {
*dt = DateTime(time.Time{})
return nil
}
// 支持的时间格式列表(按优先级排序)
formats := []string{
DateTimeFormat, // "2006-01-02 15:04:05"
DateFormat, // "2006-01-02"
RFC3339Format, // RFC3339格式
"2006/01/02 15:04:05", // 斜杠分隔格式
CompactDateTimeFormat, // 紧凑格式
time.RFC1123, // HTTP日期格式
}
var firstErr error
for _, format := range formats {
parsed, err := time.Parse(format, s)
if err == nil {
*dt = DateTime(parsed)
return nil
}
if firstErr == nil {
firstErr = err
}
}
return fmt.Errorf("DateTime.parseString: 无法解析日期时间 %q, 支持的格式示例: %s", s, DateTimeFormat)
} }
func (x DateTime) IsNotZero() bool {
return !x.IsZero() // Time 转换为标准的 time.Time
func (dt DateTime) Time() time.Time {
return time.Time(dt)
} }
func NewDateTime(t time.Time) DateTime { // IsZero 检查是否为零值
return DateTime(t) func (dt DateTime) IsZero() bool {
return time.Time(dt).IsZero()
} }
func Now() DateTime { // IsNotZero 检查是否非零值
return DateTime(time.Now()) func (dt DateTime) IsNotZero() bool {
return !dt.IsZero()
} }
func NowPtr() *DateTime { // Format 使用自定义格式格式化日期时间
j := DateTime(time.Now()) func (dt DateTime) Format(layout string) string {
return &j return time.Time(dt).Format(layout)
} }
// Value insert timestamp into mysql need this function. // Value 实现 driver.Valuer 接口,用于数据库存储
func (t DateTime) Value() (driver.Value, error) { func (dt DateTime) Value() (driver.Value, error) {
var zeroTime time.Time t := time.Time(dt)
if time.Time(t).UnixNano() == zeroTime.UnixNano() { if t.IsZero() {
return nil, nil return nil, nil
} }
return time.Time(t), nil return t, nil
} }
// Scan value of time.Time // Scan 实现 sql.Scanner 接口,用于从数据库读取
func (t *DateTime) Scan(value interface{}) error { func (dt *DateTime) Scan(value interface{}) error {
if value == nil {
*dt = DateTime(time.Time{})
return nil
}
switch v := value.(type) { switch v := value.(type) {
case time.Time: case time.Time:
val, ok := value.(time.Time) *dt = DateTime(v)
if ok { return nil
*t = DateTime(val) case []byte:
if len(v) == 0 {
*dt = DateTime(time.Time{})
return nil return nil
} }
return fmt.Errorf("unsupported time format: %T", v) return dt.parseString(string(v))
case []uint8: case string:
// 假设是RFC3339格式的二进制时间 if v == "" {
str := string(v) *dt = DateTime(time.Time{})
val, err := time.Parse(time.RFC3339, str)
if err != nil {
return err
}
*t = DateTime(val)
return nil return nil
}
return dt.parseString(v)
default: default:
return fmt.Errorf("unsupported time format: %T", v) return fmt.Errorf("DateTime.Scan: 不支持的扫描类型 %T", value)
} }
} }
// After 检查当前时间是否在另一个时间之后
func (dt DateTime) After(other DateTime) bool {
return time.Time(dt).After(time.Time(other))
}
// Before 检查当前时间是否在另一个时间之前
func (dt DateTime) Before(other DateTime) bool {
return time.Time(dt).Before(time.Time(other))
}
// Equal 检查两个时间是否相等
func (dt DateTime) Equal(other DateTime) bool {
return time.Time(dt).Equal(time.Time(other))
}
// Add 添加时间间隔
func (dt DateTime) Add(duration time.Duration) DateTime {
return DateTime(time.Time(dt).Add(duration))
}
// Unix 返回Unix时间戳
func (dt DateTime) Unix() int64 {
return time.Time(dt).Unix()
}
// UnixNano 返回纳秒级Unix时间戳
func (dt DateTime) UnixNano() int64 {
return time.Time(dt).UnixNano()
}
// 构造函数
// NewDateTime 从 time.Time 创建 DateTime
func NewDateTime(t time.Time) DateTime {
return DateTime(t)
}
// Now 返回当前日期时间
func Now() DateTime {
return DateTime(time.Now())
}
// NowPtr 返回当前日期时间的指针
func NowPtr() *DateTime {
dt := DateTime(time.Now())
return &dt
}
// ParseDateTime 从字符串解析日期时间
func ParseDateTime(s string) (DateTime, error) {
var dt DateTime
err := dt.parseString(s)
return dt, err
}
// MustParseDateTime 从字符串解析日期时间,解析失败时panic
func MustParseDateTime(s string) DateTime {
dt, err := ParseDateTime(s)
if err != nil {
panic(fmt.Sprintf("MustParseDateTime 解析失败: %q, 错误: %v", s, err))
}
return dt
}
func BeginOfDay(t time.Time) DateTime { func BeginOfDay(t time.Time) DateTime {
y, m, d := t.Date() y, m, d := t.Date()
begin := time.Date(y, m, d, 0, 0, 0, 0, t.Location()) begin := time.Date(y, m, d, 0, 0, 0, 0, t.Location())
...@@ -187,34 +333,10 @@ func ParseExtendedDuration(s string) (time.Duration, error) { ...@@ -187,34 +333,10 @@ func ParseExtendedDuration(s string) (time.Duration, error) {
return total, nil return total, nil
} }
func (x DateTime) Format(layout string) string {
return x.Time().Format(layout)
}
func (x DateTime) FormatYYYYMMDDHHmmSS() string { func (x DateTime) FormatYYYYMMDDHHmmSS() string {
return x.Time().Format(Layout_YYYYMMDDHHmmSS) return x.Time().Format(Layout_YYYYMMDDHHmmSS)
} }
// const TimeFormat = "2006-01-02 15:04:05"
// // Value insert timestamp into mysql need this function.
// func (t DateTime) Value() (driver.Value, error) {
// var zeroTime time.Time
// if time.Time(t).UnixNano() == zeroTime.UnixNano() {
// return nil, nil
// }
// return time.Time(t), nil
// }
// // Scan value of time.Time
// func (t *DateTime) Scan(v interface{}) error {
// value, ok := v.(time.Time)
// if ok {
// *t = DateTime(value)
// return nil
// }
// return fmt.Errorf("can not convert %v to timestamp", v)
// }
func (x DateTime) Date() DateTime { func (x DateTime) Date() DateTime {
y, m, d := x.Time().Date() y, m, d := x.Time().Date()
trimmedTime := time.Date(y, m, d, 0, 0, 0, 0, x.Time().Location()) trimmedTime := time.Date(y, m, d, 0, 0, 0, 0, x.Time().Location())
...@@ -245,8 +367,39 @@ func (t0 DateTime) MonthEnd() DateTime { ...@@ -245,8 +367,39 @@ func (t0 DateTime) MonthEnd() DateTime {
return DateTime(t1) return DateTime(t1)
} }
func (t DateTime) AddDate(y, m, d int) DateTime { // func (t DateTime) AddDate(y, m, d int) DateTime {
return DateTime(t.Time().AddDate(y, m, d)) // return DateTime(t.Time().AddDate(y, m, d))
// }
// AddDate 添加年、月、日,正确处理月份边界溢出
func (dt DateTime) AddDate(years, months, days int) DateTime {
t := time.Time(dt)
y, m, d := t.Date()
h, min, s := t.Clock()
nsec := t.Nanosecond()
loc := t.Location()
// 计算新月份和新年
newMonth := int(m) + months
newYear := y + years + (newMonth-1)/12
newMonth = (newMonth-1)%12 + 1
if newMonth <= 0 {
newMonth += 12
newYear--
}
// 计算新月的最大天数
maxDay := daysInMonth(newYear, time.Month(newMonth))
newDay := d
if d > maxDay {
newDay = maxDay
}
// 创建新时间,年月日部分
newTime := time.Date(newYear, time.Month(newMonth), newDay, h, min, s, nsec, loc)
// 然后加减天数
newTime = newTime.AddDate(0, 0, days)
return DateTime(newTime)
} }
// WeekDay 1-7 周一到周日 // WeekDay 1-7 周一到周日
...@@ -255,21 +408,18 @@ func (x DateTime) WeekDay() int { ...@@ -255,21 +408,18 @@ func (x DateTime) WeekDay() int {
} }
// 1-31 1号到31号 // 1-31 1号到31号
func (x DateTime) Day() int { // func (x DateTime) Day() int {
return int(x.Time().Day()) // return int(x.Time().Day())
} // }
// 1-12 1月到12月 // 1-12 1月到12月
func (x DateTime) Month() int { func (x DateTime) Month() int {
return int(x.Time().Month()) return int(x.Time().Month())
} }
// 1970年到现在第几年 func daysInMonth(year int, month time.Month) int {
func (x DateTime) Year() int { // 下个月的第0天是本月最后一天
return int(x.Time().Year()) return time.Date(year, month+1, 0, 0, 0, 0, 0, time.UTC).Day()
}
func (x DateTime) After(t1 DateTime) bool {
return x.Time().After(t1.Time())
} }
// 转为北京时间 // 转为北京时间
...@@ -300,14 +450,6 @@ func ParseYYYYMMDDHHmmSS(value string) DateTime { ...@@ -300,14 +450,6 @@ func ParseYYYYMMDDHHmmSS(value string) DateTime {
return DateTime(t) return DateTime(t)
} }
func ParseTime(layout, value string) (DateTime, error) {
t, err := time.Parse(layout, value)
if err != nil {
return DateTime{}, err
}
return DateTime(t), nil
}
func IsSameDay(t1, t2 DateTime) bool { func IsSameDay(t1, t2 DateTime) bool {
t1Local := t1.Local() t1Local := t1.Local()
t2Local := t2.Local() t2Local := t2.Local()
...@@ -358,7 +500,7 @@ func Yesterday() DateTime { ...@@ -358,7 +500,7 @@ func Yesterday() DateTime {
return DateTime(time.Now().AddDate(0, 0, -1)) return DateTime(time.Now().AddDate(0, 0, -1))
} }
func Today() DateTime { func TodayDateTime() DateTime {
return DateTime(time.Now()) return DateTime(time.Now())
} }
...@@ -385,3 +527,34 @@ func DaysBetween(t0, t1 DateTime) int { ...@@ -385,3 +527,34 @@ func DaysBetween(t0, t1 DateTime) int {
days := int(diff.Hours() / 24) days := int(diff.Hours() / 24)
return days return days
} }
// 时间组件获取方法
func (dt DateTime) Hour() int {
return time.Time(dt).Hour()
}
func (dt DateTime) Minute() int {
return time.Time(dt).Minute()
}
func (dt DateTime) Second() int {
return time.Time(dt).Second()
}
func (dt DateTime) Year() int {
return time.Time(dt).Year()
}
// func (dt DateTime) Month() time.Month {
// return time.Time(dt).Month()
// }
func (dt DateTime) Day() int {
return time.Time(dt).Day()
}
// 月份常量(Go语言time包内置,直接使用)
// time.January (1月), time.February (2月), time.March (3月)
// time.April (4月), time.May (5月), time.June (6月)
// time.July (7月), time.August (8月), time.September (9月)
// time.October (10月), time.November (11月), time.December (12月)
package xtime
import (
"encoding/json"
"testing"
"time"
"gopkg.in/yaml.v3"
)
// TestDateTime_Scan 测试数据库扫描功能
func TestDateTime_Scan(t *testing.T) {
tests := []struct {
name string
input interface{}
want string
wantErr bool
}{
{
name: "有效time.Time类型",
input: time.Date(2023, 10, 17, 14, 30, 0, 0, time.UTC),
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "有效字节切片",
input: []byte("2023-10-17 14:30:00"),
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "有效字符串",
input: "2023-10-17 14:30:00",
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "空值处理",
input: nil,
want: "",
wantErr: false,
},
{
name: "空字符串",
input: "",
want: "",
wantErr: false,
},
{
name: "空字节切片",
input: []byte{},
want: "",
wantErr: false,
},
{
name: "无效类型",
input: 123,
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var dt DateTime
err := dt.Scan(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && dt.String() != tt.want {
t.Errorf("Scan() = %v, want %v", dt.String(), tt.want)
}
})
}
}
// TestDateTime_Value 测试数据库值生成
func TestDateTime_Value(t *testing.T) {
tests := []struct {
name string
input DateTime
want interface{}
wantErr bool
}{
{
name: "有效日期时间",
input: NewDateTime(time.Date(2023, 10, 17, 14, 30, 0, 0, time.UTC)),
want: time.Date(2023, 10, 17, 14, 30, 0, 0, time.UTC),
wantErr: false,
},
{
name: "零值日期时间",
input: DateTime{},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.input.Value()
if (err != nil) != tt.wantErr {
t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
if tt.want == nil {
if got != nil {
t.Errorf("Value() = %v, want nil", got)
}
} else {
wantTime := tt.want.(time.Time)
gotTime := got.(time.Time)
if !gotTime.Equal(wantTime) {
t.Errorf("Value() = %v, want %v", got, tt.want)
}
}
}
})
}
}
// TestDateTime_JSON 测试JSON序列化和反序列化
func TestDateTime_JSON(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "有效日期时间JSON",
input: `"2023-10-17 14:30:00"`,
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "空值JSON",
input: "null",
want: "",
wantErr: false,
},
{
name: "空字符串JSON",
input: `""`,
want: "",
wantErr: false,
},
{
name: "无效日期时间格式",
input: `"2023-13-45 25:70:00"`,
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 测试反序列化
var dt DateTime
err := json.Unmarshal([]byte(tt.input), &dt)
if (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && dt.String() != tt.want {
t.Errorf("UnmarshalJSON() = %v, want %v", dt.String(), tt.want)
return
}
// 测试序列化(仅对有效用例)
if !tt.wantErr && tt.want != "" {
bytes, err := json.Marshal(dt)
if err != nil {
t.Errorf("MarshalJSON() error = %v", err)
return
}
expectedJSON := `"` + tt.want + `"`
if string(bytes) != expectedJSON {
t.Errorf("MarshalJSON() = %s, want %s", string(bytes), expectedJSON)
}
}
})
}
}
// TestDateTime_YAML 测试YAML序列化和反序列化
func TestDateTime_YAML(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "有效日期时间YAML",
input: "2023-10-17 14:30:00",
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "空字符串YAML",
input: "",
want: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 创建YAML节点进行测试
node := &yaml.Node{
Kind: yaml.ScalarNode,
Value: tt.input,
}
var dt DateTime
err := dt.UnmarshalYAML(node)
if (err != nil) != tt.wantErr {
t.Errorf("UnmarshalYAML() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && dt.String() != tt.want {
t.Errorf("UnmarshalYAML() = %v, want %v", dt.String(), tt.want)
}
// 测试序列化
if !tt.wantErr && tt.want != "" {
result, err := dt.MarshalYAML()
if err != nil {
t.Errorf("MarshalYAML() error = %v", err)
return
}
if result != tt.want {
t.Errorf("MarshalYAML() = %v, want %v", result, tt.want)
}
}
})
}
}
// TestDateTime_String 测试字符串表示
func TestDateTime_String(t *testing.T) {
tests := []struct {
name string
input DateTime
want string
}{
{
name: "有效日期时间",
input: NewDateTime(time.Date(2023, 10, 17, 14, 30, 0, 0, time.UTC)),
want: "2023-10-17 14:30:00",
},
{
name: "零值日期时间",
input: DateTime{},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.input.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}
// TestParseDateTime 测试日期时间解析函数
func TestParseDateTime(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "标准格式解析",
input: "2023-10-17 14:30:00",
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "斜杠格式日期",
input: "2023/10/17 14:30:00",
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "RFC3339格式",
input: "2023-10-17T14:30:00Z",
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "紧凑格式",
input: "20231017143000",
want: "2023-10-17 14:30:00",
wantErr: false,
},
{
name: "无效日期时间格式",
input: "invalid-datetime",
want: "",
wantErr: true,
},
{
name: "空字符串",
input: "",
want: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseDateTime(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseDateTime() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got.String() != tt.want {
t.Errorf("ParseDateTime() = %v, want %v", got.String(), tt.want)
}
})
}
}
// TestDateTime_Comparison 测试日期时间比较方法
func TestDateTime_Comparison(t *testing.T) {
dt1 := MustParseDateTime("2023-10-17 14:30:00")
dt2 := MustParseDateTime("2023-10-17 15:45:00")
dt3 := MustParseDateTime("2023-10-17 14:30:00") // 与dt1相同
t.Run("After方法", func(t *testing.T) {
if !dt2.After(dt1) {
t.Error("dt2应该在dt1之后")
}
if dt1.After(dt2) {
t.Error("dt1不应该在dt2之后")
}
})
t.Run("Before方法", func(t *testing.T) {
if !dt1.Before(dt2) {
t.Error("dt1应该在dt2之前")
}
if dt2.Before(dt1) {
t.Error("dt2不应该在dt1之前")
}
})
t.Run("Equal方法", func(t *testing.T) {
if !dt1.Equal(dt3) {
t.Error("dt1应该等于dt3")
}
if dt1.Equal(dt2) {
t.Error("dt1不应该等于dt2")
}
})
}
// TestDateTime_Arithmetic 测试日期时间算术运算
func TestDateTime_Arithmetic(t *testing.T) {
baseDateTime := MustParseDateTime("2023-10-17 14:30:00")
t.Run("Add方法", func(t *testing.T) {
result := baseDateTime.Add(2 * time.Hour)
expected := MustParseDateTime("2023-10-17 16:30:00")
if !result.Equal(expected) {
t.Errorf("Add(2小时) = %v, want %v", result, expected)
}
result = baseDateTime.Add(-30 * time.Minute)
expected = MustParseDateTime("2023-10-17 14:00:00")
if !result.Equal(expected) {
t.Errorf("Add(-30分钟) = %v, want %v", result, expected)
}
})
t.Run("AddDate方法", func(t *testing.T) {
result := baseDateTime.AddDate(0, 1, 0) // 加1个月
expected := MustParseDateTime("2023-11-17 14:30:00")
if !result.Equal(expected) {
t.Errorf("AddDate(0,1,0) = %v, want %v", result, expected)
}
result = baseDateTime.AddDate(1, 0, 0) // 加1年
expected = MustParseDateTime("2024-10-17 14:30:00")
if !result.Equal(expected) {
t.Errorf("AddDate(1,0,0) = %v, want %v", result, expected)
}
})
}
// TestDateTime_Components 测试日期时间组件获取
func TestDateTime_Components(t *testing.T) {
dt := MustParseDateTime("2023-10-17 14:30:45")
t.Run("Year方法", func(t *testing.T) {
if year := dt.Year(); year != 2023 {
t.Errorf("Year() = %v, want 2023", year)
}
})
t.Run("Month方法", func(t *testing.T) {
if month := dt.Month(); month != 10 {
t.Errorf("Month() = %v, want October", month)
}
})
t.Run("Day方法", func(t *testing.T) {
if day := dt.Day(); day != 17 {
t.Errorf("Day() = %v, want 17", day)
}
})
t.Run("Hour方法", func(t *testing.T) {
if hour := dt.Hour(); hour != 14 {
t.Errorf("Hour() = %v, want 14", hour)
}
})
t.Run("Minute方法", func(t *testing.T) {
if minute := dt.Minute(); minute != 30 {
t.Errorf("Minute() = %v, want 30", minute)
}
})
t.Run("Second方法", func(t *testing.T) {
if second := dt.Second(); second != 45 {
t.Errorf("Second() = %v, want 45", second)
}
})
}
// TestDateTime_ZeroValue 测试零值处理
func TestDateTime_ZeroValue(t *testing.T) {
var zeroDateTime DateTime
t.Run("IsZero方法", func(t *testing.T) {
if !zeroDateTime.IsZero() {
t.Error("IsZero()应该返回true对于零值")
}
validDateTime := MustParseDateTime("2023-10-17 14:30:00")
if validDateTime.IsZero() {
t.Error("IsZero()应该返回false对于有效日期时间")
}
})
t.Run("IsNotZero方法", func(t *testing.T) {
if zeroDateTime.IsNotZero() {
t.Error("IsNotZero()应该返回false对于零值")
}
validDateTime := MustParseDateTime("2023-10-17 14:30:00")
if !validDateTime.IsNotZero() {
t.Error("IsNotZero()应该返回true对于有效日期时间")
}
})
t.Run("零值的字符串表示", func(t *testing.T) {
if zeroDateTime.String() != "" {
t.Errorf("零值的String()应该返回空字符串, 得到: %s", zeroDateTime.String())
}
})
t.Run("零值的JSON序列化", func(t *testing.T) {
bytes, err := json.Marshal(zeroDateTime)
if err != nil {
t.Errorf("零值JSON序列化错误: %v", err)
}
if string(bytes) != "null" {
t.Errorf("零值JSON应该序列化为null, 得到: %s", string(bytes))
}
})
}
// TestNow 测试Now函数
func TestNow(t *testing.T) {
now := Now()
current := time.Now()
// 检查返回的日期时间组件是否匹配当前时间(允许微小误差)
if now.Year() != current.Year() || now.Month() != int(current.Month()) || now.Day() != current.Day() {
t.Error("Now()返回的日期应该与当前日期匹配")
}
}
// TestMustParseDateTime 测试MustParseDateTime函数
func TestMustParseDateTime(t *testing.T) {
t.Run("有效日期时间", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("有效日期时间不应该引起panic")
}
}()
dt := MustParseDateTime("2023-10-17 14:30:00")
if dt.String() != "2023-10-17 14:30:00" {
t.Errorf("MustParseDateTime() = %v, want 2023-10-17 14:30:00", dt.String())
}
})
t.Run("无效日期时间应该panic", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("无效日期时间应该引起panic")
}
}()
_ = MustParseDateTime("invalid-datetime")
})
}
// TestDateTime_Format 测试自定义格式格式化
func TestDateTime_Format(t *testing.T) {
dt := MustParseDateTime("2023-10-17 14:30:00")
t.Run("自定义格式", func(t *testing.T) {
formatted := dt.Format("2006/01/02 15:04")
expected := "2023/10/17 14:30"
if formatted != expected {
t.Errorf("Format() = %v, want %v", formatted, expected)
}
})
t.Run("日期格式", func(t *testing.T) {
formatted := dt.Format("2006-01-02")
expected := "2023-10-17"
if formatted != expected {
t.Errorf("Format() = %v, want %v", formatted, expected)
}
})
}
// TestDateTime_Time 测试Time方法
func TestDateTime_Time(t *testing.T) {
dt := MustParseDateTime("2023-10-17 14:30:00")
timeVal := dt.Time()
if timeVal.Year() != 2023 || timeVal.Month() != 10 || timeVal.Day() != 17 ||
timeVal.Hour() != 14 || timeVal.Minute() != 30 || timeVal.Second() != 0 {
t.Error("Time()返回的time.Time值与原始值不匹配")
}
}
// TestDateTime_Unix 测试时间戳方法
func TestDateTime_Unix(t *testing.T) {
dt := MustParseDateTime("2023-10-17 14:30:00")
unixTime := dt.Unix()
// 验证时间戳是否正确(使用已知值进行验证)
expected := time.Date(2023, 10, 17, 14, 30, 0, 0, time.UTC).Unix()
if unixTime != expected {
t.Errorf("Unix() = %v, want %v", unixTime, expected)
}
}
// TestDateTime_EdgeCases 测试边界情况
func TestDateTime_EdgeCases(t *testing.T) {
t.Run("闰年测试", func(t *testing.T) {
leapDateTime := MustParseDateTime("2020-02-29 14:30:00")
if leapDateTime.String() != "2020-02-29 14:30:00" {
t.Error("闰年日期时间处理错误")
}
})
t.Run("时区处理", func(t *testing.T) {
utcTime := time.Date(2023, 10, 17, 22, 0, 0, 0, time.UTC)
cstTime := time.Date(2023, 10, 18, 6, 0, 0, 0, time.FixedZone("CST", 8*3600))
utcDateTime := DateTime(utcTime)
cstDateTime := DateTime(cstTime)
// 不同时区的相同时间点应该相等
if !utcDateTime.Equal(cstDateTime) {
t.Error("不同时区的相同时间点应该相等")
}
})
t.Run("月份边界", func(t *testing.T) {
lastDay := MustParseDateTime("2023-01-31 23:59:59")
nextMonth := lastDay.AddDate(0, 1, 0) // 加1个月
if nextMonth.String() != "2023-02-28 23:59:59" {
t.Errorf("月份边界处理错误: %v", nextMonth)
}
})
}
// TestDateTime_Integration 集成测试
func TestDateTime_Integration(t *testing.T) {
t.Run("完整序列化循环", func(t *testing.T) {
original := MustParseDateTime("2023-10-17 14:30:00")
// JSON序列化 -> 反序列化
jsonData, err := json.Marshal(original)
if err != nil {
t.Fatalf("JSON序列化失败: %v", err)
}
var fromJSON DateTime
err = json.Unmarshal(jsonData, &fromJSON)
if err != nil {
t.Fatalf("JSON反序列化失败: %v", err)
}
if !original.Equal(fromJSON) {
t.Error("JSON序列化循环失败")
}
// 数据库值循环
dbValue, err := original.Value()
if err != nil {
t.Fatalf("Value()失败: %v", err)
}
var fromDB DateTime
err = fromDB.Scan(dbValue)
if err != nil {
t.Fatalf("Scan()失败: %v", err)
}
if !original.Equal(fromDB) {
t.Error("数据库循环失败")
}
})
}
// BenchmarkDateTime_Parse 性能测试:日期时间解析
func BenchmarkDateTime_Parse(b *testing.B) {
testCases := []string{
"2023-10-17 14:30:00",
"2023/10/17 14:30:00",
"2023-10-17T14:30:00Z",
"",
}
for _, tc := range testCases {
b.Run(tc, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = ParseDateTime(tc)
}
})
}
}
// BenchmarkDateTime_JSONMarshal 性能测试:JSON序列化
func BenchmarkDateTime_JSONMarshal(b *testing.B) {
dt := MustParseDateTime("2023-10-17 14:30:00")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = json.Marshal(dt)
}
}
// BenchmarkDateTime_JSONUnmarshal 性能测试:JSON反序列化
func BenchmarkDateTime_JSONUnmarshal(b *testing.B) {
testCases := []struct {
name string
data []byte
}{
{"有效日期时间", []byte(`"2023-10-17 14:30:00"`)},
{"空值", []byte(`null`)},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
var dt DateTime
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = json.Unmarshal(tc.data, &dt)
}
})
}
}
...@@ -2,75 +2,264 @@ package xtime ...@@ -2,75 +2,264 @@ package xtime
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"gopkg.in/yaml.v3"
) )
type Time time.Time type Time time.Time
const timeFormat = "15:04:05"
// Scan 改进:增强空值处理和错误信息
func (t *Time) Scan(value interface{}) error { func (t *Time) Scan(value interface{}) error {
if value == nil {
*t = Time(time.Time{}) // 明确处理数据库NULL值
return nil
}
switch v := value.(type) { switch v := value.(type) {
case []byte: case []byte:
parsed, err := time.Parse("15:04:05", string(v)) if len(v) == 0 {
if err != nil { *t = Time(time.Time{})
return err return nil
} }
*t = Time(parsed) return t.parseString(string(v))
case string: case string:
parsed, err := time.Parse("15:04:05", v) if v == "" {
if err != nil { *t = Time(time.Time{})
return err return nil
} }
*t = Time(parsed) return t.parseString(v)
case time.Time: case time.Time:
if v, ok := value.(time.Time); ok { // 提取时间部分,忽略日期
*t = Time(v) hour, min, sec := v.Clock()
} *t = Time(time.Date(0, 1, 1, hour, min, sec, 0, v.Location()))
return nil
default: default:
return fmt.Errorf("unsupported type: %T", v) return fmt.Errorf("Time.Scan: 不支持的扫描类型 %T, 期望: string, []byte 或 time.Time", value)
} }
return nil
} }
// Value 改进:更安全的零值处理
func (t Time) Value() (driver.Value, error) { func (t Time) Value() (driver.Value, error) {
return t.Time().Format("15:04:05"), nil if t.IsZero() {
return nil, nil // 零值对应数据库NULL
}
return t.Time().Format(timeFormat), nil
} }
// MarshalJSON 自定义JSON序列化 // MarshalJSON 改进:使用标准JSON编码和更好的零值处理
func (t Time) MarshalJSON() ([]byte, error) { func (t Time) MarshalJSON() ([]byte, error) {
return []byte(`"` + t.Time().Format("15:04:05") + `"`), nil if t.IsZero() {
return []byte("null"), nil
}
// 使用标准JSON编码器避免手动拼接问题
return json.Marshal(t.Time().Format(timeFormat))
} }
// UnmarshalJSON 自定义JSON反序列化 // UnmarshalJSON 改进:增强格式兼容性
func (t *Time) UnmarshalJSON(data []byte) error { func (t *Time) UnmarshalJSON(data []byte) error {
str := strings.Trim(string(data), `"`) str := strings.TrimSpace(string(data))
parsed, err := time.Parse("15:04:05", str)
if err != nil { // 处理null和空值
return err if str == "null" || str == `""` || str == "" {
} *t = Time(time.Time{})
*t = Time(parsed)
return nil return nil
}
// 安全去除JSON引号
var timeStr string
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
timeStr = str[1 : len(str)-1]
} else {
timeStr = str
}
return t.parseString(timeStr)
} }
// MarshalYAML 自定义YAML序列化 // MarshalYAML 改进:符合yaml.v3接口
func (t Time) MarshalYAML() (interface{}, error) { func (t Time) MarshalYAML() (interface{}, error) {
return t.Time().Format("15:04:05"), nil if t.IsZero() {
return nil, nil
}
return t.Time().Format(timeFormat), nil
}
// UnmarshalYAML 改进:使用正确的yaml.v3接口
func (t *Time) UnmarshalYAML(value *yaml.Node) error {
if value == nil || value.Kind != yaml.ScalarNode {
return fmt.Errorf("Time.UnmarshalYAML: 时间必须为标量值(字符串)")
}
str := strings.TrimSpace(value.Value)
if str == "" || str == "null" {
*t = Time(time.Time{})
return nil
}
return t.parseString(str)
} }
// Time 返回time.Time类型(仅包含时间部分)
func (t Time) Time() time.Time { func (t Time) Time() time.Time {
return time.Time(t) tm := time.Time(t)
if tm.IsZero() {
return time.Time{}
}
// 确保只返回时间部分,日期设为基准值
hour, min, sec := tm.Clock()
return time.Date(0, 1, 1, hour, min, sec, 0, tm.Location())
} }
// UnmarshalYAML 自定义YAML反序列化 // String 返回时间字符串表示
func (t *Time) UnmarshalYAML(value interface{}) error { func (t Time) String() string {
str, ok := value.(string) if t.IsZero() {
if !ok { return ""
return fmt.Errorf("time-only must be a string")
} }
parsed, err := time.Parse("15:04:05", str) return t.Time().Format(timeFormat)
if err != nil { }
return err
// IsZero 检查是否为零值
func (t Time) IsZero() bool {
return time.Time(t).IsZero()
}
// 内部解析方法 - 统一时间解析逻辑
// func (t *Time) parseString(s string) error {
// s = strings.TrimSpace(s)
// if s == "" {
// *t = Time(time.Time{})
// return nil
// }
// // 支持多种时间格式
// formats := []string{
// "15:04:05", // 标准格式
// "15:04", // 省略秒
// "15:04:05.000", // 带毫秒
// }
// var firstErr error
// for _, format := range formats {
// parsed, err := time.Parse(format, s)
// if err == nil {
// // 成功解析,提取时间部分
// hour, min, sec := parsed.Clock()
// *t = Time(time.Date(0, 1, 1, hour, min, sec, 0, parsed.Location()))
// return nil
// }
// if firstErr == nil {
// firstErr = err
// }
// }
// return fmt.Errorf("Time.parseString: 无法解析时间 %q, 支持的格式示例: 15:04:05", s)
// }
// parseString 内部解析方法 - 统一时间解析逻辑
func (t *Time) parseString(s string) error {
s = strings.TrimSpace(s)
if s == "" {
*t = Time(time.Time{})
return nil
} }
*t = Time(parsed)
// 支持多种时间格式(按优先级排序)
formats := []string{
"15:04:05", // 标准格式
"15:04", // 省略秒
"15:04:05.000", // 带毫秒
"15:04:05.000000", // 带微秒
"15:04:05.000000000", // 带纳秒
"150405", // 紧凑格式(6位数字:小时分钟秒)
"15:04:05-0700", // 带时区偏移
"15:04:05Z07:00", // 带时区(RFC3339格式)
}
var firstErr error
for _, format := range formats {
parsed, err := time.Parse(format, s)
if err == nil {
// 成功解析,提取时间部分
hour, min, sec := parsed.Clock()
*t = Time(time.Date(0, 1, 1, hour, min, sec, 0, parsed.Location()))
return nil return nil
}
if firstErr == nil {
firstErr = err
}
}
return fmt.Errorf("Time.parseString: 无法解析时间 %q, 支持的格式示例: 15:04:05, 15:04, 143000", s)
}
// 新增实用方法
// Hour 返回小时
func (t Time) Hour() int {
return t.Time().Hour()
}
// Minute 返回分钟
func (t Time) Minute() int {
return t.Time().Minute()
}
// Second 返回秒
func (t Time) Second() int {
return t.Time().Second()
}
// Before 检查当前时间是否在另一个时间之前
func (t Time) Before(other Time) bool {
return t.Time().Before(other.Time())
}
// After 检查当前时间是否在另一个时间之后
func (t Time) After(other Time) bool {
return t.Time().After(other.Time())
}
// Equal 检查两个时间是否相等
func (t Time) Equal(other Time) bool {
return t.Time().Equal(other.Time())
}
// AddHours 添加指定小时
func (t Time) AddHours(hours int) Time {
newTime := t.Time().Add(time.Duration(hours) * time.Hour)
return Time(newTime)
}
// AddMinutes 添加指定分钟
func (t Time) AddMinutes(minutes int) Time {
newTime := t.Time().Add(time.Duration(minutes) * time.Minute)
return Time(newTime)
}
// ParseTime 从字符串解析时间
func ParseTime(s string) (Time, error) {
var t Time
err := t.parseString(s)
return t, err
}
// MustParseTime 从字符串解析时间,解析失败时panic
func MustParseTime(s string) Time {
t, err := ParseTime(s)
if err != nil {
panic(fmt.Sprintf("MustParseTime 解析失败: %q, 错误: %v", s, err))
}
return t
}
// NowTime 返回当前时间(忽略日期部分)
func NowTime() Time {
now := time.Now()
return Time(time.Date(0, 1, 1, now.Hour(), now.Minute(), now.Second(), 0, now.Location()))
} }
package xtime
import (
"encoding/json"
"testing"
"time"
"gopkg.in/yaml.v3"
)
// TestTime_Scan 测试数据库扫描功能
func TestTime_Scan(t *testing.T) {
tests := []struct {
name string
input interface{}
want string
wantErr bool
}{
{
name: "有效字节切片",
input: []byte("14:30:00"),
want: "14:30:00",
wantErr: false,
},
{
name: "有效字符串",
input: "14:30:00",
want: "14:30:00",
wantErr: false,
},
{
name: "有效time.Time类型",
input: time.Date(0, 1, 1, 14, 30, 0, 0, time.UTC),
want: "14:30:00",
wantErr: false,
},
{
name: "空值处理",
input: nil,
want: "",
wantErr: false,
},
{
name: "空字符串",
input: "",
want: "",
wantErr: false,
},
{
name: "空字节切片",
input: []byte{},
want: "",
wantErr: false,
},
{
name: "无效类型",
input: 123,
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tm Time
err := tm.Scan(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tm.String() != tt.want {
t.Errorf("Scan() = %v, want %v", tm.String(), tt.want)
}
})
}
}
// TestTime_Value 测试数据库值生成
func TestTime_Value(t *testing.T) {
tests := []struct {
name string
input Time
want interface{}
wantErr bool
}{
{
name: "有效时间",
input: MustParseTime("14:30:00"),
want: "14:30:00",
wantErr: false,
},
{
name: "零值时间",
input: Time{},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.input.Value()
if (err != nil) != tt.wantErr {
t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got != tt.want {
t.Errorf("Value() = %v, want %v", got, tt.want)
}
})
}
}
// TestTime_JSON 测试JSON序列化和反序列化
func TestTime_JSON(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "有效时间JSON",
input: `"14:30:00"`,
want: "14:30:00",
wantErr: false,
},
{
name: "空值JSON",
input: "null",
want: "",
wantErr: false,
},
{
name: "空字符串JSON",
input: `""`,
want: "",
wantErr: false,
},
{
name: "无效时间格式",
input: `"25:70:00"`, // 无效的时间
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 测试反序列化
var tm Time
err := json.Unmarshal([]byte(tt.input), &tm)
if (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tm.String() != tt.want {
t.Errorf("UnmarshalJSON() = %v, want %v", tm.String(), tt.want)
return
}
// 测试序列化(仅对有效用例)
if !tt.wantErr && tt.want != "" {
bytes, err := json.Marshal(tm)
if err != nil {
t.Errorf("MarshalJSON() error = %v", err)
return
}
expectedJSON := `"` + tt.want + `"`
if string(bytes) != expectedJSON {
t.Errorf("MarshalJSON() = %s, want %s", string(bytes), expectedJSON)
}
}
})
}
}
// TestTime_YAML 测试YAML序列化和反序列化
func TestTime_YAML(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "有效时间YAML",
input: "14:30:00",
want: "14:30:00",
wantErr: false,
},
{
name: "空字符串YAML",
input: "",
want: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 创建YAML节点进行测试
node := &yaml.Node{
Kind: yaml.ScalarNode,
Value: tt.input,
}
var tm Time
err := tm.UnmarshalYAML(node)
if (err != nil) != tt.wantErr {
t.Errorf("UnmarshalYAML() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tm.String() != tt.want {
t.Errorf("UnmarshalYAML() = %v, want %v", tm.String(), tt.want)
}
// 测试序列化
if !tt.wantErr && tt.want != "" {
result, err := tm.MarshalYAML()
if err != nil {
t.Errorf("MarshalYAML() error = %v", err)
return
}
if result != tt.want {
t.Errorf("MarshalYAML() = %v, want %v", result, tt.want)
}
}
})
}
}
// TestTime_String 测试字符串表示
func TestTime_String(t *testing.T) {
tests := []struct {
name string
input Time
want string
}{
{
name: "有效时间",
input: MustParseTime("14:30:00"),
want: "14:30:00",
},
{
name: "零值时间",
input: Time{},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.input.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}
// TestParseTime 测试时间解析函数
func TestParseTime(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "标准格式解析",
input: "14:30:00",
want: "14:30:00",
wantErr: false,
},
{
name: "省略秒格式",
input: "14:30",
want: "14:30:00",
wantErr: false,
},
{
name: "紧凑格式",
input: "143000",
want: "14:30:00",
wantErr: false,
},
{
name: "无效时间格式",
input: "25:70:00", // 无效的小时和分钟
want: "",
wantErr: true,
},
{
name: "空字符串",
input: "",
want: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseTime(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseTime() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got.String() != tt.want {
t.Errorf("ParseTime() = %v, want %v", got.String(), tt.want)
}
})
}
}
// TestTime_Comparison 测试时间比较方法
func TestTime_Comparison(t *testing.T) {
time1 := MustParseTime("14:30:00")
time2 := MustParseTime("15:45:00")
time3 := MustParseTime("14:30:00") // 与time1相同
t.Run("After方法", func(t *testing.T) {
if !time2.After(time1) {
t.Error("time2应该在time1之后")
}
if time1.After(time2) {
t.Error("time1不应该在time2之后")
}
})
t.Run("Before方法", func(t *testing.T) {
if !time1.Before(time2) {
t.Error("time1应该在time2之前")
}
if time2.Before(time1) {
t.Error("time2不应该在time1之前")
}
})
t.Run("Equal方法", func(t *testing.T) {
if !time1.Equal(time3) {
t.Error("time1应该等于time3")
}
if time1.Equal(time2) {
t.Error("time1不应该等于time2")
}
})
}
// TestTime_Arithmetic 测试时间算术运算
func TestTime_Arithmetic(t *testing.T) {
baseTime := MustParseTime("14:30:00")
t.Run("AddHours方法", func(t *testing.T) {
result := baseTime.AddHours(2)
expected := MustParseTime("16:30:00")
if !result.Equal(expected) {
t.Errorf("AddHours(2) = %v, want %v", result, expected)
}
result = baseTime.AddHours(-1)
expected = MustParseTime("13:30:00")
if !result.Equal(expected) {
t.Errorf("AddHours(-1) = %v, want %v", result, expected)
}
// 测试跨日边界
lateTime := MustParseTime("23:30:00")
result = lateTime.AddHours(2)
expected = MustParseTime("01:30:00") // 第二天凌晨
if !result.Equal(expected) {
t.Errorf("AddHours跨日边界 = %v, want %v", result, expected)
}
})
t.Run("AddMinutes方法", func(t *testing.T) {
result := baseTime.AddMinutes(30)
expected := MustParseTime("15:00:00")
if !result.Equal(expected) {
t.Errorf("AddMinutes(30) = %v, want %v", result, expected)
}
result = baseTime.AddMinutes(-15)
expected = MustParseTime("14:15:00")
if !result.Equal(expected) {
t.Errorf("AddMinutes(-15) = %v, want %v", result, expected)
}
// 测试跨小时边界
result = baseTime.AddMinutes(90) // 1小时30分钟
expected = MustParseTime("16:00:00")
if !result.Equal(expected) {
t.Errorf("AddMinutes跨小时边界 = %v, want %v", result, expected)
}
})
}
// TestTime_Components 测试时间组件获取
func TestTime_Components(t *testing.T) {
tm := MustParseTime("14:30:45")
t.Run("Hour方法", func(t *testing.T) {
if hour := tm.Hour(); hour != 14 {
t.Errorf("Hour() = %v, want 14", hour)
}
})
t.Run("Minute方法", func(t *testing.T) {
if minute := tm.Minute(); minute != 30 {
t.Errorf("Minute() = %v, want 30", minute)
}
})
t.Run("Second方法", func(t *testing.T) {
if second := tm.Second(); second != 45 {
t.Errorf("Second() = %v, want 45", second)
}
})
}
// TestTime_ZeroValue 测试零值处理
func TestTime_ZeroValue(t *testing.T) {
var zeroTime Time
t.Run("IsZero方法", func(t *testing.T) {
if !zeroTime.IsZero() {
t.Error("IsZero()应该返回true对于零值")
}
validTime := MustParseTime("14:30:00")
if validTime.IsZero() {
t.Error("IsZero()应该返回false对于有效时间")
}
})
t.Run("零值的字符串表示", func(t *testing.T) {
if zeroTime.String() != "" {
t.Errorf("零值的String()应该返回空字符串, 得到: %s", zeroTime.String())
}
})
t.Run("零值的JSON序列化", func(t *testing.T) {
bytes, err := json.Marshal(zeroTime)
if err != nil {
t.Errorf("零值JSON序列化错误: %v", err)
}
if string(bytes) != "null" {
t.Errorf("零值JSON应该序列化为null, 得到: %s", string(bytes))
}
})
}
// TestNowTime 测试NowTime函数
func TestNowTime(t *testing.T) {
now := NowTime()
current := time.Now()
// 检查返回的时间组件是否匹配当前时间
if now.Hour() != current.Hour() || now.Minute() != current.Minute() || now.Second() != current.Second() {
t.Error("NowTime()返回的时间应该与当前时间匹配")
}
}
// TestMustParseTime 测试MustParseTime函数
func TestMustParseTime(t *testing.T) {
t.Run("有效时间", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("有效时间不应该引起panic")
}
}()
tm := MustParseTime("14:30:00")
if tm.String() != "14:30:00" {
t.Errorf("MustParseTime() = %v, want 14:30:00", tm.String())
}
})
t.Run("无效时间应该panic", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("无效时间应该引起panic")
}
}()
_ = MustParseTime("25:70:00")
})
}
// TestTime_EdgeCases 测试边界情况
func TestTime_EdgeCases(t *testing.T) {
t.Run("午夜时间", func(t *testing.T) {
midnight := MustParseTime("00:00:00")
if midnight.String() != "00:00:00" {
t.Error("午夜时间处理错误")
}
})
t.Run("最大有效时间", func(t *testing.T) {
maxTime := MustParseTime("23:59:59")
if maxTime.String() != "23:59:59" {
t.Error("最大时间处理错误")
}
})
t.Run("时间溢出处理", func(t *testing.T) {
// 测试接近午夜的时间加法
almostMidnight := MustParseTime("23:59:30")
result := almostMidnight.AddMinutes(2) // 应该变成00:01:30
expected := MustParseTime("00:01:30")
if !result.Equal(expected) {
t.Errorf("时间溢出处理错误: %v, want %v", result, expected)
}
})
}
// TestTime_Integration 集成测试
func TestTime_Integration(t *testing.T) {
t.Run("完整序列化循环", func(t *testing.T) {
original := MustParseTime("14:30:00")
// JSON序列化 -> 反序列化
jsonData, err := json.Marshal(original)
if err != nil {
t.Fatalf("JSON序列化失败: %v", err)
}
var fromJSON Time
err = json.Unmarshal(jsonData, &fromJSON)
if err != nil {
t.Fatalf("JSON反序列化失败: %v", err)
}
if !original.Equal(fromJSON) {
t.Error("JSON序列化循环失败")
}
// 数据库值循环
dbValue, err := original.Value()
if err != nil {
t.Fatalf("Value()失败: %v", err)
}
var fromDB Time
err = fromDB.Scan(dbValue)
if err != nil {
t.Fatalf("Scan()失败: %v", err)
}
if !original.Equal(fromDB) {
t.Error("数据库循环失败")
}
})
}
// BenchmarkTime_Parse 性能测试:时间解析
func BenchmarkTime_Parse(b *testing.B) {
testCases := []string{
"14:30:00",
"14:30",
"143000",
"",
}
for _, tc := range testCases {
b.Run(tc, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = ParseTime(tc)
}
})
}
}
// BenchmarkTime_JSONMarshal 性能测试:JSON序列化
func BenchmarkTime_JSONMarshal(b *testing.B) {
tm := MustParseTime("14:30:00")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = json.Marshal(tm)
}
}
// BenchmarkTime_JSONUnmarshal 性能测试:JSON反序列化
func BenchmarkTime_JSONUnmarshal(b *testing.B) {
testCases := []struct {
name string
data []byte
}{
{"有效时间", []byte(`"14:30:00"`)},
{"空值", []byte(`null`)},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
var tm Time
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = json.Unmarshal(tc.data, &tm)
}
})
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论