UNPKG

@syncfusion/ej2-schedule

Version:

Flexible scheduling library with more built-in features and enhanced customization options similar to outlook and google calendar, allowing the users to plan and manage their appointments with efficient data-binding support.

1,057 lines (1,032 loc) 72.9 kB
/* eslint-disable max-len */ import { isNullOrUndefined, L10n, getDefaultDateObject, getValue, cldrData } from '@syncfusion/ej2-base'; import { MS_PER_DAY, addDays, resetTime, capitalizeFirstWord } from '../schedule/base/util'; import { CalendarUtil, Islamic, Gregorian, CalendarType } from '../common/calendar-util'; import { Timezone } from '../schedule/timezone/timezone'; /** * Date Generator from Recurrence Rule */ /** * Generate Summary from Recurrence Rule * * @param {string} rule Accepts the Recurrence rule * @param {L10n} localeObject Accepts the locale object * @param {string} locale Accepts the locale name * @param {CalendarType} calendarType Accepts the calendar type * @returns {string} Returns the summary string from given recurrence rule */ export function generateSummary(rule: string, localeObject: L10n, locale: string, calendarType: CalendarType = 'Gregorian'): string { const ruleObject: RecRule = extractObjectFromRule(rule); let summary: string = localeObject.getConstant(EVERY) + ' '; let cldrObj: string[]; let cldrObj1: string[]; const calendarMode: string = calendarType.toLowerCase(); if (locale === 'en' || locale === 'en-US') { const nameSpace1: string = 'months.stand-alone.abbreviated'; const nameSpace: string = 'days.stand-alone.abbreviated'; cldrObj1 = <string[]>(getValue(nameSpace1, getDefaultDateObject(calendarMode))); cldrObj = <string[]>(getValue(nameSpace, getDefaultDateObject(calendarMode))); } else { const nameSpace1: string = 'main.' + locale + '.dates.calendars.' + calendarMode + '.months.stand-alone.abbreviated'; const nameSpace: string = 'main.' + locale + '.dates.calendars.' + calendarMode + '.days.stand-alone.abbreviated'; cldrObj1 = <string[]>(getValue(nameSpace1, cldrData)); cldrObj = <string[]>(getValue(nameSpace, cldrData)); } if (ruleObject.interval > 1) { summary += ruleObject.interval + ' '; } switch (ruleObject.freq) { case 'DAILY': summary += localeObject.getConstant(DAYS); break; case 'WEEKLY': summary += localeObject.getConstant(WEEKS) + ' ' + localeObject.getConstant(ON) + ' '; ruleObject.day.forEach((day: string, index: number) => { summary += capitalizeFirstWord(<string>getValue(DAYINDEXOBJECT[`${day}`], cldrObj), 'single'); summary += (((ruleObject.day.length - 1) === index) ? '' : ', '); }); break; case 'MONTHLY': summary += localeObject.getConstant(MONTHS) + ' ' + localeObject.getConstant(ON) + ' '; summary += getMonthSummary(ruleObject, cldrObj, localeObject); break; case 'YEARLY': summary += localeObject.getConstant(YEARS) + ' ' + localeObject.getConstant(ON) + ' '; summary += capitalizeFirstWord(<string>getValue((ruleObject.month[0]).toString(), cldrObj1), 'single') + ' '; summary += getMonthSummary(ruleObject, cldrObj, localeObject); break; } if (ruleObject.count) { summary += ', ' + (ruleObject.count) + ' ' + localeObject.getConstant(TIMES); } else if (ruleObject.until) { const tempDate: Date = ruleObject.until; summary += ', ' + localeObject.getConstant(UNTIL) + ' ' + tempDate.getDate() + ' ' + capitalizeFirstWord(<string>getValue((tempDate.getMonth() + 1).toString(), cldrObj1), 'single') + ' ' + tempDate.getFullYear(); } return summary; } /** * Generates Month summary * * @param {RecRule} ruleObject Accepts the recurrence rule object * @param {string[]} cldrObj Accepts the collections of month name from calendar * @param {L10n} localeObj Accepts the locale object * @returns {string} Returns the month summary string from given recurrence rule object * @private */ function getMonthSummary(ruleObject: RecRule, cldrObj: string[], localeObj: L10n): string { let summary: string = ''; if (ruleObject.monthDay.length) { summary += ruleObject.monthDay[0]; } else if (ruleObject.day) { const pos: number = ruleObject.setPosition - 1; summary += localeObj.getConstant(WEEKPOS[pos > -1 ? pos : (WEEKPOS.length - 1)]) + ' ' + capitalizeFirstWord(<string>getValue(DAYINDEXOBJECT[ruleObject.day[0]], cldrObj), 'single'); } return summary; } /** * Generates the date collections from the given recurrence rule * * @param {Date} startDate Accepts the rule start date * @param {string} rule Accepts the recurrence rule * @param {string} excludeDate Accepts the exception dates in string format * @param {number} startDayOfWeek Accepts the start day index of week * @param {number} maximumCount Accepts the maximum number count to generate date collections * @param {Date} viewDate Accepts the current date instead of start date * @param {CalendarType} calendarMode Accepts the calendar type * @param {string} newTimezone Accepts the timezone name * @returns {number[]} Returns the collection of dates */ export function generate(startDate: Date, rule: string, excludeDate: string, startDayOfWeek: number, maximumCount: number = MAXOCCURRENCE, viewDate: Date = null, calendarMode: CalendarType = 'Gregorian', newTimezone: string = null): number[] { const ruleObject: RecRule = extractObjectFromRule(rule); let cacheDate: Date; calendarUtil = getCalendarUtil(calendarMode); const data: number[] = []; const modifiedDate: Date = new Date(startDate.getTime()); tempExcludeDate = []; const tempDate: string[] = isNullOrUndefined(excludeDate) ? [] : excludeDate.split(','); const tz: Timezone = new Timezone(); tempDate.forEach((content: string) => { let parsedDate: Date = getDateFromRecurrenceDateString(content); if (newTimezone) { parsedDate = tz.add(new Date(parsedDate.getTime()), newTimezone); } tempExcludeDate.push(new Date(parsedDate.getTime()).setHours(0, 0, 0, 0)); }); ruleObject.recExceptionCount = !isNullOrUndefined(ruleObject.count) ? tempExcludeDate.length : 0; if (viewDate && viewDate > startDate && !ruleObject.count) { tempViewDate = new Date(new Date(viewDate.getTime()).setHours(0, 0, 0)); } else { tempViewDate = null; } if (!ruleObject.until && tempViewDate) { cacheDate = new Date(tempViewDate.getTime()); cacheDate.setDate(tempViewDate.getDate() + maximumCount * (ruleObject.interval)); ruleObject.until = cacheDate; } if (ruleObject.until && startDate > ruleObject.until) { return data; } maxOccurrence = maximumCount; startDayOfWeek = startDayOfWeek || 0; setFirstDayOfWeek(DAYINDEX[parseInt(startDayOfWeek.toString(), 10)]); if (ruleObject.until) { const end: Date = resetTime(ruleObject.until); ruleObject.until = new Date(end.getFullYear(), end.getMonth(), end.getDate(), 23, 59, 59); } switch (ruleObject.freq) { case 'DAILY': dailyType(modifiedDate, ruleObject.until, data, ruleObject); break; case 'WEEKLY': weeklyType(modifiedDate, ruleObject.until, data, ruleObject, startDayOfWeek); break; case 'MONTHLY': monthlyType(modifiedDate, ruleObject.until, data, ruleObject); break; case 'YEARLY': yearlyType(modifiedDate, ruleObject.until, data, ruleObject); } return data; } /** * Generate date object from given date string * * @param {string} recDateString Accepts the exception date as string * @returns {Date} Returns the date from exception date string */ export function getDateFromRecurrenceDateString(recDateString: string): Date { return new Date(recDateString.substr(0, 4) + '-' + recDateString.substr(4, 2) + '-' + recDateString.substr(6, 5) + ':' + recDateString.substr(11, 2) + ':' + recDateString.substr(13)); } /** * Internal method to handle exclude date * * @param {number[]} data Accepts the exception date collections * @param {number} date Accepts the new exclude date * @returns {void} * @private */ function excludeDateHandler(data: number[], date: number): void { const zeroIndex: number = new Date(date).setHours(0, 0, 0, 0); if (tempExcludeDate.indexOf(zeroIndex) === -1 && (!tempViewDate || zeroIndex >= tempViewDate.getTime())) { data.push(date); } } /** * Internal method for get date count * * @param {Date} startDate Accepts the date * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {number} Returns the number of date count * @private */ function getDateCount(startDate: Date, ruleObject: RecRule): number { let count: number = maxOccurrence; if (ruleObject.count) { count = ruleObject.count; } else if (ruleObject.until) { if (ruleObject.freq === 'DAILY' || ruleObject.freq === 'WEEKLY') { count = Math.floor((ruleObject.until.getTime() - startDate.getTime()) / MS_PER_DAY) + 1; } else if (ruleObject.freq === 'MONTHLY' || ruleObject.freq === 'YEARLY') { count = Math.floor(((ruleObject.until.getMonth() + 12 * ruleObject.until.getFullYear()) - (startDate.getMonth() + 12 * startDate.getFullYear())) / ruleObject.interval) + (ruleObject.day.length > 1 ? (Math.floor((ruleObject.until.getTime() - startDate.getTime()) / MS_PER_DAY) + 1) : 1); if (ruleObject.freq === 'YEARLY') { count = ruleObject.month.length > 1 ? ((count as number) * ruleObject.month.length) : count; } } } return count; } /** * Internal method for daily type recurrence rule * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function dailyType(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { const tempDate: Date = new Date(startDate.getTime()); const interval: number = ruleObject.interval; const expectedCount: number = getDateCount(startDate, ruleObject); let state: boolean; const expectedDays: string[] = ruleObject.day; while (compareDates(tempDate, endDate)) { state = true; state = validateRules(tempDate, ruleObject); if (state && (expectedDays.indexOf(DAYINDEX[tempDate.getDay()]) > -1 || expectedDays.length === 0)) { excludeDateHandler(data, tempDate.getTime()); if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { break; } } tempDate.setDate(tempDate.getDate() + interval); if (tempDate.getHours() !== startDate.getHours()) { tempDate.setHours(startDate.getHours()); } } } /** * Internal method for weekly type recurrence rule * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @param {number} startDayOfWeek Accepts the start day index of week * @returns {void} * @private */ function weeklyType(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule, startDayOfWeek: number): void { let tempDate: Date = new Date(startDate.getTime()); if (!ruleObject.day.length) { ruleObject.day.push(DAYINDEX[startDate.getDay()]); } const interval: number = ruleObject.interval; const expectedDays: string[] = ruleObject.day; const expectedCount: number = getDateCount(startDate, ruleObject); let weekState: boolean = true; let wkstIndex: number; let weekCollection: number[][] = []; if (expectedDays.length > 1) { if (isNullOrUndefined(ruleObject.wkst) || ruleObject.wkst === '') { ruleObject.wkst = dayIndex[0]; } wkstIndex = DAYINDEX.indexOf(ruleObject.wkst); while (compareDates(tempDate, endDate)) { let startDateDiff: number = DAYINDEX.indexOf(DAYINDEX[tempDate.getDay()]) - wkstIndex; startDateDiff = startDateDiff === -1 ? 6 : startDateDiff; const weekstartDate: Date = addDays(tempDate, -startDateDiff); let weekendDate: Date = addDays(weekstartDate, 6); let compareTempDate: Date = new Date(tempDate.getTime()); weekendDate = resetTime(weekendDate); compareTempDate = resetTime(compareTempDate); while (weekendDate >= compareTempDate) { if (expectedDays.indexOf(DAYINDEX[tempDate.getDay()]) > -1) { weekCollection.push([tempDate.getTime()]); } if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { break; } tempDate.setDate(tempDate.getDate() + 1); if (tempDate.getHours() !== startDate.getHours()) { tempDate.setHours(startDate.getHours()); } compareTempDate = new Date(tempDate.getTime()); compareTempDate = resetTime(compareTempDate); } tempDate.setDate(tempDate.getDate() - 1); if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { break; } tempDate.setDate((tempDate.getDate()) + 1 + ((interval - 1) * 7)); insertDataCollection(weekCollection, weekState, startDate, endDate, data, ruleObject); weekCollection = []; } } else { tempDate = getStartDateForWeek(startDate, ruleObject.day); if (interval > 1 && dayIndex.indexOf(ruleObject.day[0]) < (startDate.getDay() - startDayOfWeek)) { tempDate.setDate(tempDate.getDate() + ((interval - 1) * 7)); } while (compareDates(tempDate, endDate)) { weekState = validateRules(tempDate, ruleObject); if (weekState && (expectedDays.indexOf(DAYINDEX[tempDate.getDay()]) > -1)) { excludeDateHandler(data, tempDate.getTime()); } if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { break; } tempDate.setDate(tempDate.getDate() + (interval * 7)); } insertDataCollection(weekCollection, weekState, startDate, endDate, data, ruleObject); weekCollection = []; } } /** * Internal method for monthly type recurrence rule * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function monthlyType(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { // Set monthday value if BYDAY, BYMONTH and Month day property is not set based on start date if (!ruleObject.month.length && !ruleObject.day.length && !ruleObject.monthDay.length) { ruleObject.monthDay.push(startDate.getDate()); if (ruleObject.freq === 'YEARLY') { ruleObject.month.push(startDate.getMonth() + 1); } } else if (ruleObject.month.length > 0 && !ruleObject.day.length && !ruleObject.monthDay.length) { ruleObject.monthDay.push(startDate.getDate()); } const ruleType: MonthlyType = validateMonthlyRuleType(ruleObject); switch (ruleType) { case 'day': switch (ruleObject.freq) { case 'MONTHLY': monthlyDayTypeProcessforMonthFreq(startDate, endDate, data, ruleObject); break; case 'YEARLY': monthlyDayTypeProcess(startDate, endDate, data, ruleObject); break; } break; case 'both': case 'date': switch (ruleObject.freq) { case 'MONTHLY': monthlyDateTypeProcessforMonthFreq(startDate, endDate, data, ruleObject); break; case 'YEARLY': monthlyDateTypeProcess(startDate, endDate, data, ruleObject); break; } break; } } /** * Internal method for yearly type recurrence rule * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function yearlyType(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { const typeValue: YearRuleType = checkYearlyType(ruleObject); switch (typeValue) { case 'MONTH': monthlyType(startDate, endDate, data, ruleObject); break; case 'WEEKNO': processWeekNo(startDate, endDate, data, ruleObject); break; case 'YEARDAY': processYearDay(startDate, endDate, data, ruleObject); break; } } /** * Internal method for process week no * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function processWeekNo(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { let stDate: Date = calendarUtil.getYearLastDate(startDate, 0); let tempDate: Date; const expectedCount: number = getDateCount(startDate, ruleObject); let state: boolean; let startDay: number; let firstWeekSpan: number; const weekNos: number[] = ruleObject.weekNo; let weekNo: number; let maxDate: number; let minDate: number; let weekCollection: number[][] = []; const expectedDays: string[] = ruleObject.day; while (compareDates(stDate, endDate)) { startDay = dayIndex.indexOf(DAYINDEX[stDate.getDay()]); firstWeekSpan = (6 - startDay) + 1; for (let index: number = 0; index < weekNos.length; index++) { weekNo = weekNos[parseInt(index.toString(), 10)]; weekNo = (weekNo > 0) ? weekNo : 53 + weekNo + 1; maxDate = (weekNo === 1) ? firstWeekSpan : firstWeekSpan + ((weekNo - 1) * 7); minDate = (weekNo === 1) ? firstWeekSpan - 7 : firstWeekSpan + ((weekNo - 2) * 7); while (minDate < maxDate) { tempDate = new Date(stDate.getTime() + (MS_PER_DAY * minDate)); if (expectedDays.length === 0 || expectedDays.indexOf(DAYINDEX[tempDate.getDay()]) > -1) { if (isNullOrUndefined(ruleObject.setPosition)) { insertDateCollection(state, startDate, endDate, data, ruleObject, tempDate.getTime()); } else { weekCollection.push([tempDate.getTime()]); } } minDate++; } } if (!isNullOrUndefined(ruleObject.setPosition)) { insertDatasIntoExistingCollection(weekCollection, state, startDate, endDate, data, ruleObject); } if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { return; } stDate = calendarUtil.getYearLastDate(tempDate, ruleObject.interval); weekCollection = []; } } /** * Internal method for process year day * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function processYearDay(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { let stDate: Date = calendarUtil.getYearLastDate(startDate, 0); let tempDate: Date; const expectedCount: number = getDateCount(startDate, ruleObject); let state: boolean; let dateCollection: number[][] = []; let date: number; const expectedDays: string[] = ruleObject.day; while (compareDates(stDate, endDate)) { for (let index: number = 0; index < ruleObject.yearDay.length; index++) { date = ruleObject.yearDay[parseInt(index.toString(), 10)]; tempDate = new Date(stDate.getTime()); if ((date === calendarUtil.getLeapYearDaysCount() || date === -calendarUtil.getLeapYearDaysCount()) && (!calendarUtil.isLeapYear(calendarUtil.getFullYear(tempDate), 1))) { tempDate.setDate(tempDate.getDate() + 1); continue; } tempDate.setDate(tempDate.getDate() + ((date < 0) ? calendarUtil.getYearDaysCount(tempDate, 1) + 1 + date : date)); if (expectedDays.length === 0 || expectedDays.indexOf(DAYINDEX[tempDate.getDay()]) > -1) { if (ruleObject.setPosition == null) { insertDateCollection(state, startDate, endDate, data, ruleObject, tempDate.getTime()); } else { dateCollection.push([tempDate.getTime()]); } } } if (!isNullOrUndefined(ruleObject.setPosition)) { insertDatasIntoExistingCollection(dateCollection, state, startDate, endDate, data, ruleObject); } if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { return; } stDate = calendarUtil.getYearLastDate(tempDate, ruleObject.interval); dateCollection = []; } } /** * Internal method to check yearly type * * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {YearRuleType} Returns the Yearly rule type object * @private */ function checkYearlyType(ruleObject: RecRule): YearRuleType { if (ruleObject.yearDay.length) { return 'YEARDAY'; } else if (ruleObject.weekNo.length) { return 'WEEKNO'; } return 'MONTH'; } /** * Internal method to initialize recurrence rule variables * * @param {Date} startDate Accepts the start date * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {RuleData} Return the rule data object * @private */ function initializeRecRuleVariables(startDate: Date, ruleObject: RecRule): RuleData { const ruleData: RuleData = { monthCollection: [], index: 0, tempDate: new Date(startDate.getTime()), mainDate: new Date(startDate.getTime()), expectedCount: getDateCount(startDate, ruleObject), monthInit: 0, dateCollection: [] }; if (ruleObject.month.length) { calendarUtil.setMonth(ruleData.tempDate, ruleObject.month[0], ruleData.tempDate.getDate()); } return ruleData; } /** * Internal method for process monthly date type recurrence rule * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function monthlyDateTypeProcess(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { if (ruleObject.month.length) { monthlyDateTypeProcessforMonthFreq(startDate, endDate, data, ruleObject); return; } const ruleData: RuleData = initializeRecRuleVariables(startDate, ruleObject); let currentMonthDate: Date; ruleData.tempDate = ruleData.mainDate = calendarUtil.getMonthStartDate(ruleData.tempDate); while (compareDates(ruleData.tempDate, endDate)) { currentMonthDate = new Date(ruleData.tempDate.getTime()); while (calendarUtil.isSameYear(currentMonthDate, ruleData.tempDate) && (ruleData.expectedCount && (data.length + ruleObject.recExceptionCount) <= ruleData.expectedCount)) { if (ruleObject.month.length === 0 || (ruleObject.month.length > 0 && !calendarUtil.checkMonth(ruleData.tempDate, ruleObject.month))) { processDateCollectionForByMonthDay(ruleObject, ruleData, endDate, false); ruleData.beginDate = new Date(ruleData.tempDate.getTime()); ruleData.monthInit = setNextValidDate( ruleData.tempDate, ruleObject, ruleData.monthInit, ruleData.beginDate); } else { calendarUtil.setValidDate(ruleData.tempDate, 1, 1); ruleData.tempDate = getStartDateForWeek(ruleData.tempDate, ruleObject.day); break; } } ruleData.tempDate.setFullYear(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), currentMonthDate.getDate()); insertDataCollection(ruleData.dateCollection, ruleData.state, startDate, endDate, data, ruleObject); if (calendarUtil.isLastMonth(ruleData.tempDate)) { calendarUtil.setValidDate(ruleData.tempDate, 1, 1); ruleData.tempDate = getStartDateForWeek(ruleData.tempDate, ruleObject.day); } if (ruleData.expectedCount && (data.length + ruleObject.recExceptionCount) >= ruleData.expectedCount) { return; } ruleData.tempDate.setFullYear(ruleData.tempDate.getFullYear() + ruleObject.interval - 1); ruleData.tempDate = getStartDateForWeek(ruleData.tempDate, ruleObject.day); ruleData.monthInit = setNextValidDate( ruleData.tempDate, ruleObject, ruleData.monthInit, ruleData.beginDate); ruleData.dateCollection = []; } } /** * Internal method for process monthly date type with month frequency from recurrence rule * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function monthlyDateTypeProcessforMonthFreq(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { const ruleData: RuleData = initializeRecRuleVariables(startDate, ruleObject); ruleData.tempDate = ruleData.mainDate = calendarUtil.getMonthStartDate(ruleData.tempDate); if (((ruleObject.freq === 'MONTHLY' && ruleObject.interval === 12) || (ruleObject.freq === 'YEARLY')) && calendarUtil.getMonthDaysCount(startDate) < ruleObject.monthDay[0]) { return; } while (compareDates(ruleData.tempDate, endDate)) { ruleData.beginDate = new Date(ruleData.tempDate.getTime()); processDateCollectionForByMonthDay(ruleObject, ruleData, endDate, true, startDate, data); if (!isNullOrUndefined(ruleObject.setPosition)) { insertDatasIntoExistingCollection(ruleData.dateCollection, ruleData.state, startDate, endDate, data, ruleObject); } if (ruleData.expectedCount && (data.length + ruleObject.recExceptionCount) >= ruleData.expectedCount) { return; } ruleData.monthInit = setNextValidDate(ruleData.tempDate, ruleObject, ruleData.monthInit, ruleData.beginDate); ruleData.dateCollection = []; } } /** * To process date collection for Monthly & Yearly based on BYMONTH Day property * * @param {RecRule} ruleObject Accepts the recurrence rule object * @param {RuleData} recRuleVariables Accepts the rule data * @param {Date} endDate Accepts the end date * @param {boolean} isByMonth Accepts the boolean to validate either month or not * @param {Date} startDate Accepts the start date * @param {number[]} data Accepts the collection of dates * @returns {void} * @private */ function processDateCollectionForByMonthDay(ruleObject: RecRule, recRuleVariables: RuleData, endDate: Date, isByMonth?: boolean, startDate?: Date, data?: number[]): void { for (let index: number = 0; index < ruleObject.monthDay.length; index++) { recRuleVariables.date = ruleObject.monthDay[parseInt(index.toString(), 10)]; recRuleVariables.tempDate = calendarUtil.getMonthStartDate(recRuleVariables.tempDate); const maxDate: number = calendarUtil.getMonthDaysCount(recRuleVariables.tempDate); recRuleVariables.date = recRuleVariables.date > 0 ? recRuleVariables.date : (maxDate + recRuleVariables.date + 1); if (validateProperDate(recRuleVariables.tempDate, recRuleVariables.date, recRuleVariables.mainDate) && (recRuleVariables.date > 0)) { calendarUtil.setDate(recRuleVariables.tempDate, recRuleVariables.date); if (endDate && recRuleVariables.tempDate > endDate) { return; } if (ruleObject.day.length === 0 || ruleObject.day.indexOf(DAYINDEX[recRuleVariables.tempDate.getDay()]) > -1) { if (isByMonth && isNullOrUndefined(ruleObject.setPosition) && (recRuleVariables.expectedCount && (data.length + ruleObject.recExceptionCount) < recRuleVariables.expectedCount)) { insertDateCollection(recRuleVariables.state, startDate, endDate, data, ruleObject, recRuleVariables.tempDate.getTime()); } else { recRuleVariables.dateCollection.push([recRuleVariables.tempDate.getTime()]); } } } } } /** * Internal method to set next valid date * * @param {Date} tempDate Accepts the date * @param {RecRule} ruleObject Accepts the recurrence rule object * @param {number} monthInit Accepts the initial month * @param {Date} beginDate Accepts the initial date * @param {number} interval Accepts the interval duration * @returns {number} Returnx the next valid date * @private */ function setNextValidDate(tempDate: Date, ruleObject: RecRule, monthInit: number, beginDate: Date = null, interval?: number): number { let monthData: number = beginDate ? beginDate.getMonth() : 0; const startDate: Date = calendarUtil.getMonthStartDate(tempDate); interval = isNullOrUndefined(interval) ? ruleObject.interval : interval; tempDate.setFullYear(startDate.getFullYear()); tempDate.setMonth(startDate.getMonth()); tempDate.setDate(startDate.getDate()); if (ruleObject.month.length) { monthInit++; monthInit = monthInit % ruleObject.month.length; calendarUtil.setMonth(tempDate, ruleObject.month[parseInt(monthInit.toString(), 10)], 1); if (monthInit === 0) { calendarUtil.addYears(tempDate, interval, ruleObject.month[0]); } } else { if (beginDate && (beginDate.getFullYear() < tempDate.getFullYear())) { monthData = tempDate.getMonth() - 1; } calendarUtil.setValidDate(tempDate, interval, 1, monthData, beginDate); } return monthInit; } /** * To get month collection when BYDAY property having more than one value in list. * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function getMonthCollection(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { const expectedDays: string[] = ruleObject.day; let tempDate: Date = new Date(startDate.getTime()); tempDate = calendarUtil.getMonthStartDate(tempDate); let monthCollection: number[][] = []; let dateCollection: number[][] = []; let dates: number[] = []; let index: number; let state: boolean; const expectedCount: number = getDateCount(startDate, ruleObject); let monthInit: number = 0; let beginDate: Date; if (ruleObject.month.length) { calendarUtil.setMonth(tempDate, ruleObject.month[0], 1); } tempDate = getStartDateForWeek(tempDate, ruleObject.day); while (compareDates(tempDate, endDate) && (expectedCount && (data.length + ruleObject.recExceptionCount) < expectedCount)) { const currentMonthDate: Date = new Date(tempDate.getTime()); const isHavingNumber: boolean[] = expectedDays.map((item: string) => HASNUMBER.test(item)); if (isHavingNumber.indexOf(true) > -1) { for (let j: number = 0; j <= expectedDays.length - 1; j++) { const expectedDaysArray: string[] = expectedDays[parseInt(j.toString(), 10)].match(SPLITNUMBERANDSTRING); const position: number = parseInt(expectedDaysArray[0], 10); tempDate = new Date(tempDate.getTime()); tempDate = calendarUtil.getMonthStartDate(tempDate); tempDate = getStartDateForWeek(tempDate, expectedDays); currentMonthDate.setFullYear(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate()); while (calendarUtil.isSameYear(currentMonthDate, tempDate) && calendarUtil.isSameMonth(currentMonthDate, tempDate)) { if (expectedDaysArray[expectedDaysArray.length - 1] === DAYINDEX[currentMonthDate.getDay()]) { monthCollection.push([currentMonthDate.getTime()]); } currentMonthDate.setDate(currentMonthDate.getDate() + (1)); } currentMonthDate.setDate(currentMonthDate.getDate() - (1)); if (expectedDaysArray[0].indexOf('-') > -1) { index = monthCollection.length - (-1 * position); } else { index = position - 1; } index = isNaN(index) ? 0 : index; if (monthCollection.length > 0) { if (isNullOrUndefined(ruleObject.setPosition)) { insertDatasIntoExistingCollection(monthCollection, state, startDate, endDate, data, ruleObject, index); } else { dateCollection = [(filterDateCollectionByIndex(monthCollection, index, dates))]; } } if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { return; } monthCollection = []; } if (!isNullOrUndefined(ruleObject.setPosition)) { insertDateCollectionBasedonBySetPos(dateCollection, state, startDate, endDate, data, ruleObject); dates = []; } monthInit = setNextValidDate(tempDate, ruleObject, monthInit, beginDate); tempDate = getStartDateForWeek(tempDate, ruleObject.day); monthCollection = []; } else { let weekCollection: number[] = []; const dayCycleData: { [key: string]: number } = processWeekDays(expectedDays); currentMonthDate.setFullYear(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate()); const initialDate: Date = new Date(tempDate.getTime()); beginDate = new Date(tempDate.getTime()); while (calendarUtil.isSameMonth(initialDate, tempDate)) { weekCollection.push(tempDate.getTime()); if (expectedDays.indexOf(DAYINDEX[tempDate.getDay()]) > -1) { monthCollection.push(weekCollection); weekCollection = []; } tempDate.setDate(tempDate.getDate() + dayCycleData[DAYINDEX[tempDate.getDay()]]); } index = ((ruleObject.setPosition < 1) ? (monthCollection.length + ruleObject.setPosition) : ruleObject.setPosition - 1); if (isNullOrUndefined(ruleObject.setPosition)) { index = 0; const datas: number[] = []; for (let week: number = 0; week < monthCollection.length; week++) { for (let row: number = 0; row < monthCollection[parseInt(week.toString(), 10)].length; row++) { datas.push(monthCollection[parseInt(week.toString(), 10)][parseInt(row.toString(), 10)]); } } monthCollection = [datas]; } if (monthCollection.length > 0) { insertDatasIntoExistingCollection(monthCollection, state, startDate, endDate, data, ruleObject, index); } if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { return; } monthInit = setNextValidDate(tempDate, ruleObject, monthInit, beginDate); tempDate = getStartDateForWeek(tempDate, ruleObject.day); monthCollection = []; } } } /** * To process monday day type for FREQ=MONTHLY * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function monthlyDayTypeProcessforMonthFreq(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { const expectedDays: string[] = ruleObject.day; // When BYDAY property having more than 1 value. if (expectedDays.length > 1) { getMonthCollection(startDate, endDate, data, ruleObject); return; } let tempDate: Date = new Date(startDate.getTime()); const expectedCount: number = getDateCount(startDate, ruleObject); let monthCollection: number[][] = []; let beginDate: Date; let monthInit: number = 0; tempDate = calendarUtil.getMonthStartDate(tempDate); if (ruleObject.month.length) { calendarUtil.setMonth(tempDate, ruleObject.month[0], 1); } tempDate = getStartDateForWeek(tempDate, ruleObject.day); while (compareDates(tempDate, endDate) && (expectedCount && (data.length + ruleObject.recExceptionCount) < expectedCount)) { beginDate = new Date(tempDate.getTime()); const currentMonthDate: Date = new Date(tempDate.getTime()); while (calendarUtil.isSameMonth(tempDate, currentMonthDate)) { monthCollection.push([currentMonthDate.getTime()]); currentMonthDate.setDate(currentMonthDate.getDate() + (7)); } // To filter date collection based on BYDAY Index, then BYSETPOS and to insert datas into existing collection insertDateCollectionBasedonIndex(monthCollection, startDate, endDate, data, ruleObject); monthInit = setNextValidDate(tempDate, ruleObject, monthInit, beginDate); tempDate = getStartDateForWeek(tempDate, ruleObject.day); monthCollection = []; } } /** * To process monday day type for FREQ=YEARLY * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function monthlyDayTypeProcess(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { const expectedDays: string[] = ruleObject.day; const isHavingNumber: boolean[] = expectedDays.map((item: string) => HASNUMBER.test(item)); // If BYDAY property having more than 1 value in list if (expectedDays.length > 1 && isHavingNumber.indexOf(true) > -1) { processDateCollectionforByDayWithInteger(startDate, endDate, data, ruleObject); return; } else if (ruleObject.month.length && expectedDays.length === 1 && isHavingNumber.indexOf(true) > -1) { monthlyDayTypeProcessforMonthFreq(startDate, endDate, data, ruleObject); return; } let tempDate: Date = new Date(startDate.getTime()); let currentMonthDate: Date; const expectedCount: number = getDateCount(startDate, ruleObject); const interval: number = ruleObject.interval; let monthCollection: number[][] = []; if (ruleObject.month.length) { calendarUtil.setMonth(tempDate, ruleObject.month[0], tempDate.getDate()); } // Set the date as start date of the yeear if yearly freq having ByDay property alone if (isNullOrUndefined(ruleObject.setPosition) && ruleObject.month.length === 0 && ruleObject.weekNo.length === 0) { tempDate.setFullYear(startDate.getFullYear(), 0, 1); } tempDate = calendarUtil.getMonthStartDate(tempDate); tempDate = getStartDateForWeek(tempDate, ruleObject.day); while (compareDates(tempDate, endDate)) { currentMonthDate = new Date(tempDate.getTime()); while (calendarUtil.isSameYear(currentMonthDate, tempDate) && (expectedCount && (data.length + ruleObject.recExceptionCount) <= expectedCount)) { currentMonthDate = new Date(tempDate.getTime()); while (calendarUtil.isSameYear(currentMonthDate, tempDate)) { if (ruleObject.month.length === 0 || (ruleObject.month.length > 0 && !calendarUtil.checkMonth(tempDate, ruleObject.month))) { if (expectedDays.length > 1) { if (calendarUtil.compareMonth(currentMonthDate, tempDate)) { calendarUtil.setValidDate(tempDate, 1, 1); tempDate = getStartDateForWeek(tempDate, ruleObject.day); break; } if (expectedDays.indexOf(DAYINDEX[currentMonthDate.getDay()]) > -1) { monthCollection.push([currentMonthDate.getTime()]); } currentMonthDate.setDate(currentMonthDate.getDate() + (1)); } else { // If BYDAY property having 1 value in list if (currentMonthDate.getFullYear() > tempDate.getFullYear()) { calendarUtil.setValidDate(tempDate, 1, 1); tempDate = getStartDateForWeek(tempDate, ruleObject.day); break; } const newstr: string = getDayString(expectedDays[0]); if (DAYINDEX[currentMonthDate.getDay()] === newstr && new Date(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), 0) > new Date(startDate.getFullYear())) { monthCollection.push([currentMonthDate.getTime()]); } currentMonthDate.setDate(currentMonthDate.getDate() + (7)); } } else { calendarUtil.setValidDate(tempDate, 1, 1); tempDate = getStartDateForWeek(tempDate, ruleObject.day); break; } } } tempDate.setFullYear(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), currentMonthDate.getDate()); // To filter date collection based on BYDAY Index, then BYSETPOS and to insert datas into existing collection insertDateCollectionBasedonIndex(monthCollection, startDate, endDate, data, ruleObject); if (calendarUtil.isLastMonth(tempDate)) { calendarUtil.setValidDate(tempDate, 1, 1); tempDate = getStartDateForWeek(tempDate, ruleObject.day); } tempDate.setFullYear(tempDate.getFullYear() + interval - 1); if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { return; } tempDate = getStartDateForWeek(tempDate, ruleObject.day); monthCollection = []; } } /** * To process the recurrence rule when BYDAY property having values with integer * * @param {Date} startDate Accepts the strat date * @param {Date} endDate Accepts the end date * @param {number[]} data Accepts the collection of dates * @param {RecRule} ruleObject Accepts the recurrence rule object * @returns {void} * @private */ function processDateCollectionforByDayWithInteger(startDate: Date, endDate: Date, data: number[], ruleObject: RecRule): void { const expectedDays: string[] = ruleObject.day; const expectedCount: number = getDateCount(startDate, ruleObject); let tempDate: Date = new Date(startDate.getTime()); const interval: number = ruleObject.interval; let monthCollection: number[][] = []; let dateCollection: number[][] = []; let index: number; let state: boolean; let monthInit: number = 0; let currentMonthDate: Date; let currentDate: Date; let beginDate: Date; tempDate = calendarUtil.getMonthStartDate(tempDate); let datas: number[] = []; if (ruleObject.month.length) { calendarUtil.setMonth(tempDate, ruleObject.month[0], 1); } tempDate = getStartDateForWeek(tempDate, ruleObject.day); while (compareDates(tempDate, endDate)) { currentMonthDate = new Date(tempDate.getTime()); for (let i: number = 0; i <= ruleObject.month.length; i++) { for (let j: number = 0; j <= expectedDays.length - 1; j++) { tempDate = calendarUtil.getMonthStartDate(tempDate); tempDate = getStartDateForWeek(tempDate, ruleObject.day); monthCollection = []; while (calendarUtil.isSameYear(currentMonthDate, tempDate) && (expectedCount && (data.length + ruleObject.recExceptionCount) <= expectedCount)) { while (calendarUtil.isSameYear(currentMonthDate, tempDate)) { currentMonthDate = new Date(tempDate.getTime()); if (ruleObject.month.length === 0 || (ruleObject.month.length > 0 && ruleObject.month[parseInt(i.toString(), 10)] === calendarUtil.getMonth(currentMonthDate))) { const expectedDaysArray: string[] = expectedDays[parseInt(j.toString(), 10)].match(SPLITNUMBERANDSTRING); const position: number = parseInt(expectedDaysArray[0], 10); currentDate = new Date(tempDate.getTime()); while (calendarUtil.isSameYear(currentDate, tempDate) && calendarUtil.isSameMonth(currentDate, tempDate)) { if (expectedDaysArray[expectedDaysArray.length - 1] === DAYINDEX[currentDate.getDay()]) { monthCollection.push([currentDate.getTime()]); } currentDate.setDate(currentDate.getDate() + (1)); } currentDate.setDate(currentDate.getDate() - (1)); if (expectedDaysArray[0].indexOf('-') > -1) { index = monthCollection.length - (-1 * position); } else { index = position - 1; } index = isNaN(index) ? 0 : index; } monthInit = setNextValidDate(tempDate, ruleObject, monthInit, beginDate, 1); tempDate = getStartDateForWeek(tempDate, ruleObject.day); } } tempDate = j === 0 && currentDate ? new Date(currentDate.getTime()) : new Date(currentMonthDate.getTime()); if (monthCollection.length > 0) { if (isNullOrUndefined(ruleObject.setPosition)) { insertDatasIntoExistingCollection(monthCollection, state, startDate, endDate, data, ruleObject, index); } else { dateCollection = [(filterDateCollectionByIndex(monthCollection, index, datas))]; } } if (expectedCount && (data.length + ruleObject.recExceptionCount) >= expectedCount) { return; } } } if (!isNullOrUndefined(ruleObject.setPosition)) { insertDateCollectionBasedonBySetPos(dateCollection, state, startDate, endDate, data, ruleObject); datas = []; } if (calendarUtil.isLastMonth(tempDate)) { calendarUtil.setValidDate(tempDate, 1, 1); tempDate.setFullYear(tempDate.getFullYear() + interval - 1); } else { tempDate.setFullYear(tempDate.getFullYear() + interval); } tempDate = getStartDateForWeek(tempDate, ruleObject.day); if (ruleObject.month.length) { calendarUtil.setMonth(tempDate, ruleObject.month[0], tempDate.getDate()); } } } /** * To get recurrence collection if BYSETPOS is null * * @param {number[]} monthCollection Accepts the month collection dates * @param {string[]} expectedDays Accepts the exception dates * @returns {RuleData} Returns the rule data object * @private */ function getRecurrenceCollection(monthCollection: number[][], expectedDays: string[]): RuleData { let index: number; const recurrenceCollectionObject: RuleData = { monthCollection: [], index: 0 }; if (expectedDays.length === 1) { // To split numeric value from BYDAY property value const expectedDaysArrays: string[] = expectedDays[0].match(SPLITNUMBERANDSTRING); let arrPosition: number; if (expectedDaysArrays.length > 1) { arrPosition = parseInt(expectedDaysArrays[0], 10); index = ((arrPosition < 1) ? (monthCollection.length + arrPosition) : arrPosition - 1); } else { index = 0; monthCollection = getDateCollectionforBySetPosNull(monthCollection); } } else { index = 0; monthCollection = getDateCollectionforBySetPosNull(monthCollection); } recurrenceCollectionObject.monthCollection = monthCollection; recurrenceCollectionObject.index = in