package time_arrow import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "log" "sort" "strings" "sync" "time" "github.com/google/uuid" ) type TimeArrowType string const ( TimeArrowTypeDayOfWeek TimeArrowType = "DAY_OF_WEEK" TimeArrowTypeDayOfMonth TimeArrowType = "DAY_OF_MONTH" TimeArrowTypeDateSlice TimeArrowType = "DATE_SLICE" TimeArrowTypeInHolidays TimeArrowType = "IN_HOLIDAYS" //节假日 TimeArrowTypeAtLastDayHolidays TimeArrowType = "AT_LAST_DAY_HOLIDAYS" //节假日最后一天 TimeArrowTypePriorToDayHolidays TimeArrowType = "PRIOR_TO_DAY_HOLIDAYS" //节假日前一天 ) type DateSlice struct { Start string `json:"start"` End string `json:"end"` } type TimeArrow struct { TimeArrowId string `json:"time_arrow_id"` Group string `json:"group"` Type TimeArrowType `json:"type"` //可选 DayOfWeek []int `json:"day_of_week"` DayOfMonth []int `json:"day_of_month"` DateSlice []DateSlice `json:"date_slice"` //必选 TimesOnDay []string `json:"times_on_day"` ExpandTags []string `json:"expand_tags"` ExpandValue interface{} `json:"expand_value"` Weights float64 `json:"weights"` Extension map[string]interface{} `json:"extension"` } type TimeArrows []TimeArrow func isInDateSlice(t time.Time, ta TimeArrow) bool { for e := range ta.DateSlice { startStr := ta.DateSlice[e].Start startSc := strings.Split(startStr, " ") if len(startSc) < 2 { startSc = append(startSc, "00:00:00") } else { startSc[1] = timeCompletion(startSc[1]) } startStr = strings.Join(startSc, " ") endStr := ta.DateSlice[e].End endSc := strings.Split(endStr, " ") if len(endSc) < 2 { endSc = append(endSc, "00:00:00") } else { endSc[1] = timeCompletion(endSc[1]) } endStr = strings.Join(endSc, " ") startTime, err := time.ParseInLocation("2006-01-02 15:04:05", startStr, time.Local) if err != nil { log.Println("时间段开始时间格式错误", startStr) continue } endTime, err := time.ParseInLocation("2006-01-02 15:04:05", endStr, time.Local) if err != nil { log.Println("时间段结束时间格式错误", startStr) continue } if startTime.Unix() > endTime.Unix() { log.Println("开始时间必须小于结束时间", startStr, endStr) continue } if (t.After(startTime) || t.Equal(startTime)) && t.Before(endTime) { return true } } return false } func isInWeekOfDay(t time.Time, ta TimeArrow) bool { nowWeekInt := int(t.Weekday()) for k := range ta.DayOfWeek { if ta.DayOfWeek[k] == nowWeekInt { return true } } return false } var timeCompletionCache sync.Map func timeCompletion(a string) string { rets, ok := timeCompletionCache.Load(a) if ok { return rets.(string) } sc := strings.Split(a, ":") for len(sc) < 3 { sc = append(sc, "00") } for i := range sc { if len(sc[i]) == 1 { sc[i] = "0" + sc[i] } } ret := strings.Join(sc, ":") timeCompletionCache.Store(a, ret) return ret } func isInDayOfMonth(t time.Time, ta TimeArrow) bool { day := t.Day() for e := range ta.DayOfMonth { if ta.DayOfMonth[e] == day { return true } } return false } var inTimeOfDayCache sync.Map func isInTimeOfDay(t time.Time, ta TimeArrow) bool { if len(ta.TimesOnDay) == 0 { return true } tfdate := t.Format("15:04:05") cacheKey := fmt.Sprint(tfdate, strings.Join(ta.TimesOnDay, "|")) ret, ok := inTimeOfDayCache.Load(cacheKey) if ok { return ret.(bool) } for k := range ta.TimesOnDay { tsp := strings.Split(ta.TimesOnDay[k], "-") if len(tsp) < 2 { log.Println("必须为时间段:", ta.TimesOnDay[k]) continue } start, end := tsp[0], tsp[1] if end == "24" || end == "24:00" || end == "24:00:00" { end = "23:59:59" } start = timeCompletion(start) end = timeCompletion(end) // fmt.Println("ssee", start, end, tfdate) if start <= tfdate && tfdate < end { inTimeOfDayCache.Store(cacheKey, true) return true } } inTimeOfDayCache.Store(cacheKey, false) return false } func isInTimeOfDay2(t time.Time, ta TimeArrow) bool { if len(ta.TimesOnDay) == 0 { return true } // t, _ = time.ParseInLocation("2006-01-02 15:04:05", "2011-01-01 "+t.Format("15:04:05"), time.Local) tfdate := t.Format("2006-01-02") // cacheKey := fmt.Sprint(t.Format("15:04:05"), strings.Join(ta.TimesOnDay, "|")) // ret, ok := inTimeOfDayCache.Load(cacheKey) // if ok { // return ret.(bool) // } // var err error for k := range ta.TimesOnDay { tsp := strings.Split(ta.TimesOnDay[k], "-") if len(tsp) < 2 { log.Println("必须为时间段:", ta.TimesOnDay[k]) continue } start, end := tsp[0], tsp[1] start = timeCompletion(start) end = timeCompletion(end) //tfdate为当天日期 startTimeKey := fmt.Sprintf("%s %s", tfdate, start) startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", startTimeKey, time.Local) if startTime.IsZero() { log.Println("时间段开始时间格式错误", start) continue } if end == "24" || end == "24:00" || end == "24:00:00" { end = "23:59:59" } endTimeKey := fmt.Sprintf("%s %s", tfdate, end) endTime, _ := time.ParseInLocation("2006-01-02 15:04:05", endTimeKey, time.Local) if endTime.IsZero() { log.Println("时间段结束时间格式错误", end) continue } if startTime.Unix() > endTime.Unix() { log.Println("开始时间必须小于结束时间", end) continue } if (t.After(startTime) || t.Equal(startTime)) && t.Before(endTime) { // inTimeOfDayCache.Store(cacheKey, true) return true } } // inTimeOfDayCache.Store(cacheKey, false) return false } func isInHolidays(t time.Time) bool { holidaysData, err := GetHolidaysDataWithCache() if err != nil { return false } for e := range holidaysData { startTime := holidaysData[e].Start.Local() endTime := holidaysData[e].End.Local() if (t.After(startTime) || t.Equal(startTime)) && t.Before(endTime) { return true } } return false } func isInAtLastDayHolidays(t time.Time) bool { t = t.Local() holidaysData, err := GetHolidaysDataWithCache() if err != nil { return false } for e := range holidaysData { startTime, err := time.ParseInLocation("2006-01-02 15:04:05", holidaysData[e].End.Format("2006-01-02 00:00:00"), time.Local) if err != nil { log.Println("节假日最后一天时间段开始时间格式错误", holidaysData[e].End) continue } endTime, err := time.ParseInLocation("2006-01-02 15:04:05", fmt.Sprintf("%s 23:59:59", holidaysData[e].End.Format("2006-01-02")), time.Local) if err != nil { log.Println("节假日最后一天时间段结束时间格式错误", holidaysData[e].End) continue } if (t.After(startTime) || t.Equal(startTime)) && t.Before(endTime) { return true } } return false } func isInPriorToDayHolidays(t time.Time) bool { holidaysData, err := GetHolidaysDataWithCache() if err != nil { return false } for e := range holidaysData { startTime, err := time.ParseInLocation("2006-01-02 15:04:05", holidaysData[e].Start.AddDate(0, 0, -1).Format("2006-01-02 00:00:00"), time.Local) if err != nil { log.Println("节假日前一天时间段开始时间格式错误", holidaysData[e].Start) continue } endTime, err := time.ParseInLocation("2006-01-02 15:04:05", fmt.Sprintf("%s 23:59:59", holidaysData[e].Start.AddDate(0, 0, -1).Format("2006-01-02")), time.Local) if err != nil { log.Println("节假日前一天时间段结束时间格式错误", holidaysData[e].Start) continue } if (t.After(startTime) || t.Equal(startTime)) && t.Before(endTime) { return true } } return false } func isInExpandTags(ta TimeArrow, expandTag string) bool { if len(ta.ExpandTags) == 0 { return true } for e := range ta.ExpandTags { if ta.ExpandTags[e] == expandTag { return true } } return false } func createUUID() string { uid := uuid.Must(uuid.NewUUID()) return uid.String() } // CreateDayOfWeekTypePlan 创建一个每周计划 func CreateDayOfWeekTypePlan(group string, dayOfWeek []int, timesOnDay []string, expandValue interface{}, expandTags []string, weights float64) TimeArrow { return TimeArrow{ TimeArrowId: createUUID(), Type: TimeArrowTypeDayOfWeek, Group: group, DayOfWeek: dayOfWeek, TimesOnDay: timesOnDay, ExpandValue: expandValue, ExpandTags: expandTags, Weights: weights, } } // CreateDayOfMonthTypePlan 创建一个每月计划 func CreateDayOfMonthTypePlan(group string, dayOfMonth []int, timesOnDay []string, expandValue interface{}, expandTags []string, weights float64) TimeArrow { return TimeArrow{ TimeArrowId: createUUID(), Type: TimeArrowTypeDayOfMonth, Group: group, DayOfMonth: dayOfMonth, TimesOnDay: timesOnDay, ExpandValue: expandValue, ExpandTags: expandTags, Weights: weights, } } // CreateDateSliceTypePlan 创建一个时间段计划 func CreateDateSliceTypePlan(group string, dateSlice []DateSlice, timesOnDay []string, expandValue interface{}, expandTags []string, weights float64) TimeArrow { return TimeArrow{ TimeArrowId: createUUID(), Type: TimeArrowTypeDateSlice, Group: group, DateSlice: dateSlice, TimesOnDay: timesOnDay, ExpandValue: expandValue, ExpandTags: expandTags, Weights: weights, } } type HolidaysItem struct { Name string `json:"name"` Start time.Time `json:"start"` End time.Time `json:"end"` } func jsonEncode(v interface{}) string { b, _ := json.Marshal(v) return string(b) } var GetHolidaysData func() ([]HolidaysItem, error) var holidayCache cache var holidayCacheLock sync.Mutex func GetHolidaysDataWithCache() ([]HolidaysItem, error) { holidayCacheLock.Lock() defer holidayCacheLock.Unlock() if holidayCache.IsExpired() || holidayCache.GetData() == "" { data, err := GetHolidaysData() if err != nil { return nil, err } holidayCache.SetData(jsonEncode(data), time.Second*60) } data := holidayCache.GetData() var result []HolidaysItem err := json.Unmarshal([]byte(data), &result) return result, err } type TimeArrowHelper struct { GetData func(group string) (TimeArrows, error) cacheMap map[string]*TimeArrow } func (th *TimeArrowHelper) CallHitTimeArrow(t time.Time, group string, call func(*TimeArrow), expandTags ...string) error { ta, err := th.GetData(group) if err != nil { return err } sort.Slice(ta, func(i, j int) bool { return ta[i].Weights > ta[j].Weights }) for e := range ta { //当天具体时间判断 if !isInTimeOfDay(t, ta[e]) { continue } //扩展标签判断 if !isInExpandTags(ta[e], strings.Join(expandTags, "-")) { continue } switch ta[e].Type { case TimeArrowTypeDayOfWeek: //一周中某一天是否判定 if isInWeekOfDay(t, ta[e]) { call(&ta[e]) } break case TimeArrowTypeDayOfMonth: //一月中某一天是否判定 if isInDayOfMonth(t, ta[e]) { call(&ta[e]) } break case TimeArrowTypeDateSlice: //一月中某一天是否判定 if isInDateSlice(t, ta[e]) { call(&ta[e]) } break case TimeArrowTypeAtLastDayHolidays: //节假日最后一天 if isInAtLastDayHolidays(t) { call(&ta[e]) } break case TimeArrowTypePriorToDayHolidays: //节假日前一天 if isInPriorToDayHolidays(t) { call(&ta[e]) } break case TimeArrowTypeInHolidays: //节假日 if isInHolidays(t) { call(&ta[e]) } break default: log.Println("类型错误:", ta[e].Type) } } return nil } func md52Str(text string) string { h := md5.New() h.Write([]byte(text)) return hex.EncodeToString(h.Sum(nil)) } var hitTimeArrowCache = map[string]*TimeArrow{} var hitTimeArrowCacheLock sync.Mutex func (th *TimeArrowHelper) GetHitTimeArrow(t time.Time, group string, expandTags ...string) (*TimeArrow, error) { ta, err := th.GetData(group) if err != nil { return nil, err } keyA := md52Str(jsonEncode(ta)) keyB := md52Str(jsonEncode(t)) keyD := md52Str(group) keyC := md52Str(strings.Join(expandTags, "-")) key := fmt.Sprint(keyA, "_", keyB, "_", keyC, "_", keyD) hitTimeArrowCacheLock.Lock() defer hitTimeArrowCacheLock.Unlock() if hitTimeArrowCache[key] != nil { if hitTimeArrowCache[key].Group == "___nil" { return nil, nil } return hitTimeArrowCache[key], nil } sort.Slice(ta, func(i, j int) bool { return ta[i].Weights > ta[j].Weights }) for e := range ta { //当天具体时间判断 if !isInTimeOfDay(t, ta[e]) { continue } //扩展标签判断 if !isInExpandTags(ta[e], strings.Join(expandTags, "-")) { continue } switch ta[e].Type { case TimeArrowTypeDayOfWeek: //一周中某一天是否判定 if isInWeekOfDay(t, ta[e]) { hitTimeArrowCache[key] = &ta[e] return &ta[e], nil } break case TimeArrowTypeDayOfMonth: //一月中某一天是否判定 if isInDayOfMonth(t, ta[e]) { hitTimeArrowCache[key] = &ta[e] return &ta[e], nil } break case TimeArrowTypeDateSlice: //一月中某一天是否判定 if isInDateSlice(t, ta[e]) { hitTimeArrowCache[key] = &ta[e] return &ta[e], nil } break case TimeArrowTypeAtLastDayHolidays: //节假日最后一天 if isInAtLastDayHolidays(t) { hitTimeArrowCache[key] = &ta[e] return &ta[e], nil } break case TimeArrowTypePriorToDayHolidays: //节假日前一天 if isInPriorToDayHolidays(t) { hitTimeArrowCache[key] = &ta[e] return &ta[e], nil } break case TimeArrowTypeInHolidays: //节假日 if isInHolidays(t) { hitTimeArrowCache[key] = &ta[e] return &ta[e], nil } break default: log.Println("类型错误:", ta[e].Type) } } hitTimeArrowCache[key] = &TimeArrow{ Group: "___nil", } return nil, nil }