From f419f4d092c55d9cfefc2c89a8c8d67318a308da Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Mon, 4 May 2026 14:57:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20Time=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=EF=BC=8C=E6=A0=B8=E5=BF=83=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E8=87=B3=20cast=EF=BC=8C=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E4=B8=8E=20DSL=20=E8=AE=A1=E7=AE=97=E8=83=BD?= =?UTF-8?q?=E5=8A=9B=20(by=20AI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 + time.go | 302 ++++----------------------------------------------- 2 files changed, 27 insertions(+), 280 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9380421..0db1a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## [v1.0.5] - 2026-05-04 +- **重构**: 核心时间解析与操作逻辑迁移至 `apigo.cc/go/cast` 模块。 +- **优化**: 现已支持更强大的多格式解析(包括紧凑格式、中文日期、多级时间戳)及高性能 DSL 计算。 +- **增强**: `TimeZone` 增加 `Now()` 方法。 + ## [v1.0.4] - 2026-05-01 - **新增**: 引入 `TimeZone` 上下文,支持 explicit 时区控制,兼容原有包级 API。 - **新增**: 引入 `Timer` 计时工具,支持 `Start`, `Record`, `Pause`, `Resume` 及统计描述。 diff --git a/time.go b/time.go index 6971520..52c91ac 100644 --- a/time.go +++ b/time.go @@ -2,8 +2,6 @@ package time import ( "fmt" - "regexp" - "strconv" "strings" "time" @@ -12,294 +10,51 @@ import ( // TimeZone 定义了特定时区上下文下的时间操作 type TimeZone struct { - loc *time.Location + *cast.TimeZone } +// New 创建一个时区上下文 func New(loc *time.Location) *TimeZone { - if loc == nil { - loc = time.Local - } - return &TimeZone{loc: loc} + return &TimeZone{TimeZone: cast.NewTimeZone(loc)} } -var defaultTZ = New(time.Local) - // Parse 将任意类型转换为 time.Time。 -// 支持:time.Time, 时间戳 (秒/毫秒/微秒/纳秒), RFC3339, JS 格式, 中文格式等。 // 转换失败返回当前时间 time.Now()。 func (tz *TimeZone) Parse(v any) time.Time { - if v == nil { - return time.Now().In(tz.loc) + tm := tz.ParseTime(v) + if tm.IsZero() { + return time.Now().In(tz.Location()) } - - if tm, ok := v.(time.Time); ok { - return tm.In(tz.loc) - } - - str := strings.TrimSpace(cast.String(v)) - if str == "" { - return time.Now().In(tz.loc) - } - - var tm time.Time - var err error - - // 1. 处理纯数字 (时间戳或紧凑格式) - if num := cast.Int64(v); num > 0 { - // 紧凑格式解析 - switch len(str) { - case 14: // 20060102150405 - if tm, err = time.ParseInLocation("20060102150405", str, tz.loc); err == nil { - return tm - } - case 8: // 20060102 - if tm, err = time.ParseInLocation("20060102", str, tz.loc); err == nil { - return tm - } - case 6: // 150405 或 060102 - if tm, err = time.ParseInLocation("150405", str, tz.loc); err == nil { - return tm - } - if tm, err = time.ParseInLocation("060102", str, tz.loc); err == nil { - return tm - } - } - - // 时间戳处理 - switch { - case num < 1e10: // 秒 - return time.Unix(num, 0).In(tz.loc) - case num < 1e13: // 毫秒 - return time.UnixMilli(num).In(tz.loc) - case num < 1e16: // 微秒 - return time.UnixMicro(num).In(tz.loc) - default: // 纳秒 - return time.Unix(0, num).In(tz.loc) - } - } - - // 2. 标准格式与常见变体 - // RFC3339 (带 T 和 Z 或偏移量) - if strings.Contains(str, "T") { - if tm, err = time.Parse(time.RFC3339Nano, str); err == nil { - return tm.In(tz.loc) - } - if tm, err = time.Parse(time.RFC3339, str); err == nil { - return tm.In(tz.loc) - } - } - - // 处理分隔符:-, /, . - if len(str) >= 10 && (str[4] == '-' || str[4] == '/' || str[4] == '.') { - sep := str[4] - layout := fmt.Sprintf("2006%c01%c02", sep, sep) - - // 完整日期时间 - if len(str) >= 19 { - tsep := str[10] // 通常是 ' ' 或 'T' - if len(str) >= 20 && str[19] == '.' { - // 带纳秒 - if tm, err = time.ParseInLocation(layout+fmt.Sprintf("%c15:04:05.999999999", tsep), str, tz.loc); err == nil { - return tm - } - } else { - if tm, err = time.ParseInLocation(layout+fmt.Sprintf("%c15:04:05", tsep), str[:19], tz.loc); err == nil { - return tm - } - } - } - // 纯日期 - if tm, err = time.ParseInLocation(layout, str[:10], tz.loc); err == nil { - return tm - } - } - - // 06-01-02 这种短年份格式 - if len(str) >= 8 && (str[2] == '-' || str[2] == '/' || str[2] == '.') { - sep := str[2] - layout := fmt.Sprintf("06%c01%c02", sep, sep) - if len(str) >= 17 && (str[8] == ' ' || str[8] == 'T') { - if tm, err = time.ParseInLocation(layout+fmt.Sprintf("%c15:04:05", str[8]), str[:17], tz.loc); err == nil { - return tm - } - } - if tm, err = time.ParseInLocation(layout, str[:8], tz.loc); err == nil { - return tm - } - } - - // 3. 特殊格式:JS, HTTP, 中文 - if strings.Contains(str, "GMT") || strings.Contains(str, "CST") { - cleanStr := strings.SplitN(str, " (", 2)[0] - cleanStr = strings.Replace(cleanStr, "CST", "+0800", 1) - - formats := []string{ - "Mon Jan 02 2006 15:04:05 GMT-0700", - "Mon, 02 Jan 2006 15:04:05 GMT", - time.RFC1123Z, - time.RFC1123, - time.UnixDate, - } - for _, f := range formats { - if tm, err = time.Parse(f, cleanStr); err == nil { - return tm.In(tz.loc) - } - } - } - - // 中文格式解析 - if strings.ContainsAny(str, "年月日时点分秒") { - return tz.parseCN(str) - } - - // 纯时间格式 - if strings.Contains(str, ":") { - if tm, err = time.ParseInLocation("15:04:05.999999", str, tz.loc); err == nil { - return tm - } - if tm, err = time.ParseInLocation("15:04:05", str, tz.loc); err == nil { - return tm - } - if tm, err = time.ParseInLocation("15:04", str, tz.loc); err == nil { - return tm - } - } - - return time.Now().In(tz.loc) -} - -var cnDateRegex = regexp.MustCompile(`(\d{2,4})?年?(\d{1,2})月(\d{1,2})日`) -var cnTimeRegex = regexp.MustCompile(`(上午|下午)?(\d{1,2})(?:时|点|:)(\d{1,2})(?:分|:)(\d{1,2})?秒?`) - -func (tz *TimeZone) parseCN(str string) time.Time { - str = strings.ReplaceAll(str, ":", ":") - str = strings.ReplaceAll(str, " ", "") - var y, m, d, h, mm, s int - if matches := cnDateRegex.FindStringSubmatch(str); len(matches) == 4 { - y = cast.Int(matches[1]) - m = cast.Int(matches[2]) - d = cast.Int(matches[3]) - if y > 0 && y < 100 { y += 2000 } - } - if matches := cnTimeRegex.FindStringSubmatch(str); len(matches) == 5 { - h = cast.Int(matches[2]) - mm = cast.Int(matches[3]) - s = cast.Int(matches[4]) - if matches[1] == "下午" && h < 12 { h += 12 } - if matches[1] == "上午" && h >= 12 { h -= 12 } - } - if y == 0 { y = time.Now().Year() } - if m == 0 { m = int(time.Now().Month()) } - if d == 0 { d = time.Now().Day() } - return time.Date(y, time.Month(m), d, h, mm, s, 0, tz.loc) + return tm } // Parse 将任意类型转换为 time.Time。 -// 支持:time.Time, 时间戳 (秒/毫秒/微秒/纳秒), RFC3339, JS 格式, 中文格式等。 -// 转换失败返回当前时间 time.Now()。 func Parse(v any) time.Time { - return defaultTZ.Parse(v) + tm := cast.ParseTime(v) + if tm.IsZero() { + return time.Now().In(cast.DefaultTimeZone.Location()) + } + return tm } // Format 格式化时间。 -// layout 支持: YYYY-MM-DD HH:mm:ss, YYYY/MM/DD, HH:mm 等直观格式。 func (tz *TimeZone) Format(layout string, v any) string { - tm := tz.Parse(v) - l := layout - l = strings.ReplaceAll(l, "YYYY", "2006") - l = strings.ReplaceAll(l, "YY", "06") - l = strings.ReplaceAll(l, "MM", "01") - l = strings.ReplaceAll(l, "M", "1") - l = strings.ReplaceAll(l, "DD", "02") - l = strings.ReplaceAll(l, "D", "2") - l = strings.ReplaceAll(l, "HH", "15") - l = strings.ReplaceAll(l, "hh", "03") - l = strings.ReplaceAll(l, "h", "3") - l = strings.ReplaceAll(l, "mm", "04") - l = strings.ReplaceAll(l, "ss", "05") - l = strings.ReplaceAll(l, "a", "pm") - l = strings.ReplaceAll(l, "A", "PM") - l = strings.ReplaceAll(l, "ZZ", "-0700") - l = strings.ReplaceAll(l, "Z", "-07:00") - return tm.Format(l) + return tz.FormatTime(layout, v) } +// Format 格式化时间。 func Format(layout string, v any) string { - return defaultTZ.Format(layout, v) + return cast.FormatTime(layout, v) } // Add 时间加减 DSL。 -// 格式如: "+1Y-2M+3D", "+1h30m", "-1s"。 -// 单位支持: Y (年), M (月), D (天), h, m, s, ms, us, ns。 func (tz *TimeZone) Add(expr string, v any) time.Time { - tm := tz.Parse(v) - if expr == "" { - return tm - } - - i := 0 - years, months, days := 0, 0, 0 - var duration time.Duration - - for i < len(expr) { - sign := 1 - if expr[i] == '+' { - i++ - } else if expr[i] == '-' { - sign = -1 - i++ - } - - j := i - for j < len(expr) && expr[j] >= '0' && expr[j] <= '9' { - j++ - } - num := 1 - if j > i { - num, _ = strconv.Atoi(expr[i:j]) - } - val := num * sign - - i = j - unit := "" - // 匹配双字符单位 (ms, us, ns) - if i+2 <= len(expr) { - u2 := expr[i : i+2] - if u2 == "ms" || u2 == "us" || u2 == "ns" { - unit = u2 - i += 2 - } - } - // 匹配单字符单位 - if unit == "" && i < len(expr) { - unit = expr[i : i+1] - i++ - } - - switch unit { - case "Y": years += val - case "M": months += val - case "D": days += val - case "h": duration += time.Duration(val) * time.Hour - case "m": duration += time.Duration(val) * time.Minute - case "s": duration += time.Duration(val) * time.Second - case "ms": duration += time.Duration(val) * time.Millisecond - case "us": duration += time.Duration(val) * time.Microsecond - case "ns": duration += time.Duration(val) * time.Nanosecond - default: - // 默认秒 - duration += time.Duration(val) * time.Second - } - } - - if years != 0 || months != 0 || days != 0 { - tm = tm.AddDate(years, months, days) - } - return tm.Add(duration) + return tz.AddTime(expr, v) } +// Add 时间加减 DSL。 func Add(expr string, v any) time.Time { - return defaultTZ.Add(expr, v) + return cast.AddTime(expr, v) } // Timer 计时器 @@ -365,23 +120,10 @@ func (t *Timer) Describe() string { return sb.String() } -// DescribeDuration 将时长转化为自然语言描述,例如 "1h 1m 1s" -func (tz *TimeZone) DescribeDuration(d time.Duration) string { - h := d / time.Hour - d -= h * time.Hour - m := d / time.Minute - d -= m * time.Minute - s := d / time.Second - - var parts []string - if h > 0 { parts = append(parts, fmt.Sprintf("%dh", h)) } - if m > 0 { parts = append(parts, fmt.Sprintf("%dm", m)) } - if s > 0 { parts = append(parts, fmt.Sprintf("%ds", s)) } - if len(parts) == 0 { return "0s" } - return strings.Join(parts, " ") +// DescribeDuration 将时长转化为自然语言描述 +func DescribeDuration(d time.Duration) string { + return cast.DescribeDuration(d) } // Now 获取当前时间 -func (tz *TimeZone) Now() time.Time { return time.Now().In(tz.loc) } - -func Now() time.Time { return defaultTZ.Now() } +func Now() time.Time { return cast.Now() }