package time import ( "fmt" "regexp" "strconv" "strings" "time" "apigo.cc/go/cast" ) // TimeZone 定义了特定时区上下文下的时间操作 type TimeZone struct { loc *time.Location } func New(loc *time.Location) *TimeZone { if loc == nil { loc = time.Local } return &TimeZone{loc: 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) } 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) } // Parse 将任意类型转换为 time.Time。 // 支持:time.Time, 时间戳 (秒/毫秒/微秒/纳秒), RFC3339, JS 格式, 中文格式等。 // 转换失败返回当前时间 time.Now()。 func Parse(v any) time.Time { return defaultTZ.Parse(v) } // 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) } func Format(layout string, v any) string { return defaultTZ.Format(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) } func Add(expr string, v any) time.Time { return defaultTZ.Add(expr, v) } // Timer 计时器 type Timer struct { start time.Time last time.Time laps []Lap pause time.Duration } type Lap struct { Label string Duration time.Duration At time.Time } // Start 开始计时 func Start() *Timer { now := time.Now() return &Timer{start: now, last: now} } // Record 记录一段耗时 (Lap),并返回段耗时 func (t *Timer) Record(label string) time.Duration { now := time.Now() d := now.Sub(t.last) t.last = now t.laps = append(t.laps, Lap{Label: label, Duration: d, At: now}) return d } // Pause 暂停记录 func (t *Timer) Pause(label string) { now := time.Now() d := now.Sub(t.last) t.laps = append(t.laps, Lap{Label: label, Duration: d, At: now}) t.last = now // 暂时标记下一次开始点 } // Resume 从暂停中恢复 func (t *Timer) Resume() { t.last = time.Now() } // Stop 结束计时,返回总持续时间 func (t *Timer) Stop() time.Duration { return time.Since(t.start) } // Summarize 返回所有记录段 func (t *Timer) Summarize() []Lap { return t.laps } // Describe 返回格式化后的统计字符串 func (t *Timer) Describe() string { var sb strings.Builder total := t.Stop() for _, lap := range t.laps { sb.WriteString(fmt.Sprintf("[%s] %v; ", lap.Label, lap.Duration)) } sb.WriteString(fmt.Sprintf("Total: %v", total)) 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, " ") } // Helpers func (tz *TimeZone) Now() time.Time { return time.Now().In(tz.loc) } func (tz *TimeZone) Today() time.Time { t := time.Now().In(tz.loc) return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, tz.loc) } func (tz *TimeZone) Yesterday() time.Time { return tz.Today().AddDate(0, 0, -1) } func (tz *TimeZone) Tomorrow() time.Time { return tz.Today().AddDate(0, 0, 1) } func Now() time.Time { return defaultTZ.Now() } func Today() time.Time { return defaultTZ.Today() } func Yesterday() time.Time { return defaultTZ.Yesterday() } func Tomorrow() time.Time { return defaultTZ.Tomorrow() }