You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
526 lines
12 KiB
526 lines
12 KiB
package time_arrow
|
|
|
|
import (
|
|
"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() {
|
|
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)
|
|
}
|
|
|
|
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 (th *TimeArrowHelper) GetHitTimeArrow(t time.Time, group string, expandTags ...string) (*TimeArrow, error) {
|
|
ta, err := th.GetData(group)
|
|
if err != nil {
|
|
return nil, 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]) {
|
|
return &ta[e], nil
|
|
}
|
|
break
|
|
case TimeArrowTypeDayOfMonth:
|
|
//一月中某一天是否判定
|
|
if isInDayOfMonth(t, ta[e]) {
|
|
return &ta[e], nil
|
|
}
|
|
break
|
|
case TimeArrowTypeDateSlice:
|
|
//一月中某一天是否判定
|
|
if isInDateSlice(t, ta[e]) {
|
|
return &ta[e], nil
|
|
}
|
|
break
|
|
case TimeArrowTypeAtLastDayHolidays:
|
|
//节假日最后一天
|
|
if isInAtLastDayHolidays(t) {
|
|
return &ta[e], nil
|
|
}
|
|
break
|
|
case TimeArrowTypePriorToDayHolidays:
|
|
//节假日前一天
|
|
if isInPriorToDayHolidays(t) {
|
|
return &ta[e], nil
|
|
}
|
|
break
|
|
case TimeArrowTypeInHolidays:
|
|
//节假日
|
|
if isInHolidays(t) {
|
|
return &ta[e], nil
|
|
}
|
|
break
|
|
default:
|
|
log.Println("类型错误:", ta[e].Type)
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|