import { Iterate , IterateAction } from 'iteratez' ; import { Constants } from './Constants' ; import { Day , DayInput , DayProperty } from './Day' ; import { durations , Unit } from './DayFunctions' ; import { DaySpan } from './DaySpan' ; import { FrequencyCheck , FrequencyValue } from './Frequency' ; import { Functions as fn } from './Functions' ; import { Identifier , IdentifierInput } from './Identifier' ; import { LocaleRule , Locales } from './Locale' ; import { Op } from './Operation' ; import { Parse } from './Parse' ; import { ScheduleModifier , ScheduleModifierSpan } from './ScheduleModifier' ; import { Time , TimeInput } from './Time' ; import { Units } from './Units' ; /**
 * A tuple which identifies an event on the schedule. The tuple contains the
 * total span of the event occurrence, the day of the event (could be the start
 * day, end day, or any days in between for multi-day events) as well as the
 * identifier for the event.
 */ export type ScheduleEventTuple = [ DaySpan , Day , IdentifierInput ] ; /**
 * A shortcut for the most common type of iterators a Schedule returns.
 */ type Iter < M , T = Day > = Iterate < T , number , Schedule < M > > ; typeparam export interface ScheduleInput < M > { see start ? : DayInput ; see end ? : DayInput ; see maxOccurrences ? : number ; /**
     * A shortcut to setting the [[Schedule.start]], [[Schedule.end]],
     * [[Schedule.year]], [[Schedule.month]], and [[Schedule.dayOfMonth]].
     */ on ? : DayInput ; see times ? : TimeInput [ ] ; see duration ? : number ; see durationUnit ? : string ; see exclude ? : ( Day | IdentifierInput ) [ ] ; see include ? : ( Day | IdentifierInput ) [ ] ; see cancel ? : ( Day | IdentifierInput ) [ ] ; see meta ? : { [ identifier ] : M ; } ; see month ? : FrequencyValue ; see year ? : FrequencyValue ; see day ? : FrequencyValue ; see quarter ? : FrequencyValue ; see dayOfWeek ? : FrequencyValue ; see dayOfMonth ? : FrequencyValue ; see lastDayOfMonth ? : FrequencyValue ; see dayOfYear ? : FrequencyValue ; see week ? : FrequencyValue ; see weekOfYear ? : FrequencyValue ; see weekspanOfYear ? : FrequencyValue ; see fullWeekOfYear ? : FrequencyValue ; see lastWeekspanOfYear ? : FrequencyValue ; see lastFullWeekOfYear ? : FrequencyValue ; see weekOfMonth ? : FrequencyValue ; see weekspanOfMonth ? : FrequencyValue ; see fullWeekOfMonth ? : FrequencyValue ; see lastWeekspanOfMonth ? : FrequencyValue ; see lastFullWeekOfMonth ? : FrequencyValue ; /**
     * The function to parse metadata with.
     */ parseMeta ? ( input ) : M ; } typeparam export class Schedule < M > { /**
     * The earliest an event can occur in the schedule, or `null` if there are no
     * restrictions when the earliest event can occur. This day is inclusive.
     */ public start : Day ; /**
     * The latest an event can occur in the schedule, or `null` if there are no
     * restrictions when the latest event can occur. This day is inclusive.
     */ public end : Day ; private _maxOccurrences : number ; /**
     * The maximum number of occurrences allowed in the Schedule.
     * [[Schedule.start]] is required and [[Schedule.end]] is ignored and
     * overwritten. This is an expensive check and should be avoided if
     * possible. If this value is less than 1 it is ignored.
     */ public get maxOccurrences ( ) : number { return this . _maxOccurrences ; } public set maxOccurrences ( value ) { this . _maxOccurrences = value ; if ( this . checks ) { this . updateEnd ( ) ; } } /**
     * The length of events in this schedule.
     */ public duration : number ; /**
     * The unit which describes the duration of the event.
     */ public durationUnit : Unit ; /**
     * The times at which the events occur on the days they should. If there are
     * no times specified its assumed to be an all day event - potentially over
     * multiple days or weeks based on [[Schedule.duration]] and
     * [[Schedule.durationUnit]].
     */ public times : Time [ ] ; /**
     * The number of days an event in this schedule lasts PAST the starting day.
     * If this is a full day event with a duration greater than zero this value
     * will be greater than one. If this event occurs at a specific time with a
     * given duration that is taken into account and if it passes over into the
     * next day this value will be greater than one. This value is used to look
     * back in time when trying to figure out what events start or overlap on a
     * given day.
     */ public durationInDays : number ; /**
     * A set of identifiers which mark what days or times are excluded on the
     * schedule. This typically represents the set of event occurrences removed.
     */ public exclude : ScheduleModifier < boolean > ; /**
     * A set of identifiers which mark what days or times are included outside
     * the normal series of days on the schedule. This typically represents
     * an event occurrence which is moved so its added to the exclude and include
     * sets.
     */ public include : ScheduleModifier < boolean > ; /**
     * A set of identifiers which mark what days, times, weeks, months, etc that
     * should have all event occurrences cancelled.
     */ public cancel : ScheduleModifier < boolean > ; /**
     * A map of metadata keyed by an identifier. The metadata is placed in
     * [[CalendarEvent]].
     */ public meta : ScheduleModifier < M > ; /**
     * How frequent the event occurs based on [[Day.day]].
     */ public day : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.dayOfWeek]].
     */ public dayOfWeek : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.dayOfMonth]].
     */ public dayOfMonth : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.lastDayOfMonth]].
     */ public lastDayOfMonth : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.dayOfYear]].
     */ public dayOfYear : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.month]].
     */ public month : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.week]].
     */ public week : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.weekOfYear]].
     */ public weekOfYear : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.weekspanOfYear]].
     */ public weekspanOfYear : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.fullWeekOfYear]].
     */ public fullWeekOfYear : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.lastWeekspanOfYear]].
     */ public lastWeekspanOfYear : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.lastFullWeekOfYear]].
     */ public lastFullWeekOfYear : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.weekOfMonth]].
     */ public weekOfMonth : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.weekspanOfMonth]].
     */ public weekspanOfMonth : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.fullWeekOfMonth]].
     */ public fullWeekOfMonth : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.lastWeekspanOfMonth]].
     */ public lastWeekspanOfMonth : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.lastFullWeekOfMonth]].
     */ public lastFullWeekOfMonth : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.year]].
     */ public year : FrequencyCheck ; /**
     * How frequent the event occurs based on [[Day.quarter]].
     */ public quarter : FrequencyCheck ; see public checks : FrequencyCheck [ ] ; param input public constructor ( input ? ) { this . exclude = new ScheduleModifier < boolean > ( ) ; this . include = new ScheduleModifier < boolean > ( ) ; this . cancel = new ScheduleModifier < boolean > ( ) ; this . meta = new ScheduleModifier < M > ( ) ; if ( fn . isDefined ( input ) ) { this . set ( input ) ; } } param input param parseMeta see public set ( input , parseMeta = ( x => x ) ) { if ( input instanceof Schedule ) { Parse . schedule < M > ( input . toInput ( ) , undefined , this ) ; } else { Parse . schedule < M > ( input , fn . coalesce ( input . parseMeta , parseMeta ) , this ) ; } return this ; } /**
     * Returns the last event time specified or `undefined` if this schedule is
     * for an all day event.
     */ public get lastTime ( ) : Time { return this . times [ this . times . length - 1 ] ; } /**
     * The [[Identifier]] for this schedule. Either [[Identifier.Day]] or
     * [[Identifier.Time]].
     */ public get identifierType ( ) : Identifier { return this . isFullDay ( ) ? Identifier . Day : Identifier . Time ; } /**
     * Updates the [[Schedule.durationInDays]] variable based on the
     * [[Schedule.lastTime]] (if any), the [[Schedule.duration]] and it's
     * [[Schedule.durationUnit]].
     */ public updateDurationInDays ( ) { const start = this . lastTime ? this . lastTime . toMilliseconds ( ) : 0 ; const duration = this . duration * ( durations [ this . durationUnit ] || 0 ) ; const exclude = Constants . MILLIS_IN_DAY ; const day = Constants . MILLIS_IN_DAY ; this . durationInDays = Math . max ( 0 , Math . ceil ( ( start + duration - exclude ) / day ) ) ; return this ; } /**
     * Updates [[Schedule.checks]] based on the frequencies that were specified
     * in the schedule input.
     */ public updateChecks ( ) { this . checks = Parse . givenFrequency ( [ this . year , this . month , this . day , this . quarter , this . week , this . weekOfYear , this . fullWeekOfYear , this . weekspanOfYear , this . lastFullWeekOfYear , this . lastWeekspanOfYear , this . weekOfMonth , this . weekspanOfMonth , this . fullWeekOfMonth , this . lastWeekspanOfMonth , this . lastFullWeekOfMonth , this . dayOfWeek , this . dayOfMonth , this . lastDayOfMonth , this . dayOfYear ] ) ; return this ; } /**
     * Updates the [[Schedule.end]] based on [[Schedule.maxOccurrences]]
     */ public updateEnd ( ) { if ( this . maxOccurrences > 0 && this . start ) { this . end = null ; const maximumYears = this . getMaximum ( 'year' ) - this . start . year + 1 ; const lookahead = maximumYears * Constants . DAYS_IN_YEAR ; const previousOccurrences = this . include . spans ( ) . where ( ( { span } ) => span . start . isBefore ( this . start ) ) . count ( ) ; const occurrences = this . maxOccurrences - previousOccurrences ; const last = this . forecast ( this . start , false , occurrences , 0 , true , lookahead ) . last ( ) ; if ( last ) { this . end = last [ 0 ] . end ; } } return this ; } /**
     * Estimates the maximum number of `prop` from the start date that events
     * could be happening. If the start date is not specified -1 will be
     * returned representing potentially infinite `prop`s. If specific `prop`s
     * are specified the difference between the maximum `prop` and the start `prop`
     * will be returned. After that if [[Schedule.maxOccurrences]] is not
     * specified -1 will be returned unless the end date is specified. In that
     * case the `prop`s between the start and end are returned. Otherwise
     * if events occur every X `prop`s then that calculation is used taking into
     * account [[Schedule.maxOccurrences]]. Finally no year rule is specified
     * so worst case is assumed, [[Schedule.maxOccurrences]].
     *
     * The returned value is always rounded up, so if the first and last
     * occurrence happens the same `prop` 1 will be returned.
     */ public getMaximum ( prop ) { if ( ! this . start ) { return - 1 ; } if ( this . end ) { return this . end [ prop ] ; } const occurs = this . maxOccurrences ; const start = this . start [ prop ] ; const frequency = this [ prop ] ; if ( frequency ) { const input = frequency . input ; if ( fn . isFrequencyValueEquals ( input ) ) { return input ; } else if ( fn . isFrequencyValueOneOf ( input ) ) { let max = input [ 0 ] ; for ( let i = 1 ; i < input . length ; i ++ ) { if ( input [ i ] > max ) max = input [ i ] ; } return max ; } if ( occurs > 0 && fn . isFrequencyValueEvery ( input ) ) { const { every , offset } = input ; return start + ( offset || 0 ) + every * occurs ; } } return occurs <= 0 ? - 1 : start + occurs - 1 ; } /**
     * Gets the start date of the schedule. Even if one is not specified, one
     * can be calculated if this is a single event schedule.
     */ public getStart ( ) { if ( this . start ) { return this . start ; } const singleEventSpan = this . getSingleEventSpan ( ) ; if ( singleEventSpan ) { return singleEventSpan . start ; } return null ; } /**
     * Gets the end date of the schedule. Even if one is not specified, one
     * can be calculated if this is a single event schedule.
     */ public getEnd ( ) { if ( this . end ) { return this . end ; } const singleEventSpan = this . getSingleEventSpan ( ) ; if ( singleEventSpan ) { return singleEventSpan . end ; } return null ; } /**
     * Attempts to calculate the entire range of the schedule taking into account
     * any start date, end date, the included and excluded dates, and also even
     * if the start and end date aren't specified, it checks to see if this is a
     * single event schedule. If the start of the result is not defined, that
     * means this schedule has occurred since the beginning of time. If the end
     * of the result is not defined, that means the schedule will occurr until
     * the end of time.
     */ public getRange ( useInclude = true ) { const hasTimes = this . times . length > 0 ; let start = this . getStart ( ) ; let end = this . getEnd ( ) ; if ( useInclude ) { this . include . spans ( ) . each ( ( { span } ) => { if ( ! this . isExcluded ( span . start , hasTimes ) ) { if ( ! start || span . start . isBefore ( start ) ) { start = span . start ; } if ( ! end || span . end . isAfter ( end ) ) { end = span . end ; } } } ) ; } return { start , end } ; } /**
     * Returns an iterator for all occurrences in this schedule. If a finite
     * list of occurrences is not possible to generate, an empty iterator will
     * be returned. A finite set of occurrences can be determine when a start
     * and end date are specified.
     */ public getOccurrences ( ) { const start = this . getStart ( ) ; const end = this . getEnd ( ) ; if ( ! start || ! end ) { return Iterate . empty ( ) ; } const daysBetween = start . daysBetween ( end , Op . CEIL , true ) ; const beforeStart = this . include . spans ( ) . where ( ( { span } ) => span . start . isBefore ( start ) ) . sorted ( ( a , b ) => a . span . start . time - b . span . start . time ) . transform ( ( { span } ) => span ) ; const afterStart = this . forecast ( start , false , daysBetween ) . transform ( ( [ span ] ) => span ) ; const afterEnd = this . include . spans ( ) . where ( ( { span } ) => span . start . isAfter ( end ) ) . sorted ( ( a , b ) => a . span . start . time - b . span . start . time ) . transform ( ( { span } ) => span ) ; return Iterate . join ( beforeStart , afterStart , afterEnd ) ; } param day returns see see public matchesSpan ( day ) { return ( this . start === null || day . isSameOrAfter ( this . start ) ) && ( this . end === null || day . isBefore ( this . end ) ) ; } param start param end returns see see public matchesRange ( start , end ) { if ( this . start && end . isBefore ( this . start ) ) { return false ; } if ( this . end && start . isAfter ( this . end ) ) { return false ; } return true ; } param day param lookAtTime returns public isExcluded ( day , lookAtTime = true ) { return this . exclude . get ( day , false , lookAtTime ) ; } param day param lookAtTime returns public isIncluded ( day , lookAtTime = true ) { return this . include . get ( day , false , lookAtTime ) ; } param day param lookAtTime returns public isCancelled ( day , lookAtTime = true ) { return this . cancel . get ( day , false , lookAtTime ) ; } param day param otherwise param lookAtTime returns public getMeta ( day , otherwise = null , lookAtTime = true ) { return this . meta . get ( day , otherwise , lookAtTime ) ; } param day returns public getMetas ( day ) { return this . meta . getAll ( day ) ; } /**
     * Returns whether the events in the schedule are all day long or start at
     * specific times. Full day events start at the start of the day and end at
     * the start of the next day (if the duration = `1` and durationUnit = 'days').
     * Full day events have no times specified and should have a durationUnit of
     * either `days` or `weeks`.
     */ public isFullDay ( ) { return this . times . length === 0 ; } param fullDay param defaultTime public setFullDay ( fullDay = true , defaultTime = '08:00' ) { if ( fullDay !== this . isFullDay ( ) ) { if ( fullDay ) { this . times = [ ] ; if ( this . durationUnit !== 'day' ) { this . duration = 1 ; this . durationUnit = 'day' ; } } else { this . times = [ Parse . time ( defaultTime ) ] ; if ( this . durationUnit !== 'hour' ) { this . duration = 1 ; this . durationUnit = 'hour' ; } } } return this ; } param addSpan public adjustDefinedSpan ( addSpan = false ) { const single = this . getSingleEventSpan ( ) ; if ( single && ( addSpan || ( this . start && this . end ) ) ) { this . start = single . start . startOf ( 'day' ) ; this . end = single . end . endOf ( 'day' ) ; } return this ; } param day returns public getFullSpan ( day ) { const start = day . startOf ( 'day' ) ; const end = start . add ( this . durationUnit , this . duration ) ; return new DaySpan ( start , end ) ; } param day param time returns public getTimeSpan ( day , time ) { const start = day . withTime ( time ) ; const end = start . add ( this . durationUnit , this . duration ) ; return new DaySpan ( start , end ) ; } param day returns see see see public matchesDay ( day ) { if ( this . isIncluded ( day , false ) ) { return true ; } if ( ! this . matchesSpan ( day ) || this . isFullyExcluded ( day ) ) { return false ; } for ( const check of this . checks ) { if ( ! check ( day [ check . property ] ) ) { return false ; } } return true ; } param day returns public hasIncludedTime ( day ) { return ! this . iterateIncludeTimes ( day ) . empty ( ) ; } param day returns public isFullyExcluded ( day ) { if ( this . exclude . isEmpty ( ) ) { return false ; } if ( this . isExcluded ( day , false ) ) { return true ; } if ( this . isFullDay ( ) ) { return false ; } for ( const time of this . times ) { if ( ! this . isExcluded ( day . withTime ( time ) ) ) { return false ; } } return true ; } param day param includeDay param lookAhead returns public nextDay ( day , includeDay = false , lookAhead = Constants . DAYS_IN_YEAR ) { return this . iterateDaycast ( day , 1 , true , includeDay , lookAhead ) . first ( ) ; } param day param max param includeDay param lookAhead returns public nextDays ( day , max , includeDay = false , lookAhead = Constants . DAYS_IN_YEAR ) { return this . iterateDaycast ( day , max , true , includeDay , lookAhead ) ; } param day param includeDay param lookBack returns public prevDay ( day , includeDay = false , lookBack = Constants . DAYS_IN_YEAR ) { return this . iterateDaycast ( day , 1 , false , includeDay , lookBack ) . first ( ) ; } param day param max param includeDay param lookAhead returns public prevDays ( day , max , includeDay = false , lookBack = Constants . DAYS_IN_YEAR ) { return this . iterateDaycast ( day , max , false , includeDay , lookBack ) ; } param day param max param next param includeDay param lookup returns see public iterateDaycast ( day , max , next , includeDay = false , lookup = Constants . DAYS_IN_YEAR ) { return new Iterate < Day , number , Schedule < M > > ( iterator => { let iterated = 0 ; let acted = 0 ; for ( let days = 0 ; days < lookup ; days ++ ) { if ( ! includeDay || days > 0 ) { day = next ? day . next ( ) : day . prev ( ) ; } if ( ! this . iterateSpans ( day , false ) . empty ( ) ) { const action = iterator . act ( day , acted ++ ) ; if ( action === IterateAction . STOP || ++ iterated >= max ) { return ; } } } } ) ; } param day param covers returns public iterateSpans ( day , covers = false ) { return new Iterate < DaySpan , number , Schedule < M > > ( iterator => { let current = day ; let lookBehind = covers ? this . durationInDays : 0 ; let key = 0 ; if ( this . isFullDay ( ) ) { while ( lookBehind >= 0 ) { if ( this . matchesDay ( current ) ) { const span = this . getFullSpan ( current ) ; if ( span . matchesDay ( day ) ) { switch ( iterator . act ( span , key ++ ) ) { case IterateAction . STOP : return ; } } } current = current . prev ( ) ; lookBehind -- ; } } else { while ( lookBehind >= 0 ) { if ( this . matchesDay ( current ) ) { for ( const time of this . times ) { const span = this . getTimeSpan ( current , time ) ; if ( span . matchesDay ( day ) && ! this . isExcluded ( span . start , true ) ) { switch ( iterator . act ( span , key ++ ) ) { case IterateAction . STOP : return ; } } } } else { this . iterateIncludeTimes ( current , day ) . each ( ( span , i , timeIterator ) => { switch ( iterator . act ( span , key ++ ) ) { case IterateAction . STOP : timeIterator . stop ( ) ; break ; } } ) ; if ( iterator . action === IterateAction . STOP ) { return ; } } lookBehind -- ; if ( lookBehind >= 0 ) { current = current . prev ( ) ; } } } } ) ; } param day returns public matchesTime ( day ) { return ! ! this . iterateSpans ( day , true ) . where ( span => span . start . sameMinute ( day ) ) . first ( ) ; } param day returns public coversDay ( day ) { return ! this . iterateSpans ( day , true ) . empty ( ) ; } param day return public coversTime ( day ) { return ! ! this . iterateSpans ( day , true ) . where ( span => span . contains ( day ) ) . first ( ) ; } param property param frequency public setFrequency ( property , frequency ? ) { this [ property ] = Parse . frequency ( frequency , property ) ; return this ; } param time param excluded public setExcluded ( time , excluded = true ) { const type = this . identifierType ; this . exclude . set ( time , excluded , type ) ; this . include . set ( time , ! excluded , type ) ; return this ; } param time param cancelled public setCancelled ( time , cancelled = true ) { this . cancel . set ( time , cancelled , this . identifierType ) ; return this ; } param time param removeInclude returns public removeTime ( time , removeInclude = true ) { let found = false ; for ( let i = 0 ; i < this . times . length && ! found ; i ++ ) { found = time . matches ( this . times [ i ] ) ; if ( found ) { this . times . splice ( i , 1 ) ; } } if ( found ) { if ( removeInclude ) { this . include . removeTime ( time ) ; } this . exclude . removeTime ( time ) ; this . cancel . removeTime ( time ) ; this . meta . removeTime ( time ) ; } return found ; } param toTime param fromTime returns public move ( toTime , fromTime ? , meta ? ) { if ( ! this . moveSingleEvent ( toTime ) && fromTime ) { return this . moveInstance ( fromTime , toTime ) ; } return false ; } param fromTime param toTime returns public moveTime ( fromTime , toTime ) { let found = false ; for ( let i = 0 ; i < this . times . length && ! found ; i ++ ) { found = fromTime . matches ( this . times [ i ] ) ; if ( found ) { this . times . splice ( i , 1 , toTime ) ; } } if ( found ) { this . include . moveTime ( fromTime , toTime ) ; this . exclude . moveTime ( fromTime , toTime ) ; this . cancel . moveTime ( fromTime , toTime ) ; this . meta . moveTime ( fromTime , toTime ) ; this . adjustDefinedSpan ( false ) ; } return found ; } param fromTime param toTime returns see public moveInstance ( fromTime , toTime ) { const type = this . identifierType ; this . exclude . set ( fromTime , true , type ) ; this . exclude . set ( toTime , false , type ) ; this . include . set ( toTime , true , type ) ; this . include . set ( fromTime , false , type ) ; if ( this . cancel . get ( fromTime , false ) && ! this . cancel . get ( toTime , false ) ) { this . cancel . set ( toTime , true , type ) ; if ( this . cancel . getIdentifier ( fromTime ) === type ) { this . cancel . unset ( fromTime , type ) ; } } const meta = this . meta . get ( fromTime , null ) ; if ( meta && meta !== this . meta . get ( toTime , null ) ) { this . meta . set ( toTime , meta , type ) ; if ( this . meta . getIdentifier ( fromTime ) === type ) { this . meta . unset ( fromTime , type ) ; } } return true ; } param toTime param takeTime returns see public moveSingleEvent ( toTime , takeTime = true ) { if ( ! this . isSingleEvent ( ) ) { return false ; } for ( const check of this . checks ) { const prop = check . property ; const value = toTime [ prop ] ; const frequency = Parse . frequency ( [ value ] , prop ) ; this [ prop ] = frequency ; } if ( this . times . length === 1 && takeTime ) { this . times = [ toTime . asTime ( ) ] ; } this . updateChecks ( ) ; const span = this . getSingleEventSpan ( ) ; if ( this . start ) { this . start = span . start . startOf ( 'day' ) ; } if ( this . end ) { this . end = span . end . endOf ( 'day' ) ; } this . updateEnd ( ) ; return true ; } returns see public getSingleEventSpan ( ) { if ( ! this . isSingleEvent ( ) ) { return null ; } const startOfYear = Day . build ( this . year . input [ 0 ] , 0 , 1 ) ; const start = this . iterateDaycast ( startOfYear , 1 , true , true , Constants . DAYS_IN_YEAR ) . first ( ) ; if ( ! start ) { return null ; } return this . isFullDay ( ) ? this . getFullSpan ( start ) : this . getTimeSpan ( start , this . times [ 0 ] ) ; } returns public isSingleEvent ( ) { if ( this . times . length > 1 ) { return false ; } if ( ! this . include . isEmpty ( ) ) { return false ; } if ( ! this . isSingleYear ( ) ) { return false ; } if ( this . isSingleDayOfYear ( ) ) { return true ; } if ( this . isSingleMonth ( ) && this . isSingleDayOfMonth ( ) ) { return true ; } if ( this . isSingleMonth ( ) && this . isSingleWeekOfMonth ( ) && this . isSingleDayOfWeek ( ) ) { return true ; } if ( this . isSingleWeekOfYear ( ) && this . isSingleDayOfWeek ( ) ) { return true ; } return false ; } returns see public isSingleYear ( ) { return this . isSingleFrequency ( this . year ) ; } returns see public isSingleMonth ( ) { return this . isSingleFrequency ( this . month ) ; } returns see see public isSingleDayOfMonth ( ) { return this . isSingleFrequency ( this . dayOfMonth ) || this . isSingleFrequency ( this . lastDayOfMonth ) ; } returns see see public isSingleDayOfWeek ( ) { return this . isSingleFrequency ( this . dayOfWeek ) || this . isSingleFrequency ( this . day ) ; } returns see public isSingleDayOfYear ( ) { return this . isSingleFrequency ( this . dayOfYear ) ; } returns see see see see see public isSingleWeekOfMonth ( ) { return this . isSingleFrequency ( this . weekspanOfMonth ) || this . isSingleFrequency ( this . fullWeekOfMonth ) || this . isSingleFrequency ( this . weekOfMonth ) || this . isSingleFrequency ( this . lastFullWeekOfMonth ) || this . isSingleFrequency ( this . lastWeekspanOfMonth ) ; } returns see see see see see see public isSingleWeekOfYear ( ) { return this . isSingleFrequency ( this . weekspanOfYear ) || this . isSingleFrequency ( this . fullWeekOfYear ) || this . isSingleFrequency ( this . week ) || this . isSingleFrequency ( this . weekOfYear ) || this . isSingleFrequency ( this . lastFullWeekOfYear ) || this . isSingleFrequency ( this . lastWeekspanOfYear ) ; } returns public isSingleFrequency ( frequency ) { return ( fn . isFrequencyValueOneOf ( frequency . input ) && frequency . input . length === 1 ) || fn . isFrequencyValueEquals ( frequency . input ) ; } param around param covers param daysAfter param daysBefore param times param lookAround returns public forecast ( around , covers = true , daysAfter , daysBefore = daysAfter , times = false , lookAround = Constants . DAYS_IN_YEAR ) { const type = this . identifierType ; let tupleIndex = 0 ; const tuplesForDay = ( day , tuples ) : boolean => { const spans = this . iterateSpans ( day , covers ) . array ( ) ; const last = times ? spans . length : Math . min ( 1 , spans . length ) ; const offset = times ? 0 : spans . length - 1 ; for ( let i = 0 ; i < last ; i ++ ) { const span = spans [ i + offset ] ; const id = type . get ( span . start ) ; if ( tuples . act ( [ span , day , id ] , tupleIndex ++ ) === IterateAction . STOP ) { return false ; } } return true ; } ; const prev = new Iterate < ScheduleEventTuple , number , Schedule < M > > ( iterator => { let curr = around ; for ( let i = 0 ; i < lookAround ; i ++ ) { if ( ! tuplesForDay ( curr , iterator ) ) { break ; } curr = curr . prev ( ) ; } } ) ; const next = new Iterate < ScheduleEventTuple , number , Schedule < M > > ( iterator => { let curr = around ; for ( let i = 0 ; i < lookAround ; i ++ ) { curr = curr . next ( ) ; if ( ! tuplesForDay ( curr , iterator ) ) { break ; } } } ) ; return prev . take ( daysBefore + 1 ) . reverse ( ) . append ( next . take ( daysAfter ) ) ; } param day param matchAgainst returns public iterateIncludeTimes ( day , matchAgainst = day ) { const isIncludedTime = ( result ) => { const [ id , included ] = result ; return included && Identifier . Time . is ( id ) ; } ; const getSpan = ( result ) => { const [ id ] = result ; const time = Identifier . Time . start ( id ) ; const span = this . getTimeSpan ( time , time . asTime ( ) ) ; if ( span . matchesDay ( matchAgainst ) ) { return span ; } } ; return this . include . query ( day . dayIdentifier ) . where ( isIncludedTime ) . transform < DaySpan > ( getSpan ) ; } returns public clone ( ) { return new Schedule < M > ( this . toInput ( ) ) ; } param returnDays param returnTimes param timeFormat param alwaysDuration param alwaysReturnEnd returns see public toInput ( returnDays = false , returnTimes = false , timeFormat = '' , alwaysDuration = false , alwaysReturnEnd = false ) { const defaultUnit = Constants . DURATION_DEFAULT_UNIT ( this . isFullDay ( ) ) ; const exclusions = this . exclude . identifiers ( v => v ) . array ( ) ; const inclusions = this . include . identifiers ( v => v ) . array ( ) ; const cancels = this . cancel . identifiers ( v => v ) . array ( ) ; const hasMeta = ! this . meta . isEmpty ( ) ; const out = { } ; const times = [ ] ; for ( const time of this . times ) { times . push ( returnTimes ? time : ( timeFormat ? time . format ( timeFormat ) : time . toString ( ) ) ) ; } if ( this . start ) out . start = returnDays ? this . start : this . start . time ; if ( this . end && ( alwaysReturnEnd || this . maxOccurrences < 1 ) ) out . end = returnDays ? this . end : this . end . time ; if ( this . maxOccurrences > 0 ) out . maxOccurrences = this . maxOccurrences ; if ( times . length ) out . times = times ; if ( alwaysDuration || this . duration !== Constants . DURATION_DEFAULT ) out . duration = this . duration ; if ( alwaysDuration || this . durationUnit !== defaultUnit ) out . durationUnit = this . durationUnit ; if ( exclusions . length ) out . exclude = exclusions ; if ( inclusions . length ) out . include = inclusions ; if ( cancels . length ) out . cancel = cancels ; if ( hasMeta ) out . meta = fn . extend ( { } , this . meta . map ) ; if ( this . dayOfWeek . input ) out . dayOfWeek = this . dayOfWeek . input ; if ( this . dayOfMonth . input ) out . dayOfMonth = this . dayOfMonth . input ; if ( this . lastDayOfMonth . input ) out . lastDayOfMonth = this . lastDayOfMonth . input ; if ( this . dayOfYear . input ) out . dayOfYear = this . dayOfYear . input ; if ( this . year . input ) out . year = this . year . input ; if ( this . month . input ) out . month = this . month . input ; if ( this . day . input ) out . day = this . day . input ; if ( this . week . input ) out . week = this . week . input ; if ( this . weekOfYear . input ) out . weekOfYear = this . weekOfYear . input ; if ( this . weekspanOfYear . input ) out . weekspanOfYear = this . weekspanOfYear . input ; if ( this . fullWeekOfYear . input ) out . fullWeekOfYear = this . fullWeekOfYear . input ; if ( this . lastWeekspanOfYear . input ) out . lastWeekspanOfYear = this . lastWeekspanOfYear . input ; if ( this . lastFullWeekOfYear . input ) out . lastFullWeekOfYear = this . lastFullWeekOfYear . input ; if ( this . weekOfMonth . input ) out . weekOfMonth = this . weekOfMonth . input ; if ( this . weekspanOfMonth . input ) out . weekspanOfMonth = this . weekspanOfMonth . input ; if ( this . fullWeekOfMonth . input ) out . fullWeekOfMonth = this . fullWeekOfMonth . input ; if ( this . lastWeekspanOfMonth . input ) out . lastWeekspanOfMonth = this . lastWeekspanOfMonth . input ; if ( this . lastFullWeekOfMonth . input ) out . lastFullWeekOfMonth = this . lastFullWeekOfMonth . input ; return out ; } param thing param includeRange param includeTimes param includeDuration param includeExcludes param includeIncludes param includeCancels returns public describe ( thing = 'event' , includeRange = true , includeTimes = true , includeDuration = false , includeExcludes = false , includeIncludes = false , includeCancels = false ) { const lang = Locales . current ; let out = '' ; if ( includeRange ) { if ( this . start ) { out += lang . scheduleStartingOn ( this . start ) ; if ( this . end ) { out += lang . scheduleEndingOn ( this . end ) ; } } else if ( this . end ) { out += lang . scheduleEndsOn ( this . end ) ; } } out += lang . scheduleThing ( thing , ! out ) ; out += this . describeRule ( this . dayOfWeek . input , lang . ruleDayOfWeek ) ; out += this . describeRule ( this . day . input , lang . ruleDay ) ; out += this . describeRule ( this . lastDayOfMonth . input , lang . ruleLastDayOfMonth ) ; out += this . describeRule ( this . dayOfMonth . input , lang . ruleDayOfMonth ) ; out += this . describeRule ( this . dayOfYear . input , lang . ruleDayOfYear ) ; out += this . describeRule ( this . year . input , lang . ruleYear ) ; out += this . describeRule ( this . month . input , lang . ruleMonth ) ; out += this . describeRule ( this . week . input , lang . ruleWeek ) ; out += this . describeRule ( this . weekOfYear . input , lang . ruleWeekOfYear ) ; out += this . describeRule ( this . weekspanOfYear . input , lang . ruleWeekspanOfYear ) ; out += this . describeRule ( this . fullWeekOfYear . input , lang . ruleFullWeekOfYear ) ; out += this . describeRule ( this . lastWeekspanOfYear . input , lang . ruleLastWeekspanOfYear ) ; out += this . describeRule ( this . lastFullWeekOfYear . input , lang . ruleLastFullWeekOfYear ) ; out += this . describeRule ( this . weekOfMonth . input , lang . ruleWeekOfMonth ) ; out += this . describeRule ( this . fullWeekOfMonth . input , lang . ruleFullWeekOfMonth ) ; out += this . describeRule ( this . weekspanOfMonth . input , lang . ruleWeekspanOfMonth ) ; out += this . describeRule ( this . lastFullWeekOfMonth . input , lang . ruleLastFullWeekOfMonth ) ; out += this . describeRule ( this . lastWeekspanOfMonth . input , lang . ruleLastWeekspanOfMonth ) ; if ( includeTimes && this . times . length ) { out += lang . scheduleAtTimes ; out += this . describeArray ( this . times , x => x . format ( 'hh:mm a' ) ) ; } if ( includeDuration && this . duration !== Constants . DURATION_DEFAULT ) { out += lang . scheduleDuration ( this . duration , this . durationUnit ) ; } if ( includeExcludes ) { const excludes = this . exclude . spans ( ) . array ( ) ; if ( excludes . length ) { out += lang . scheduleExcludes ; out += this . describeArray ( excludes , x => x . span . summary ( Units . DAY ) ) ; } } if ( includeIncludes ) { const includes = this . include . spans ( ) . array ( ) ; if ( includes . length ) { out += lang . scheduleIncludes ; out += this . describeArray ( includes , x => x . span . summary ( Units . DAY ) ) ; } } if ( includeCancels ) { const cancels = this . cancel . spans ( ) . array ( ) ; if ( cancels . length ) { out += lang . scheduleCancels ; out += this . describeArray ( cancels , x => x . span . summary ( Units . DAY ) ) ; } } return out ; } param value param localeRule returns private describeRule ( value , localeRule ) { let out = '' ; if ( fn . isFrequencyValueEvery ( value ) ) { out += ' ' + localeRule . every ( value . every ) ; if ( value . offset ) { out += ' ' + localeRule . offset ( value . offset ) ; } } else if ( fn . isFrequencyValueOneOf ( value ) ) { if ( value . length ) { out += ' ' + localeRule . oneOf ( value ) ; } } else if ( fn . isFrequencyValueEquals ( value ) ) { out += ' ' + localeRule . oneOf ( [ value ] ) ; } return out ; } param array param map returns private describeArray < T > ( array , map ) { return Locales . current . list ( array . map ( map ) ) ; } param input param days returns public static forDay < M > ( input , days = 1 ) { const day = Day . parse ( input ) ; if ( ! day ) { return null ; } return new Schedule < M > ( { year : [ day . year ] , month : [ day . month ] , dayOfMonth : [ day . dayOfMonth ] , duration : days , durationUnit : 'day' } ) ; } param input param time param duration param durationUnit returns public static forTime < M > ( input , time , duration = 1 , durationUnit = 'hour' ) { const day = Day . parse ( input ) ; if ( ! day ) { return null ; } return new Schedule < M > ( { year : [ day . year ] , month : [ day . month ] , dayOfMonth : [ day . dayOfMonth ] , times : [ time ] , duration , durationUnit } ) ; } param span returns public static forSpan < M > ( span ) { const start = span . start ; const minutes = span . minutes ( ) ; const isDay = minutes % Constants . MINUTES_IN_DAY === 0 ; const isHour = minutes % Constants . MINUTES_IN_HOUR === 0 ; const duration = isDay ? minutes / Constants . MINUTES_IN_DAY : ( isHour ? minutes / Constants . MINUTES_IN_HOUR : minutes ) ; const durationUnit = isDay ? 'day' : ( isHour ? 'hour' : 'minute' ) ; return this . forTime < M > ( start , start . asTime ( ) , duration , durationUnit ) ; } }