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
 | |
| }
 |