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

修改xtime

上级 ddf10376
......@@ -47,7 +47,7 @@ require (
golang.org/x/sync v0.16.0
google.golang.org/grpc v1.67.1
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/postgres v1.5.4
gorm.io/driver/sqlite v1.5.4
......
......@@ -2,105 +2,279 @@ package xtime
import (
"database/sql/driver"
"encoding/json"
"fmt"
"strings"
"time"
"gopkg.in/yaml.v3"
)
// Date 自定义日期类型,基于 time.Time,但只关心年月日
type Date time.Time
// 使用常量确保格式一致性,便于维护
const (
dateFormat = "2006-01-02" // 标准ISO格式
)
// Scan 改进:增强健壮性,统一解析路径,优化错误信息
func (d *Date) Scan(value interface{}) error {
if value == nil {
*d = Date(time.Time{}) // 明确处理数据库NULL值
return nil
}
switch v := value.(type) {
case []byte:
parsed, err := time.Parse("2006-01-02", string(v))
if err != nil {
return err
if len(v) == 0 {
*d = Date(time.Time{})
return nil
}
*d = Date(parsed)
return d.parseString(string(v))
case string:
parsed, err := time.Parse("2006-01-02", v)
if err != nil {
return err
if v == "" {
*d = Date(time.Time{})
return nil
}
*d = Date(parsed)
return d.parseString(v)
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:
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) {
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) {
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 {
str := strings.Trim(string(data), `"`)
parsed, err := time.Parse("2006-01-02", str)
if err != nil {
return err
str := strings.TrimSpace(string(data))
// 处理显式null和空字符串
if str == "null" || str == `""` || str == "" {
*d = Date(time.Time{})
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
}
*d = Date(parsed)
return nil
return d.parseString(dateStr)
}
// MarshalYAML 自定义YAML序列化
// MarshalYAML 改进:符合yaml.v3接口标准
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 {
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反序列化
func (d *Date) UnmarshalYAML(value interface{}) error {
str, ok := value.(string)
if !ok {
return fmt.Errorf("date must be a string")
// parseString 内部解析方法改进:支持多种格式,优化错误处理
func (d *Date) parseString(s string) error {
s = strings.TrimSpace(s)
if s == "" {
*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", // 英文缩写格式
}
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
}
if firstErr == nil {
firstErr = err
}
}
return fmt.Errorf("Date.parseString: 无法解析日期 %q, 支持的格式示例: 2006-01-02", s)
}
// Equal 改进:精确的日期比较(只比较年月日)
func (d Date) Equal(other Date) bool {
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
}
*d = Date(parsed)
return nil
return d.Time().Before(other.Time())
}
// String 返回日期字符串表示
func (d Date) String() string {
return d.Time().Format("2006-01-02")
// After 检查当前日期是否在另一个日期之后
func (d Date) After(other Date) bool {
if d.Equal(other) {
return false
}
return d.Time().After(other.Time())
}
// Today 返回当前日期
func TodayDate() Date {
// 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 返回当前日期(忽略时间部分)
func Today() Date {
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 从字符串解析日期
func ParseDate(s string) (Date, error) {
t, err := time.Parse("2006-01-02", s)
if err != nil {
return Date{}, err
}
return Date(t), nil
var d Date
err := d.parseString(s)
return d, err
}
// MustParseDate 从字符串解析日期,如果解析失败会panic
// MustParseDate 从字符串解析日期,解析失败时panic
func MustParseDate(s string) Date {
d, err := ParseDate(s)
if err != nil {
panic(err)
panic(fmt.Sprintf("MustParseDate 解析失败: %q, 错误: %v", s, err))
}
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)
}
差异被折叠。
差异被折叠。
差异被折叠。
......@@ -2,75 +2,264 @@ package xtime
import (
"database/sql/driver"
"encoding/json"
"fmt"
"strings"
"time"
"gopkg.in/yaml.v3"
)
type Time time.Time
const timeFormat = "15:04:05"
// Scan 改进:增强空值处理和错误信息
func (t *Time) Scan(value interface{}) error {
if value == nil {
*t = Time(time.Time{}) // 明确处理数据库NULL值
return nil
}
switch v := value.(type) {
case []byte:
parsed, err := time.Parse("15:04:05", string(v))
if err != nil {
return err
if len(v) == 0 {
*t = Time(time.Time{})
return nil
}
*t = Time(parsed)
return t.parseString(string(v))
case string:
parsed, err := time.Parse("15:04:05", v)
if err != nil {
return err
if v == "" {
*t = Time(time.Time{})
return nil
}
*t = Time(parsed)
return t.parseString(v)
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:
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) {
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) {
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 {
str := strings.Trim(string(data), `"`)
parsed, err := time.Parse("15:04:05", str)
if err != nil {
return err
str := strings.TrimSpace(string(data))
// 处理null和空值
if str == "null" || str == `""` || str == "" {
*t = Time(time.Time{})
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
}
*t = Time(parsed)
return nil
return t.parseString(timeStr)
}
// MarshalYAML 自定义YAML序列化
// MarshalYAML 改进:符合yaml.v3接口
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 {
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反序列化
func (t *Time) UnmarshalYAML(value interface{}) error {
str, ok := value.(string)
if !ok {
return fmt.Errorf("time-only must be a string")
// String 返回时间字符串表示
func (t Time) String() string {
if t.IsZero() {
return ""
}
return t.Time().Format(timeFormat)
}
// 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
}
parsed, err := time.Parse("15:04:05", str)
// 支持多种时间格式(按优先级排序)
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
}
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 {
return err
panic(fmt.Sprintf("MustParseTime 解析失败: %q, 错误: %v", s, err))
}
*t = Time(parsed)
return nil
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()))
}
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论