package main
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
)
const (
FORMAT_DATETIME = "2006-01-02 15:04:05"
FORMAT_DURATION = "15h04m05s"
)
// the frequency an event recurs
type RecurrenceWeekday string
// the frequency an event recurs
type RecurrenceFrequency string
const (
SecondRecurrenceFrequency RecurrenceFrequency = "SECONDLY"
MinuteRecurrenceFrequency = "MINUTELY"
HourRecurrenceFrequency = "HOURLY"
DayRecurrenceFrequency = "DAILY"
WeekRecurrenceFrequency = "WEEKLY"
MonthRecurrenceFrequency = "MONTHLY"
YearRecurrenceFrequency = "YEARLY"
)
const (
MondayRecurrenceWeekday RecurrenceWeekday = "MO"
TuesdayRecurrenceWeekday = "TU"
WednesdayRecurrenceWeekday = "WE"
ThursdayRecurrenceWeekday = "TH"
FridayRecurrenceWeekday = "FR"
SaturdayRecurrenceWeekday = "SA"
SundayRecurrenceWeekday = "SU"
)
func Date(year int, month time.Month, monthWeek int, weekDay time.Weekday, hour, minute, sec int, loc *time.Location) time.Time {
monthFstDayTime := time.Date(year, month, 1, hour, minute, sec, 0, loc) //一号
week := monthFstDayTime.Weekday()
var monthDay int = 1
if week != time.Sunday { // 算出第一个星期天到底是几号
monthDay = 1 + int(time.Sunday) - int(week)
//monthDay = int(week) - 7
}
if week == time.Sunday {
monthDay = -6
}
monthDay = monthDay + (monthWeek * 7) + int(weekDay)
return time.Date(year, month, monthDay, hour, minute, sec, 0, loc)
}
func WeekdayInt(s string) time.Weekday {
var r time.Weekday
switch RecurrenceWeekday(s) {
case SundayRecurrenceWeekday:
r = time.Sunday
case MondayRecurrenceWeekday:
r = time.Monday
case TuesdayRecurrenceWeekday:
r = time.Tuesday
case WednesdayRecurrenceWeekday:
r = time.Wednesday
case ThursdayRecurrenceWeekday:
r = time.Thursday
case FridayRecurrenceWeekday:
r = time.Friday
case SaturdayRecurrenceWeekday:
r = time.Saturday
}
return r
}
func WeekdayString(weekday time.Weekday) RecurrenceWeekday {
var r RecurrenceWeekday
switch weekday {
case time.Sunday:
r = SundayRecurrenceWeekday
case time.Monday:
r = MondayRecurrenceWeekday
case time.Tuesday:
r = TuesdayRecurrenceWeekday
case time.Wednesday:
r = WednesdayRecurrenceWeekday
case time.Thursday:
r = ThursdayRecurrenceWeekday
case time.Friday:
r = FridayRecurrenceWeekday
case time.Saturday:
return SaturdayRecurrenceWeekday
}
return r
}
type TRecurrenceInfo struct {
EventId uint64 `json:"-"`
Frequency string `json:"frequency,omitempty"` // daily, weekly, monthly, yearly
Interval int64 `json:"interval,omitempty"` // 每几天(1-365)/周(1-52)/月(1-12)/年(1-10)
ByDay string `json:"byday,omitempty"` // 星期几(SU,MO,TU,WE,TH,FR,SA), CSV格式字符串, 所以存在可能为多个
ByMonth string `json:"bymonth,omitempty"` // 几月(1-12)
ByMonthDay string `json:"bymonthday,omitempty"` // 几号(1-31)
BySetPos string `json:"bysetpos,omitempty"` // 第几周(1-5)
RecurrenceCount int64 `json:"repeatcount,omitempty"` // 循环次数(2-100)
RecurrenceEndDate int64 `json:"repeatenddate,omitempty"` // 时间戳(秒)
}
const (
DayDuration = int64(time.Hour) * 24
WeekDuration = int64(time.Hour) * 24 * 7
MonthDuration = int64(time.Hour) * 24 * 30
YearDuration = int64(time.Hour) * 24 * 365
)
//周期会议的第一场时间
func (rule *TRecurrenceInfo) GetFstTime(start, end, length int64, loc *time.Location) (fstStart, fstEnd time.Time) {
startTime := time.Unix(start, 0).In(loc)
eneTime := time.Unix(end, 0).In(loc)
switch strings.ToUpper(rule.Frequency) {
case DayRecurrenceFrequency:
fstStart = startTime
fstEnd = eneTime
case WeekRecurrenceFrequency:
if len(rule.ByDay) > 0 {
dayWeekInts := rule.convertDayList()
fstWeekDay := time.Weekday(dayWeekInts[0])
startWeekDay := startTime.Weekday()
if startWeekDay == fstWeekDay {
fstStart = startTime
fstEnd = fstStart.Add(time.Duration(int64(time.Hour) * length))
} else {
deleduration := int64(7-startWeekDay+fstWeekDay) * DayDuration
fstStart = startTime.Add(time.Duration(deleduration))
fstEnd = fstStart.Add(time.Duration(int64(time.Hour) * length))
}
}
case MonthRecurrenceFrequency:
year, month, _ := startTime.Date()
hour, minute, sec := startTime.Clock()
if len(rule.ByMonthDay) > 0 {
monthDayList := strings.Split(rule.ByMonthDay, ",")
fstMonthDay := 1
if len(monthDayList) > 0 {
fstMonthDay = int(ToInt64(monthDayList[0], 1))
}
fstStart = time.Date(year, month, fstMonthDay, hour, minute, sec, 0, loc)
if fstStart.Unix() < start {
fstStart = time.Date(year, month+1, fstMonthDay, hour, minute, sec, 0, loc)
}
fstEnd = fstStart.Add(time.Duration(int64(time.Hour) * length))
}
if len(rule.BySetPos) > 0 && len(rule.ByDay) > 0 {
fstWeekDay := WeekdayInt(rule.ByDay)
fstStart = Date(year, month, int(ToInt64(rule.BySetPos, 0)),
fstWeekDay, hour, minute, sec, loc)
if fstStart.Unix() < start {
fstStart = Date(year, month+1, int(ToInt64(rule.BySetPos, 0)),
fstWeekDay, hour, minute, sec, loc)
}
fstEnd = fstStart.Add(time.Duration(int64(time.Hour) * length))
}
case YearRecurrenceFrequency:
year, month, _ := startTime.Date()
hour, minute, sec := startTime.Clock()
month = time.Month(ToInt64(rule.ByMonth, 1))
if len(rule.ByMonthDay) > 0 {
fstStart = time.Date(year, month, int(ToInt64(rule.ByMonthDay, 1)), hour, minute, sec, 0, loc)
if fstStart.Unix() < start {
fstStart = time.Date(year+1, month, int(ToInt64(rule.ByMonthDay, 1)), hour, minute, sec, 0, loc)
}
fstEnd = fstStart.Add(time.Duration(int64(time.Hour) * length))
}
if len(rule.BySetPos) > 0 && len(rule.ByDay) > 0 {
fstWeekDay := WeekdayInt(rule.ByDay)
fstStart = Date(year, month, int(ToInt64(rule.BySetPos, 0)),
fstWeekDay, hour, minute, sec, loc)
if fstStart.Unix() < start {
fstStart = Date(year+1, month, int(ToInt64(rule.BySetPos, 0)),
fstWeekDay, hour, minute, sec, loc)
}
fstEnd = fstStart.Add(time.Duration(int64(time.Hour) * length))
}
}
return
}
//利用迭代的方式做
//@index 表示第几个周期
//@fstStartTime 周期第一场的时间
//@fstEndTime 周期第一场的结束时间
func (rule *TRecurrenceInfo) GetRecurrenceSingleTime2(index, fstStartTime, fstEndTime, now int64, loc *time.Location) (start, end time.Time) {
if index < 0 {
return
}
// 处理周期会议
var deltaDuration int64 = 0
if rule.RecurrenceCount > 0 {
if index >= rule.RecurrenceCount {
return
}
}
if now <= fstStartTime {
return
}
start = time.Unix(fstStartTime, 0).In(loc)
end = time.Unix(fstEndTime, 0).In(loc)
interval := rule.Interval
switch strings.ToUpper(rule.Frequency) {
case DayRecurrenceFrequency:
deltaDuration = index * interval * DayDuration
start = start.Add(time.Duration(deltaDuration))
end = end.Add(time.Duration(deltaDuration))
if now > end.Unix() {
index++
return rule.GetRecurrenceSingleTime2(index, fstStartTime, fstEndTime, now, loc)
}
case WeekRecurrenceFrequency:
dayList := strings.Split(rule.ByDay, ",")
dayLen := int64(len(dayList))
if dayLen > 1 {
dayWeekInts := rule.convertDayList()
offset := dayWeekInts[len(dayWeekInts)-1] - dayWeekInts[0]
//计算这个周期里的第一天的开始与最后一天时间结束时间
fstDayStartTime, _, _, lastDayEndTime := rule.singleCycleWeekTime(index, interval, offset, start, end)
fmt.Println("recurrenceSingleStartTime:", fstDayStartTime, "recurrenceSingleEndTime:", lastDayEndTime, "offset:", offset, "now:", time.Unix(now, 0).In(loc))
//如果当前时间大于某个周期的最后一天的时间,则递归,继续计算下个周期的时间
if now > lastDayEndTime.Unix() {
index++
return rule.GetRecurrenceSingleTime2(index, fstStartTime, fstEndTime, now, loc)
}
//否则,在这个周期里查找最近的一天的时间
if now < lastDayEndTime.Unix() {
nowTime := time.Unix(now, 0).In(loc)
nowWeekDay := nowTime.Weekday()
if nowWeekDay == 0 {
nowWeekDay = 7
}
//否则的话,就取下个周期中,比当前时间所在天最近的一天
for _, dayWeekInt := range dayWeekInts {
if nowWeekDay <= time.Weekday(dayWeekInt) {
offset = dayWeekInt - dayWeekInts[0]
break
}
}
if now < fstDayStartTime.Unix() {
offset = 0
}
_, _, start, end = rule.singleCycleWeekTime(index, interval, offset, start, end)
fmt.Println("************************")
fmt.Println("start:", start, "end:", end)
return
}
break
}
_, _, start, end = rule.singleCycleWeekTime(index, interval, 0, start, end)
if now > end.Unix() {
index++
return rule.GetRecurrenceSingleTime2(index, fstStartTime, fstEndTime, now, loc)
}
case MonthRecurrenceFrequency:
if rule.BySetPos == "" {
dayList := strings.Split(rule.ByMonthDay, ",")
dayLen := int64(len(dayList))
var offset int64
if dayLen > 0 {
offset = ToInt64(dayList[dayLen-1], 0) - ToInt64(dayList[0], 0)
}
fstDayStartTime, _, _, lastDayEndTime := rule.singleCycleMonthTime(index, interval, offset, rule.BySetPos, start, end)
fmt.Println("recurrenceSingleStartTime:", fstDayStartTime, "recurrenceSingleEndTime:", lastDayEndTime, "offset:", offset)
if now > lastDayEndTime.Unix() {
index++
return rule.GetRecurrenceSingleTime2(index, fstStartTime, fstEndTime, now, loc)
}
//查找大于当前时间且在周期规则内的时间
if now < lastDayEndTime.Unix() {
nowTime := time.Unix(now, 0).In(loc)
_, _, nowDayInt := nowTime.Date()
//当前时间比下一个周期的开始时间早,则去下一个周期的开始时间
if now < fstDayStartTime.Unix() {
offset = 0
}
//否则的话,就取下个周期中,比当前时间所在天最近的一天
for _, dayInt := range dayList {
if int64(nowDayInt) <= ToInt64(dayInt, 0) {
offset = ToInt64(dayInt, 0) - ToInt64(dayList[0], 0)
break
}
}
_, _, start, end = rule.singleCycleMonthTime(index, interval, offset, rule.BySetPos, start, end)
fmt.Println("month")
fmt.Println("start:", start, "end:", end)
return
}
} else {
_, _, start, end = rule.singleCycleMonthTime(index, interval, 0, rule.BySetPos, start, end)
if now > end.Unix() {
index++
return rule.GetRecurrenceSingleTime2(index, fstStartTime, fstEndTime, now, loc)
}
}
case YearRecurrenceFrequency:
if rule.BySetPos == "" {
nextYearTime := start.AddDate(int(index*interval), 0, 0)
deltaDuration = int64(nextYearTime.Sub(start))
start = start.Add(time.Duration(deltaDuration))
end = end.Add(time.Duration(deltaDuration))
} else {
bySetPos := ToInt64(rule.BySetPos, 1)
year, month, _ := start.Date()
hour, minute, sec := start.Clock()
start = Date(year+int(index*interval), month, int(bySetPos),
WeekdayInt(rule.ByDay), hour, minute, sec, start.Location())
year, month, _ = end.Date()
hour, minute, sec = end.Clock()
end = Date(year+int(index*interval), month, int(bySetPos),
WeekdayInt(rule.ByDay), hour, minute, sec, end.Location())
}
if now > end.Unix() {
index++
return rule.GetRecurrenceSingleTime2(index, fstStartTime, fstEndTime, now, loc)
}
}
return
}
//返回某个周期的会议的开始、结束时间
//这种case:每周礼拜一、二、五周期会议,会返回第一天的开始、结束(比如:礼拜一)和最后一天的开始、结束时间(比如:礼拜五)
//@index表示第几个周期
//@interval表示周期间隔
//@offset表示某个周期里,最后一天与第一天的时间间隔。比如,这种case:周五和周一的时间间隔
//@fstStart表示周期会议第一场开始时间
//@fstEnd表示周期会议第一场结束时间
//@fstDayStartTime表示某个周期里,第一天的开始时间
//@fstDayEndTime表示某个周期里,第一天的结束时间
//@start某个周期里固定某天的会议开始时间,当offset=0时,等于fstDayStartTime
//@end某个周期里固定某天的会议结束时间,当offset=0时,等于fstDayEndTime
func (rule *TRecurrenceInfo) singleCycleWeekTime(index, interval int64, offset int, fstStart, fstEnd time.Time) (fstDayStartTime, fstDayEndTime, start, end time.Time) {
deltaDuration := index * interval * WeekDuration
fstDayStartTime = fstStart.Add(time.Duration(deltaDuration))
fstDayEndTime = fstEnd.Add(time.Duration(deltaDuration))
deltaDuration += int64(offset) * DayDuration
start = fstStart.Add(time.Duration(deltaDuration))
end = fstEnd.Add(time.Duration(deltaDuration))
return
}
//返回某个周期的会议的开始、结束时间
//这种case:每月2、3、4、5号周期会议,会返回第一天的开始、结束(比如:2号)和最后一天的开始、结束时间(比如:5号)
//@index表示第几个周期
//@offset表示某个周期里,最后一天与第一天的时间间隔。比如,这种case:周五和周一的时间间隔
//@fstStart表示周期会议第一场开始时间
//@fstEnd表示周期会议第一场结束时间
//@fstDayStartTime表示某个周期里,第一天的开始时间
//@fstDayEndTime表示某个周期里,第一天的结束时间
//@start某个周期里固定某天的会议开始时间,当offset=0时,等于fstDayStartTime
//@end某个周期里固定某天的会议结束时间,当offset=0时,等于fstDayEndTime
func (rule *TRecurrenceInfo) singleCycleMonthTime(index, interval, offset int64, setPos string, fstStart, fstEnd time.Time) (fstDayStartTime, fstDayEndTime, start, end time.Time) {
nextMonthTime := start.AddDate(0, int(index*interval), 0)
deltaDuration := int64(nextMonthTime.Sub(start))
fstDayStartTime = fstStart.Add(time.Duration(deltaDuration))
fstDayEndTime = fstEnd.Add(time.Duration(deltaDuration))
deltaDuration += int64(offset) * DayDuration
start = fstStart.Add(time.Duration(deltaDuration))
end = fstEnd.Add(time.Duration(deltaDuration))
if len(setPos) != 0 {
bySetPos := ToInt64(rule.BySetPos, 1)
year, month, _ := fstStart.Date()
hour, minute, sec := fstStart.Clock()
start = Date(year, month+time.Month(index*interval), int(bySetPos),
WeekdayInt(rule.ByDay), hour, minute, sec, start.Location())
fstDayEndTime = start
year, month, _ = fstEnd.Date()
hour, minute, sec = fstEnd.Clock()
end = Date(year, month+time.Month(index*interval), int(bySetPos),
WeekdayInt(rule.ByDay), hour, minute, sec, end.Location())
fstDayEndTime = end
}
return
}
func (rule *TRecurrenceInfo) convertDayList() []int {
dayList := strings.Split(rule.ByDay, ",")
dayWeekInts := make([]int, 0)
for _, day := range dayList {
dayWeekInt := int(WeekdayInt(day))
if dayWeekInt == 0 {
dayWeekInt = 7
}
dayWeekInts = append(dayWeekInts, dayWeekInt)
}
sort.Ints(dayWeekInts)
return dayWeekInts
}
//落在周期规则时间里这一天和第一天所在周几相隔的天数
func getDuration(dayList []string, recentWeekDayInt int64) int64 {
firstWeekDayInt := int64(WeekdayInt(dayList[0]))
return recentWeekDayInt - firstWeekDayInt
}
func ToInt64(v interface{}, defaultVal int64) int64 {
if v == nil {
return defaultVal
}
switch v.(type) {
case bool:
if v.(bool) {
return 1
}
return 0
case string:
i, err := strconv.ParseInt(v.(string), 10, 64)
if err != nil {
return defaultVal
}
return i
case uint64:
return int64(v.(uint64))
case int64:
return int64(v.(int64))
case int:
return int64(v.(int))
case int32:
return int64(v.(int32))
case uint32:
return int64(v.(uint32))
case float64:
return int64(v.(float64))
case int8:
return int64(v.(int8))
case uint8:
return int64(v.(uint8))
}
return defaultVal
}
func main() {
rule := &TRecurrenceInfo{
Frequency: "daily",
Interval: 1,
RecurrenceEndDate: 0,
}
rule = &TRecurrenceInfo{
Frequency: "daily",
Interval: 2,
RecurrenceEndDate: 0,
}
rule = &TRecurrenceInfo{
Frequency: "weekly",
Interval: 1,
ByDay: "MO,TU,WE,TH,FR",
RecurrenceEndDate: 0,
}
rule = &TRecurrenceInfo{
Frequency: "weekly",
Interval: 1,
ByDay: "MO,WE,FR",
RecurrenceEndDate: 0,
}
rule = &TRecurrenceInfo{
Frequency: "weekly",
Interval: 2,
ByDay: "MO,WE,FR",
RecurrenceEndDate: 0,
}
rule = &TRecurrenceInfo{
Frequency: "weekly",
Interval: 2,
ByDay: "MO,WE,FR",
RecurrenceEndDate: 0,
}
nowStr := "2022-08-09 10:00:00"
startStr := "2022-08-01 10:00:00"
endStr := "2022-08-01 11:00:00"
now, _ := time.Parse(FORMAT_DATETIME, nowStr)
start, _ := time.Parse(FORMAT_DATETIME, startStr)
end, _ := time.Parse(FORMAT_DATETIME, endStr)
fmt.Println("now:", now.Unix(), "start:", start.Unix())
s, e := rule.GetRecurrenceSingleTime2(0, start.Unix(), end.Unix(), now.Unix(), time.Local)
fmt.Println("【周N周后M+天每几周星期几】递归的方式获取周期单场时间:start,end", s, e)
rule = &TRecurrenceInfo{
Frequency: "weekly",
Interval: 2,
ByDay: "MO",
RecurrenceEndDate: 0,
}
nowStr = "2022-08-09 10:00:00"
startStr = "2022-08-01 10:00:00"
endStr = "2022-08-01 11:00:00"
now, _ = time.Parse(FORMAT_DATETIME, nowStr)
start, _ = time.Parse(FORMAT_DATETIME, startStr)
end, _ = time.Parse(FORMAT_DATETIME, endStr)
fmt.Println("now:", now.Unix(), "start:", start.Unix())
s, e = rule.GetRecurrenceSingleTime2(0, start.Unix(), end.Unix(), now.Unix(), time.Local)
fmt.Println("【周】递归的方式获取周期单场时间:start,end", s, e)
rule = &TRecurrenceInfo{
Frequency: "monthly",
Interval: 1,
ByMonthDay: "1,2,3,4,5,8,18",
RecurrenceEndDate: 0,
}
nowStr = "2022-08-09 10:00:00"
startStr = "2022-08-01 10:00:00"
endStr = "2022-08-01 11:00:00"
now, _ = time.Parse(FORMAT_DATETIME, nowStr)
start, _ = time.Parse(FORMAT_DATETIME, startStr)
end, _ = time.Parse(FORMAT_DATETIME, endStr)
s, e = rule.GetRecurrenceSingleTime2(0, start.Unix(), end.Unix(), now.Unix(), time.Local)
fmt.Println("【月每几月的几号】递归的方式获取周期单场时间:start,end", s, e)
rule = &TRecurrenceInfo{
Frequency: "monthly",
Interval: 1,
ByDay: "MO",
BySetPos: "2",
RecurrenceEndDate: 0,
}
nowStr = "2022-08-09 10:00:00"
startStr = "2022-08-08 10:00:00"
endStr = "2022-08-08 11:00:00"
now, _ = time.Parse(FORMAT_DATETIME, nowStr)
start, _ = time.Parse(FORMAT_DATETIME, startStr)
end, _ = time.Parse(FORMAT_DATETIME, endStr)
s, e = rule.GetRecurrenceSingleTime2(0, start.Unix(), end.Unix(), now.Unix(), time.Local)
fmt.Println("【月每几月的第几个星期几】递归的方式获取周期单场时间:start,end", s, e)
// recentDay, recentWeekDayInt, recentDayInt := recentDay(now.Unix(), []string{"MO", "WE", "FR"}, time.Local)
// fmt.Println("**************")
// fmt.Println(recentDay, recentWeekDayInt, recentDayInt)
getFstTime()
}
func getFstTime() {
rule := &TRecurrenceInfo{
Frequency: "daily",
Interval: 2,
RecurrenceEndDate: 0,
}
startStr := "2022-08-11 10:00:00"
endStr := "2022-08-11 11:00:00"
start, _ := time.Parse(FORMAT_DATETIME, startStr)
end, _ := time.Parse(FORMAT_DATETIME, endStr)
fstStart, fstEnd := rule.GetFstTime(start.Unix(), end.Unix(), 1, time.Local)
fmt.Println("【每两天】", fstStart, fstEnd)
rule = &TRecurrenceInfo{
Frequency: "weekly",
Interval: 1,
ByDay: "MO,TU,WE,TH,FR",
RecurrenceEndDate: 0,
}
fstStart, fstEnd = rule.GetFstTime(start.Unix(), end.Unix(), 1, time.Local)
fmt.Println("【每周一二三四五】", fstStart, fstEnd)
rule = &TRecurrenceInfo{
Frequency: "weekly",
Interval: 1,
ByDay: "TH",
RecurrenceEndDate: 0,
}
fstStart, fstEnd = rule.GetFstTime(start.Unix(), end.Unix(), 1, time.Local)
fmt.Println("【每周四】", fstStart, fstEnd)
rule = &TRecurrenceInfo{
Frequency: "monthly",
Interval: 1,
ByMonthDay: "1,2,3,4,5,8,18",
RecurrenceEndDate: 0,
}
fstStart, fstEnd = rule.GetFstTime(start.Unix(), end.Unix(), 1, time.Local)
fmt.Println("【月每几月的几号】", fstStart, fstEnd)
rule = &TRecurrenceInfo{
Frequency: "monthly",
Interval: 1,
ByDay: "MO",
BySetPos: "2",
RecurrenceEndDate: 0,
}
fstStart, fstEnd = rule.GetFstTime(start.Unix(), end.Unix(), 1, time.Local)
fmt.Println("【月每几月的第几个星期几】", fstStart, fstEnd)
rule = &TRecurrenceInfo{
Frequency: "yearly",
Interval: 1,
ByMonth: "1",
ByMonthDay: "1",
RecurrenceEndDate: 0,
}
fstStart, fstEnd = rule.GetFstTime(start.Unix(), end.Unix(), 1, time.Local)
fmt.Println("【每年第一个月1号】", fstStart, fstEnd)
rule = &TRecurrenceInfo{
Frequency: "yearly",
Interval: 1,
ByMonth: "1",
BySetPos: "1",
ByDay: "MO",
RecurrenceEndDate: 0,
}
fstStart, fstEnd = rule.GetFstTime(start.Unix(), end.Unix(), 1, time.Local)
fmt.Println("【每年第一个月第一周礼拜一】", fstStart, fstEnd)
}