Files
chatlog/pkg/util/time.go
Shen Junzheng 78cce92ce3 x
2025-03-19 13:11:09 +08:00

637 lines
20 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 util
import (
"regexp"
"strconv"
"strings"
"time"
)
var zoneStr = time.Now().Format("-0700")
// 时间粒度常量
type TimeGranularity int
const (
GranularityUnknown TimeGranularity = iota // 未知粒度
GranularitySecond // 精确到秒
GranularityMinute // 精确到分钟
GranularityHour // 精确到小时
GranularityDay // 精确到天
GranularityMonth // 精确到月
GranularityQuarter // 精确到季度
GranularityYear // 精确到年
)
// timeOf 内部函数,解析各种格式的时间点,并返回时间粒度
// 支持以下格式:
// 1. 时间戳(秒): 1609459200 (GranularitySecond)
// 2. 标准日期: 20060102, 2006-01-02 (GranularityDay)
// 3. 带时间的日期: 20060102/15:04, 2006-01-02/15:04 (GranularityMinute)
// 4. 完整时间: 20060102150405 (GranularitySecond)
// 5. RFC3339: 2006-01-02T15:04:05Z07:00 (GranularitySecond)
// 6. 相对时间: 5h-ago, 3d-ago, 1w-ago, 1m-ago, 1y-ago (根据单位确定粒度)
// 7. 自然语言: now (GranularitySecond), today, yesterday (GranularityDay)
// 8. 年份: 2006 (GranularityYear)
// 9. 月份: 200601, 2006-01 (GranularityMonth)
// 10. 季度: 2006Q1, 2006Q2, 2006Q3, 2006Q4 (GranularityQuarter)
// 11. 年月日时分: 200601021504 (GranularityMinute)
func timeOf(str string) (t time.Time, g TimeGranularity, ok bool) {
if str == "" {
return time.Time{}, GranularityUnknown, false
}
str = strings.TrimSpace(str)
// 处理自然语言时间
switch strings.ToLower(str) {
case "now":
return time.Now(), GranularitySecond, true
case "today":
now := time.Now()
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()), GranularityDay, true
case "yesterday":
now := time.Now().AddDate(0, 0, -1)
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()), GranularityDay, true
case "this-week":
now := time.Now()
weekday := int(now.Weekday())
if weekday == 0 { // 周日
weekday = 7
}
// 本周一
monday := now.AddDate(0, 0, -(weekday - 1))
return time.Date(monday.Year(), monday.Month(), monday.Day(), 0, 0, 0, 0, now.Location()), GranularityDay, true
case "last-week":
now := time.Now()
weekday := int(now.Weekday())
if weekday == 0 { // 周日
weekday = 7
}
// 上周一
lastMonday := now.AddDate(0, 0, -(weekday-1)-7)
return time.Date(lastMonday.Year(), lastMonday.Month(), lastMonday.Day(), 0, 0, 0, 0, now.Location()), GranularityDay, true
case "this-month":
now := time.Now()
return time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()), GranularityMonth, true
case "last-month":
now := time.Now()
return time.Date(now.Year(), now.Month()-1, 1, 0, 0, 0, 0, now.Location()), GranularityMonth, true
case "this-year":
now := time.Now()
return time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()), GranularityYear, true
case "last-year":
now := time.Now()
return time.Date(now.Year()-1, 1, 1, 0, 0, 0, 0, now.Location()), GranularityYear, true
case "all":
// 返回零值时间
return time.Time{}, GranularityYear, true
}
// 处理相对时间: 5h-ago, 3d-ago, 1w-ago, 1m-ago, 1y-ago
if strings.HasSuffix(str, "-ago") {
str = strings.TrimSuffix(str, "-ago")
// 特殊处理 0d-ago 为当天开始
if str == "0d" {
now := time.Now()
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()), GranularityDay, true
}
// 解析数字和单位
re := regexp.MustCompile(`^(\d+)([hdwmy])$`)
matches := re.FindStringSubmatch(str)
if len(matches) == 3 {
num, err := strconv.Atoi(matches[1])
if err != nil {
return time.Time{}, GranularityUnknown, false
}
// 确保数字是正数
if num <= 0 {
// 对于0d-ago已经特殊处理其他0或负数都是无效的
if num == 0 && matches[2] != "d" {
return time.Time{}, GranularityUnknown, false
}
return time.Time{}, GranularityUnknown, false
}
now := time.Now()
var resultTime time.Time
var granularity TimeGranularity
switch matches[2] {
case "h": // 小时
resultTime = now.Add(-time.Duration(num) * time.Hour)
granularity = GranularityHour
case "d": // 天
resultTime = now.AddDate(0, 0, -num)
granularity = GranularityDay
case "w": // 周
resultTime = now.AddDate(0, 0, -num*7)
granularity = GranularityDay
case "m": // 月
resultTime = now.AddDate(0, -num, 0)
granularity = GranularityMonth
case "y": // 年
resultTime = now.AddDate(-num, 0, 0)
granularity = GranularityYear
default:
return time.Time{}, GranularityUnknown, false
}
return resultTime, granularity, true
}
// 尝试标准 duration 解析
dur, err := time.ParseDuration(str)
if err == nil {
// 根据duration单位确定粒度
hours := dur.Hours()
if hours < 1 {
return time.Now().Add(-dur), GranularitySecond, true
} else if hours < 24 {
return time.Now().Add(-dur), GranularityHour, true
} else {
return time.Now().Add(-dur), GranularityDay, true
}
}
return time.Time{}, GranularityUnknown, false
}
// 处理季度: 2006Q1, 2006Q2, 2006Q3, 2006Q4
if matched, _ := regexp.MatchString(`^\d{4}Q[1-4]$`, str); matched {
re := regexp.MustCompile(`^(\d{4})Q([1-4])$`)
matches := re.FindStringSubmatch(str)
if len(matches) == 3 {
year, _ := strconv.Atoi(matches[1])
quarter, _ := strconv.Atoi(matches[2])
// 验证年份范围
if year < 1970 || year > 9999 {
return time.Time{}, GranularityUnknown, false
}
// 计算季度的开始月份
startMonth := time.Month((quarter-1)*3 + 1)
return time.Date(year, startMonth, 1, 0, 0, 0, 0, time.Local), GranularityQuarter, true
}
}
// 处理年份: 2006
if len(str) == 4 && isDigitsOnly(str) {
year, err := strconv.Atoi(str)
if err == nil && year >= 1970 && year <= 9999 {
return time.Date(year, 1, 1, 0, 0, 0, 0, time.Local), GranularityYear, true
}
return time.Time{}, GranularityUnknown, false
}
// 处理月份: 200601 或 2006-01
if (len(str) == 6 && isDigitsOnly(str)) || (len(str) == 7 && strings.Count(str, "-") == 1) {
var year, month int
var err error
if len(str) == 6 && isDigitsOnly(str) {
year, err = strconv.Atoi(str[0:4])
if err != nil {
return time.Time{}, GranularityUnknown, false
}
month, err = strconv.Atoi(str[4:6])
if err != nil {
return time.Time{}, GranularityUnknown, false
}
} else { // 2006-01
parts := strings.Split(str, "-")
if len(parts) != 2 {
return time.Time{}, GranularityUnknown, false
}
year, err = strconv.Atoi(parts[0])
if err != nil {
return time.Time{}, GranularityUnknown, false
}
month, err = strconv.Atoi(parts[1])
if err != nil {
return time.Time{}, GranularityUnknown, false
}
}
if year < 1970 || year > 9999 || month < 1 || month > 12 {
return time.Time{}, GranularityUnknown, false
}
return time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.Local), GranularityMonth, true
}
// 处理日期格式: 20060102 或 2006-01-02
if len(str) == 8 && isDigitsOnly(str) {
// 验证年月日
year, _ := strconv.Atoi(str[0:4])
month, _ := strconv.Atoi(str[4:6])
day, _ := strconv.Atoi(str[6:8])
if year < 1970 || year > 9999 || month < 1 || month > 12 || day < 1 || day > 31 {
return time.Time{}, GranularityUnknown, false
}
// 进一步验证日期是否有效
if !isValidDate(year, month, day) {
return time.Time{}, GranularityUnknown, false
}
// 直接构造时间
result := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
return result, GranularityDay, true
} else if len(str) == 10 && strings.Count(str, "-") == 2 {
// 验证年月日
parts := strings.Split(str, "-")
if len(parts) != 3 {
return time.Time{}, GranularityUnknown, false
}
year, err1 := strconv.Atoi(parts[0])
month, err2 := strconv.Atoi(parts[1])
day, err3 := strconv.Atoi(parts[2])
if err1 != nil || err2 != nil || err3 != nil {
return time.Time{}, GranularityUnknown, false
}
if year < 1970 || year > 9999 || month < 1 || month > 12 || day < 1 || day > 31 {
return time.Time{}, GranularityUnknown, false
}
// 进一步验证日期是否有效
if !isValidDate(year, month, day) {
return time.Time{}, GranularityUnknown, false
}
// 直接构造时间
result := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
return result, GranularityDay, true
}
// 处理年月日时分: 200601021504
if len(str) == 12 && isDigitsOnly(str) {
year, _ := strconv.Atoi(str[0:4])
month, _ := strconv.Atoi(str[4:6])
day, _ := strconv.Atoi(str[6:8])
hour, _ := strconv.Atoi(str[8:10])
minute, _ := strconv.Atoi(str[10:12])
if year < 1970 || year > 9999 || month < 1 || month > 12 || day < 1 || day > 31 ||
hour < 0 || hour > 23 || minute < 0 || minute > 59 {
return time.Time{}, GranularityUnknown, false
}
// 进一步验证日期是否有效
if !isValidDate(year, month, day) {
return time.Time{}, GranularityUnknown, false
}
// 直接构造时间
result := time.Date(year, time.Month(month), day, hour, minute, 0, 0, time.Local)
return result, GranularityMinute, true
}
// 处理带时间的日期: 20060102/15:04 或 2006-01-02/15:04
if strings.Contains(str, "/") {
parts := strings.Split(str, "/")
if len(parts) != 2 {
return time.Time{}, GranularityUnknown, false
}
datePart := parts[0]
timePart := parts[1]
// 验证日期部分
var year, month, day int
var err1, err2, err3 error
if len(datePart) == 8 && isDigitsOnly(datePart) {
year, err1 = strconv.Atoi(datePart[0:4])
month, err2 = strconv.Atoi(datePart[4:6])
day, err3 = strconv.Atoi(datePart[6:8])
} else if len(datePart) == 10 && strings.Count(datePart, "-") == 2 {
dateParts := strings.Split(datePart, "-")
if len(dateParts) != 3 {
return time.Time{}, GranularityUnknown, false
}
year, err1 = strconv.Atoi(dateParts[0])
month, err2 = strconv.Atoi(dateParts[1])
day, err3 = strconv.Atoi(dateParts[2])
} else {
return time.Time{}, GranularityUnknown, false
}
if err1 != nil || err2 != nil || err3 != nil {
return time.Time{}, GranularityUnknown, false
}
if year < 1970 || year > 9999 || month < 1 || month > 12 || day < 1 || day > 31 {
return time.Time{}, GranularityUnknown, false
}
// 进一步验证日期是否有效
if !isValidDate(year, month, day) {
return time.Time{}, GranularityUnknown, false
}
// 验证时间部分
if !regexp.MustCompile(`^\d{2}:\d{2}$`).MatchString(timePart) {
return time.Time{}, GranularityUnknown, false
}
timeParts := strings.Split(timePart, ":")
hour, err1 := strconv.Atoi(timeParts[0])
minute, err2 := strconv.Atoi(timeParts[1])
if err1 != nil || err2 != nil {
return time.Time{}, GranularityUnknown, false
}
if hour < 0 || hour > 23 || minute < 0 || minute > 59 {
return time.Time{}, GranularityUnknown, false
}
// 直接构造时间
result := time.Date(year, time.Month(month), day, hour, minute, 0, 0, time.Local)
return result, GranularityMinute, true
}
// 处理完整时间: 20060102150405
if len(str) == 14 && isDigitsOnly(str) {
year, _ := strconv.Atoi(str[0:4])
month, _ := strconv.Atoi(str[4:6])
day, _ := strconv.Atoi(str[6:8])
hour, _ := strconv.Atoi(str[8:10])
minute, _ := strconv.Atoi(str[10:12])
second, _ := strconv.Atoi(str[12:14])
if year < 1970 || year > 9999 || month < 1 || month > 12 || day < 1 || day > 31 ||
hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 {
return time.Time{}, GranularityUnknown, false
}
// 进一步验证日期是否有效
if !isValidDate(year, month, day) {
return time.Time{}, GranularityUnknown, false
}
// 直接构造时间
result := time.Date(year, time.Month(month), day, hour, minute, second, 0, time.Local)
return result, GranularitySecond, true
}
// 处理时间戳(秒)
if isDigitsOnly(str) {
n, err := strconv.ParseInt(str, 10, 64)
if err == nil {
// 检查是否是合理的时间戳范围
if n >= 1000000000 && n <= 253402300799 { // 2001年到2286年的秒级时间戳
return time.Unix(n, 0), GranularitySecond, true
}
}
return time.Time{}, GranularityUnknown, false
}
// 处理 RFC3339: 2006-01-02T15:04:05Z07:00
if strings.Contains(str, "T") && (strings.Contains(str, "Z") || strings.Contains(str, "+") || strings.Contains(str, "-")) {
t, err := time.Parse(time.RFC3339, str)
if err != nil {
// 尝试不带秒的格式
t, err = time.Parse("2006-01-02T15:04Z07:00", str)
}
if err == nil {
return t, GranularitySecond, true
}
}
// 排除所有其他不支持的格式
return time.Time{}, GranularityUnknown, false
}
// TimeOf 解析各种格式的时间点
// 支持以下格式:
// 1. 时间戳(秒): 1609459200
// 2. 标准日期: 20060102, 2006-01-02
// 3. 带时间的日期: 20060102/15:04, 2006-01-02/15:04
// 4. 完整时间: 20060102150405
// 5. RFC3339: 2006-01-02T15:04:05Z07:00
// 6. 相对时间: 5h-ago, 3d-ago, 1w-ago, 1m-ago, 1y-ago (小时、天、周、月、年)
// 7. 自然语言: now, today, yesterday
// 8. 年份: 2006
// 9. 月份: 200601, 2006-01
// 10. 季度: 2006Q1, 2006Q2, 2006Q3, 2006Q4
// 11. 年月日时分: 200601021504
func TimeOf(str string) (t time.Time, ok bool) {
t, _, ok = timeOf(str)
return
}
// TimeRangeOf 解析各种格式的时间范围
// 支持以下格式:
// 1. 单个时间点: 根据时间粒度确定合适的时间范围
// - 精确到秒/分钟/小时: 扩展为当天范围
// - 精确到天: 当天 00:00:00 ~ 23:59:59
// - 精确到月: 当月第一天 ~ 最后一天
// - 精确到季度: 季度第一天 ~ 最后一天
// - 精确到年: 当年第一天 ~ 最后一天
//
// 2. 时间区间: 2006-01-01~2006-01-31, 2006-01-01,2006-01-31, 2006-01-01 to 2006-01-31
// 3. 相对时间: last-7d, last-30d, last-3m, last-1y (最近7天、30天、3个月、1年)
// 4. 特定时间段: today, yesterday, this-week, last-week, this-month, last-month, this-year, last-year
// 5. all: 表示所有时间
func TimeRangeOf(str string) (start, end time.Time, ok bool) {
if str == "" {
return time.Time{}, time.Time{}, false
}
str = strings.TrimSpace(str)
// 处理 all 特殊情况
if strings.ToLower(str) == "all" {
start = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
end = time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.UTC)
return start, end, true
}
// 处理相对时间范围: last-7d, last-30d, last-3m, last-1y
if matched, _ := regexp.MatchString(`^last-\d+[dwmy]$`, str); matched {
re := regexp.MustCompile(`^last-(\d+)([dwmy])$`)
matches := re.FindStringSubmatch(str)
if len(matches) == 3 {
num, err := strconv.Atoi(matches[1])
if err != nil || num <= 0 {
return time.Time{}, time.Time{}, false
}
now := time.Now()
end = time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, now.Location())
switch matches[2] {
case "d": // 天
start = now.AddDate(0, 0, -num)
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location())
return start, end, true
case "w": // 周
start = now.AddDate(0, 0, -num*7)
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location())
return start, end, true
case "m": // 月
start = now.AddDate(0, -num, 0)
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location())
return start, end, true
case "y": // 年
start = now.AddDate(-num, 0, 0)
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location())
return start, end, true
}
}
}
// 处理时间区间: 2006-01-01~2006-01-31, 2006-01-01,2006-01-31, 2006-01-01 to 2006-01-31
separators := []string{"~", ",", " to "}
for _, sep := range separators {
if strings.Contains(str, sep) {
parts := strings.Split(str, sep)
if len(parts) == 2 {
startTime, startGran, startOk := timeOf(strings.TrimSpace(parts[0]))
endTime, endGran, endOk := timeOf(strings.TrimSpace(parts[1]))
if startOk && endOk {
// 根据粒度调整时间范围
start = adjustStartTime(startTime, startGran)
end = adjustEndTime(endTime, endGran)
// 确保开始时间早于结束时间
if start.After(end) {
// 正确交换开始和结束时间
start, end = adjustStartTime(endTime, endGran), adjustEndTime(startTime, startGran)
}
return start, end, true
}
}
}
}
// 处理单个时间点,根据粒度确定合适的时间范围
t, g, ok := timeOf(str)
if ok {
switch g {
case GranularitySecond, GranularityMinute, GranularityHour:
// 精确到秒/分钟/小时的时间点,扩展为当天范围
start = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
end = time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, t.Location())
case GranularityDay:
// 精确到天的时间点
start = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
end = time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, t.Location())
case GranularityMonth:
// 精确到月的时间点
start = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
end = time.Date(t.Year(), t.Month()+1, 0, 23, 59, 59, 999999999, t.Location())
case GranularityQuarter:
// 精确到季度的时间点
quarter := (t.Month()-1)/3 + 1
startMonth := time.Month((int(quarter)-1)*3 + 1)
endMonth := startMonth + 2
start = time.Date(t.Year(), startMonth, 1, 0, 0, 0, 0, t.Location())
end = time.Date(t.Year(), endMonth+1, 0, 23, 59, 59, 999999999, t.Location())
case GranularityYear:
// 精确到年的时间点
start = time.Date(t.Year(), 1, 1, 0, 0, 0, 0, t.Location())
end = time.Date(t.Year(), 12, 31, 23, 59, 59, 999999999, t.Location())
}
return start, end, true
}
return time.Time{}, time.Time{}, false
}
// adjustStartTime 根据时间粒度调整开始时间
func adjustStartTime(t time.Time, g TimeGranularity) time.Time {
switch g {
case GranularitySecond, GranularityMinute, GranularityHour:
// 对于精确到秒/分钟/小时的时间,保持原样
return t
case GranularityDay:
// 精确到天,设置为当天开始
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
case GranularityMonth:
// 精确到月,设置为当月第一天
return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
case GranularityQuarter:
// 精确到季度,设置为季度第一天
quarter := (t.Month()-1)/3 + 1
startMonth := time.Month((int(quarter)-1)*3 + 1)
return time.Date(t.Year(), startMonth, 1, 0, 0, 0, 0, t.Location())
case GranularityYear:
// 精确到年,设置为当年第一天
return time.Date(t.Year(), 1, 1, 0, 0, 0, 0, t.Location())
default:
// 未知粒度,默认为当天开始
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
}
// adjustEndTime 根据时间粒度调整结束时间
func adjustEndTime(t time.Time, g TimeGranularity) time.Time {
switch g {
case GranularitySecond, GranularityMinute, GranularityHour:
// 对于精确到秒/分钟/小时的时间,设置为当天结束
return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, t.Location())
case GranularityDay:
// 精确到天,设置为当天结束
return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, t.Location())
case GranularityMonth:
// 精确到月,设置为当月最后一天
return time.Date(t.Year(), t.Month()+1, 0, 23, 59, 59, 999999999, t.Location())
case GranularityQuarter:
// 精确到季度,设置为季度最后一天
quarter := (t.Month()-1)/3 + 1
startMonth := time.Month((int(quarter)-1)*3 + 1)
endMonth := startMonth + 2
return time.Date(t.Year(), endMonth+1, 0, 23, 59, 59, 999999999, t.Location())
case GranularityYear:
// 精确到年,设置为当年最后一天
return time.Date(t.Year(), 12, 31, 23, 59, 59, 999999999, t.Location())
default:
// 未知粒度,默认为当天结束
return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, t.Location())
}
}
// isDigitsOnly 检查字符串是否只包含数字
func isDigitsOnly(s string) bool {
for _, c := range s {
if c < '0' || c > '9' {
return false
}
}
return len(s) > 0
}
// isValidDate 检查日期是否有效
func isValidDate(year, month, day int) bool {
// 检查月份的天数
daysInMonth := 31
switch month {
case 4, 6, 9, 11:
daysInMonth = 30
case 2:
// 闰年判断
if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
daysInMonth = 29
} else {
daysInMonth = 28
}
}
return day <= daysInMonth
}