123 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
package controllers
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/teambition/rrule-go"
 | 
						|
)
 | 
						|
 | 
						|
type RecurrenceRule struct {
 | 
						|
	Frequency string
 | 
						|
	UntilTime time.Time
 | 
						|
}
 | 
						|
 | 
						|
type Period struct {
 | 
						|
	StartTime time.Time
 | 
						|
	EndTime   time.Time
 | 
						|
}
 | 
						|
 | 
						|
func (r *Period) String() string {
 | 
						|
	if r == nil {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	return r.StartTime.Format(time.RFC3339) + "-" + r.EndTime.Format(time.RFC3339)
 | 
						|
}
 | 
						|
 | 
						|
func MatchSchedule(now time.Time, startTime, endTime time.Time, recurrenceRule RecurrenceRule) (*Period, *Period, error) {
 | 
						|
	return calculateActiveAndUpcomingRecurringPeriods(
 | 
						|
		now,
 | 
						|
		startTime,
 | 
						|
		endTime,
 | 
						|
		recurrenceRule.Frequency,
 | 
						|
		recurrenceRule.UntilTime,
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
func calculateActiveAndUpcomingRecurringPeriods(now, startTime, endTime time.Time, frequency string, untilTime time.Time) (*Period, *Period, error) {
 | 
						|
	var freqValue rrule.Frequency
 | 
						|
 | 
						|
	var freqDurationDay int
 | 
						|
	var freqDurationMonth int
 | 
						|
	var freqDurationYear int
 | 
						|
 | 
						|
	switch frequency {
 | 
						|
	case "Daily":
 | 
						|
		freqValue = rrule.DAILY
 | 
						|
		freqDurationDay = 1
 | 
						|
	case "Weekly":
 | 
						|
		freqValue = rrule.WEEKLY
 | 
						|
		freqDurationDay = 7
 | 
						|
	case "Monthly":
 | 
						|
		freqValue = rrule.MONTHLY
 | 
						|
		freqDurationMonth = 1
 | 
						|
	case "Yearly":
 | 
						|
		freqValue = rrule.YEARLY
 | 
						|
		freqDurationYear = 1
 | 
						|
	case "":
 | 
						|
		if now.Before(startTime) {
 | 
						|
			return nil, &Period{StartTime: startTime, EndTime: endTime}, nil
 | 
						|
		}
 | 
						|
 | 
						|
		if now.Before(endTime) {
 | 
						|
			return &Period{StartTime: startTime, EndTime: endTime}, nil, nil
 | 
						|
		}
 | 
						|
 | 
						|
		return nil, nil, nil
 | 
						|
	default:
 | 
						|
		return nil, nil, fmt.Errorf(`invalid freq %q: It must be one of "Daily", "Weekly", "Monthly", and "Yearly"`, frequency)
 | 
						|
	}
 | 
						|
 | 
						|
	freqDurationLater := time.Date(
 | 
						|
		now.Year()+freqDurationYear,
 | 
						|
		time.Month(int(now.Month())+freqDurationMonth),
 | 
						|
		now.Day()+freqDurationDay,
 | 
						|
		now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), now.Location(),
 | 
						|
	)
 | 
						|
 | 
						|
	freqDuration := freqDurationLater.Sub(now)
 | 
						|
 | 
						|
	overrideDuration := endTime.Sub(startTime)
 | 
						|
	if overrideDuration > freqDuration {
 | 
						|
		return nil, nil, fmt.Errorf("override's duration %s must be equal to sor shorter than the duration implied by freq %q (%s)", overrideDuration, frequency, freqDuration)
 | 
						|
	}
 | 
						|
 | 
						|
	rrule, err := rrule.NewRRule(rrule.ROption{
 | 
						|
		Freq:    freqValue,
 | 
						|
		Dtstart: startTime,
 | 
						|
		Until:   untilTime,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	overrideDurationBefore := now.Add(-overrideDuration + 1)
 | 
						|
	activeOverrideStarts := rrule.Between(overrideDurationBefore, now, true)
 | 
						|
 | 
						|
	var active *Period
 | 
						|
 | 
						|
	if len(activeOverrideStarts) > 1 {
 | 
						|
		return nil, nil, fmt.Errorf("[bug] unexpted number of active overrides found: %v", activeOverrideStarts)
 | 
						|
	} else if len(activeOverrideStarts) == 1 {
 | 
						|
		active = &Period{
 | 
						|
			StartTime: activeOverrideStarts[0],
 | 
						|
			EndTime:   activeOverrideStarts[0].Add(overrideDuration),
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	oneSecondLater := now.Add(1)
 | 
						|
	upcomingOverrideStarts := rrule.Between(oneSecondLater, freqDurationLater, true)
 | 
						|
 | 
						|
	var next *Period
 | 
						|
 | 
						|
	if len(upcomingOverrideStarts) > 0 {
 | 
						|
		next = &Period{
 | 
						|
			StartTime: upcomingOverrideStarts[0],
 | 
						|
			EndTime:   upcomingOverrideStarts[0].Add(overrideDuration),
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return active, next, nil
 | 
						|
}
 |