cast/time.go

346 lines
8.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cast
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
// TimeZone 定义了特定时区上下文下的时间操作
type TimeZone struct {
loc *time.Location
}
// NewTimeZone 创建一个时区上下文
func NewTimeZone(loc *time.Location) *TimeZone {
if loc == nil {
loc = time.Local
}
return &TimeZone{loc: loc}
}
// DefaultTimeZone 全局默认时区,默认为本地时区
var DefaultTimeZone = NewTimeZone(time.Local)
// SetDefaultTimeZone 修改全局默认时区
func SetDefaultTimeZone(loc *time.Location) {
if loc != nil {
DefaultTimeZone = NewTimeZone(loc)
}
}
// ParseTime 将任意类型转换为 time.Time。
// 支持time.Time, 时间戳 (秒/毫秒/微秒/纳秒), RFC3339, JS 格式, 中文格式等。
// 转换失败返回零值 time.Time{} 以保持 cast 的静默风格。
func (tz *TimeZone) ParseTime(v any) time.Time {
if v == nil {
return time.Time{}
}
if tm, ok := v.(time.Time); ok {
return tm.In(tz.loc)
}
str := strings.TrimSpace(String(v))
if str == "" {
return time.Time{}
}
var tm time.Time
var err error
// 1. 处理纯数字 (时间戳或紧凑格式)
num := Int64(v)
if 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.Time{}
}
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 = Int(matches[1])
m = Int(matches[2])
d = Int(matches[3])
if y > 0 && y < 100 {
y += 2000
}
}
if matches := cnTimeRegex.FindStringSubmatch(str); len(matches) == 5 {
h = Int(matches[2])
mm = Int(matches[3])
s = Int(matches[4])
if matches[1] == "下午" && h < 12 {
h += 12
}
if matches[1] == "上午" && h >= 12 {
h -= 12
}
}
now := time.Now().In(tz.loc)
if y == 0 {
y = now.Year()
}
if m == 0 {
m = int(now.Month())
}
if d == 0 {
d = now.Day()
}
return time.Date(y, time.Month(m), d, h, mm, s, 0, tz.loc)
}
// ParseTime 将任意类型转换为 time.Time。
func ParseTime(v any) time.Time {
return DefaultTimeZone.ParseTime(v)
}
// FormatTime 格式化时间。
// layout 支持: YYYY-MM-DD HH:mm:ss, YYYY/MM/DD, HH:mm 等直观格式。
func (tz *TimeZone) FormatTime(layout string, v any) string {
tm := tz.ParseTime(v)
if tm.IsZero() {
return ""
}
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)
}
// FormatTime 格式化时间。
func FormatTime(layout string, v any) string {
return DefaultTimeZone.FormatTime(layout, v)
}
// AddTime 时间加减 DSL。
// 格式如: "+1Y-2M+3D", "+1h30m", "-1s"。
// 单位支持: Y (年), M (月), D (天), h, m, s, ms, us, ns。
func (tz *TimeZone) AddTime(expr string, v any) time.Time {
tm := tz.ParseTime(v)
if tm.IsZero() {
return tm
}
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)
}
// AddTime 时间加减 DSL。
func AddTime(expr string, v any) time.Time {
return DefaultTimeZone.AddTime(expr, v)
}
// Now 获取当前时间
func (tz *TimeZone) Now() time.Time { return time.Now().In(tz.loc) }
// Now 获取当前时间
func Now() time.Time { return DefaultTimeZone.Now() }