
function rhc_in_array(needle,haystack){
	var r = jQuery.inArray(needle,haystack);
	return (r!=undefined && (r != -1)) ;
}

Date.countMonthDays = function(y, m) { // m : 0->11
    return (new Date(y, m + 1, 0)).getDate();
}

Date.findNthWeekDays = function(y, m, nth, week_day) { // m : false or 0->11, week_day : 0->SU, 1->MO, ..., nth : N (Nth from start of month), -N (Nth from end of month), 0 (all)
    var dates = [];
    if (nth >= 0) { 
        if (m === false) { // from start of year
            var date = new Date(y, 0, 1);
            var end_year_ts = new Date(y + 1, 0, 1).getTime();
            var count = 0;
            while (date.getTime() < end_year_ts) {
                if (date.getDay() == week_day) {
                    count++;
                    if (nth == 0 || count == nth) {
                        dates.push(date);
                    }
                }
                date = new Date(date.getFullYear(), date.getMonth(), date.getDate()+1);
            }
        } else { // from start of month
            date = new Date(y, m, 1);
            var end_month_ts = new Date(y, m + 1, 1).getTime();
            count = 0;
            while (date.getTime() < end_month_ts) {
                if (date.getDay() == week_day) {
                    count++;
                    if (nth == 0 || count == nth) {
                        dates.push(date);
                    }
                }
                date = new Date(date.getFullYear(), date.getMonth(), date.getDate()+1);
            }
        }
        
    } else if (nth < 0) {
        if (m === false) { // from end of year
            nth = Math.abs(nth);
            date = new Date(y + 1, 0, 0);
            var begin_year_ts = new Date(y, 0, 1).getTime();
            count = 0;
            while (date.getTime() >= begin_year_ts) {
                if (date.getDay() == week_day) {
                    count++;
                    if (nth == 0 || count == nth) {
                        dates.push(date);
                    }
                }
                date = new Date(date.getFullYear(), date.getMonth(), date.getDate()-1);
            }
        } else { // from end of month
            nth = Math.abs(nth);
            date = new Date(y, m + 1, 0);
            var begin_month_ts = new Date(y, m, 1).getTime();
            count = 0;
            while (date.getTime() >= begin_month_ts) {
                if (date.getDay() == week_day) {
                    count++;
                    if (nth == 0 || count == nth) {
                        dates.push(date);
                    }
                }
                date = new Date(date.getFullYear(), date.getMonth(), date.getDate()-1);
            }
        }
        
    }
    return dates;
}

Date.prototype.isSameDate = function(other_date) {
    return other_date instanceof Date &&
        this.getFullYear() == other_date.getFullYear() &&
        this.getMonth() == other_date.getMonth() &&
        this.getDate() == other_date.getDate();
    
}

Date.fromWeek = function(nth, y, wkday){ // nth : nth week (1 : first week) of the year y, wkday : day of the week to retrieve (0->SU, 1->MO, ...)
    nth = nth - 1;
    y = y || new Date().getFullYear();
    var d1= new Date(y, 0, 4);
    if (wkday == undefined) wkday = 1;
    return d1.nextWeek(wkday, nth);
}

Date.prototype.nextWeek = function(wd, nth){ // wd : 0->SU, 1->MO, ... (default : same day), nth : how many week to add (default 1)
    if(nth== undefined) nth= 1;
    var incr= nth < 0? 1: -1,
    D= new Date(this), dd= D.getDay();
    if(wd== undefined) wd= dd;
    while(D.getDay()!= wd) D.setDate(D.getDate()+ incr);
    D.setDate(D.getDate()+ 7*nth);
    return D;
}

/**
* Returns the week number for this date. dowOffset is the day of week the week
* "starts" on for your locale - it can be from 0 to 6. If dowOffset is 1 (Monday),
* the week returned is the ISO 8601 week number.
* @param dowOffset
* @return int
*/
Date.prototype.getWeekNo = function (dowOffset) { // 0 -> wkst SU, 1 -> wkst MO
    /*getWeek() was developed by Nick Baicoianu at MeanFreePath: http://www.meanfreepath.com */

    dowOffset = dowOffset !== undefined ? dowOffset : 0; //default dowOffset to zero
    var newYear = new Date(this.getFullYear(),0,1);
    var day = newYear.getDay() - dowOffset; //the day of week the year begins on
    day = (day >= 0 ? day : day + 7);
    var daynum = Math.floor((this.getTime() - newYear.getTime() -
        (this.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/86400000) + 1;
    var weeknum;
    //if the year starts before the middle of a week
    if(day < 4) {
        weeknum = Math.floor((daynum+day-1)/7) + 1;
        if(weeknum > 52) {
            var nYear = new Date(this.getFullYear() + 1,0,1);
            var nday = nYear.getDay() - dowOffset;
            nday = nday >= 0 ? nday : nday + 7;
            /*if the next year starts before the middle of
            the week, it is week #1 of that year*/
            weeknum = nday < 4 ? 1 : 53;
        }
    } else {
        weeknum = Math.floor((daynum+day-1)/7);
    }
    return weeknum;
};

Scheduler = function(start_date, rfc_rrule, test_mode) { // Scheduler "class" (global visibility)
    this.init_recurrence_rules = function() {
        // mandatory
        this.rrule_freq = false;

        // both count & until are forbidden
        this.rrule_count = false;
        this.rrule_until = false;

        // facultative
        this.rrule_interval = 1;
        this.rrule_bysecond = false;
        this.rrule_byminute = false;
        this.rrule_byhour = false;
        this.rrule_byday = false; // +1, -2, etc. only for monthly or yearly
        this.rrule_bymonthday = false;
        this.rrule_byyearday = false;
        this.rrule_byweekno = false; // only for yearly
        this.rrule_bymonth = false;
        this.rrule_bysetpos = false; // only in conjonction with others BYxxx rules
        this.rrule_wkst = "MO"; // significant where weekly interval > 1 & where yearly byweekno is specified
    }

    this.test_mode = test_mode === true ? true : false;
    this.start_date = start_date;
    this.start_ts = start_date.getTime();
    
    this.init_recurrence_rules();
    this.exception_dates = [];
	this.repeat_dates = [];//Added by AL
	
    this.dayFromDayNo = [ "SU", "MO", "TU", "WE", "TH", "FR", "SA" ];
    this.dayNoFromDay = {
        SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6
    };

    if (rfc_rrule) {
        this.add_recurrence_rules(rfc_rrule);
    }

}; 

// adds at least one RRULE
Scheduler.prototype.add_recurrence_rules = function(rfc_rrule) {
    if (rfc_rrule.indexOf("RRULE:") == 0) { // removes "RRULE:" if needed
        rfc_rrule = rfc_rrule.slice(6);
    }

    var rules = rfc_rrule.split(";");
    var nb_rules = rules.length;

    for (var i = 0; i < nb_rules; i++) {
        var rule = rules[i].split("=");
        var rule_value = rule[1];
        switch (rule[0]) { // rule name
            case "FREQ":
                this.rrule_freq = rule_value;
                break;
            case "UNTIL":
                var until = rule_value;
                var y = until.substr(0, 4);
                var m = until.substr(4, 2) - 1; // js Date month -> 0 to 11
                var d = until.substr(6, 2);
                if (until.length > 8) {
                    var h = until.substr(9, 2);
                    var min = until.substr(11, 2);
                    var s = until.substr(13, 2);
                    this.rrule_until = new Date(y, m, d, h, min, s).getTime();
                } else {
                    this.rrule_until = new Date(y, m, d, 23, 59, 59).getTime();
                }

                break;
            case "COUNT":
                this.rrule_count = rule_value;
                break;
            case "INTERVAL":
                this.rrule_interval = rule_value;
                break;
            case "BYSECOND":
                this.rrule_bysecond = rule_value.split(",");
                break;
            case "BYMINUTE":
                this.rrule_byminute = rule_value.split(",");
                break;
            case "BYHOUR":
                this.rrule_byhour = rule_value.split(",");
                break;
            case "BYDAY":
                this.rrule_byday = rule_value.split(",");
                break;
            case "BYMONTHDAY":
                if (this.rrule_freq != "WEEKLY") {
                    this.rrule_bymonthday = rule_value.split(",");
                }
                break;
            case "BYYEARDAY":
                if (this.rrule_freq == "YEARLY") {
                    this.rrule_byyearday = rule_value.split(",");
                }
                break;
            case "BYWEEKNO":
                if (this.rrule_freq == "YEARLY") {
                    this.rrule_byweekno = rule_value.split(",");
                }
                break;
            case "BYMONTH":
                this.rrule_bymonth = rule_value.split(",");
                break;
            case "BYSETPOS":
                this.rrule_bysetpos = rule_value.split(",");
                this.rrule_bysetpos.sort(function(a,b) {
                    return parseInt(a, 10) - parseInt(b, 10);
                });
                break;
            case "WKST":
                this.rrule_wkst = rule_value;
                break;
        }
    }

    //if BYSECOND, BYMINUTE, BYHOUR, BYDAY, BYMONTHDAY or BYMONTH unspecified, fetch values from start date
    if (!this.rrule_bysecond) {
        this.rrule_bysecond = [ this.start_date.getSeconds().toString() ];
    }
    if (!this.rrule_byminute) {
        this.rrule_byminute = [ this.start_date.getMinutes().toString() ];
    }
    if (!this.rrule_byhour) {
        this.rrule_byhour = [ this.start_date.getHours().toString() ];
    }
    if (!this.rrule_byday && this.rrule_freq == "WEEKLY") {// auto value only when freq=weekly i guess...
        this.rrule_byday = [ this.dayFromDayNo[this.start_date.getDay()] ];
    }
    if (!this.rrule_byday && !this.rrule_bymonthday && !this.rrule_byyearday && !this.rrule_byweekno && (this.rrule_freq == "MONTHLY" || this.rrule_freq == "YEARLY")) {
        this.rrule_bymonthday = [ this.start_date.getDate().toString() ];
    }
    if (!this.rrule_byday && !this.rrule_byyearday && !this.rrule_bymonth && !this.rrule_byweekno && this.rrule_freq == "YEARLY") {
        this.rrule_bymonth = [ (this.start_date.getMonth() + 1).toString() ];
    }
}

// removes all RRULEs
Scheduler.prototype.remove_recurrence_rules = function() {
    this.init_recurrence_rules();
}

// adds at least one EXDATE (optional)
Scheduler.prototype.add_exception_dates = function(dates) {
    var nb_date = dates.length;
    for (var i = 0; i < nb_date; i++) {
        this.exception_dates.push(dates[i].getTime());
    }
    this.exception_dates.sort();
}

// removes all EXDATEs
Scheduler.prototype.remove_exception_dates = function() {
    this.exception_dates = [];
}

// retourns all occurrences as Date array (test mode => timestamp array)
Scheduler.prototype.all_occurrences = function(filter_begin_ts, filter_end_ts) {
    var occurrences = [];
    if ((filter_begin_ts === undefined || filter_end_ts === undefined) &&
        this.rrule_count === false && this.rrule_until === false) { 
        return null; // infinity of results => must be processed with filter_begin_ts & filter_end_ts
    }

    var current_date = this.start_date;
    var count = 0; // used to filter by rrule_count
    var count_period = 0; // used to process intervals
    var _break = 0;
period_loop:
    while ((this.rrule_count === false || count < this.rrule_count)
    && (this.rrule_until === false || current_date.getTime() <= this.rrule_until)
    && (filter_end_ts === undefined || current_date.getTime() <= filter_end_ts)) {

        var day = this.dayFromDayNo[current_date.getDay()];
        var d = current_date.getDate();
        var m = current_date.getMonth() + 1;
        var y = current_date.getFullYear();
        var week_no = current_date.getWeekNo(this.rrule_wkst == "MO" ? 1 : 0); // 1 to 53
        var h = current_date.getHours();
        var min = current_date.getMinutes();
        var s = current_date.getSeconds();
		
if( _break++ > 10000 ){
	console.log( 'rrecur-parser.js, infinite loop. breaking.' );
	break;
}

        this.current_pos = 1; // used in rrule_bysetpos
        this.old_pos = []; // used when bysetpos is a negative number

        if (count_period % this.rrule_interval == 0 && this.check_rules(day, d, m, week_no, h, min, s)) { // current date matches interval AND rules byday, bymonthday, bymonth
            if (this.rrule_freq == "DAILY") {
                for (var h_it = 0; h_it < this.rrule_byhour.length; h_it++) {
                    for (var min_it = 0; min_it < this.rrule_byminute.length; min_it++) {
                        for (var s_it = 0; s_it < this.rrule_bysecond.length; s_it++) {
                            var date_to_push = new Date(y, (m-1), d, this.rrule_byhour[h_it], this.rrule_byminute[min_it], this.rrule_bysecond[s_it]);
                            var ts_to_push = date_to_push.getTime();
                            if (this.rrule_bysetpos !== false && !rhc_in_array(this.current_pos.toString(),this.rrule_bysetpos) ) {
                                this.current_pos++;
                                this.old_pos.push(date_to_push);
                                continue;
                            }
                            if ((this.rrule_until !== false && ts_to_push > this.rrule_until) ||
                                (filter_end_ts !== undefined && ts_to_push > filter_end_ts)) {
                                break period_loop;
                            }
                            if (ts_to_push >= this.start_ts) {
                                if (filter_begin_ts === undefined || ts_to_push >= filter_begin_ts) {
                                    occurrences.push(date_to_push);
                                }
                                count++;
                            }
                            
                            this.current_pos++;
                            this.old_pos.push(date_to_push);

                            if (this.rrule_count !== false && count >= this.rrule_count) {
                                break period_loop;
                            }
                        }
                    }
                }
            } else if ( rhc_in_array(this.rrule_freq,["WEEKLY", "MONTHLY", "YEARLY"]) ) {
                switch (this.rrule_freq) {
                    case "WEEKLY":
						var _y = week_no==1 && m==12 ? parseInt(y+1) : y ;
                        var period_begin = Date.fromWeek(week_no, _y, this.rrule_wkst == "MO" ? 1 : 0);
                        var until = Date.fromWeek(week_no + 1, _y, this.rrule_wkst == "MO" ? 1 : 0);
                        break;
                    case "MONTHLY":
                        period_begin = new Date(y, m - 1, 1)
                        until = new Date(y, m, 1);
                        break;
                    case "YEARLY":
                        period_begin = new Date(y, 0, 1);
                        until = new Date(y + 1, 0, 1);
                        break;
                }

                var it_date = period_begin;

                while (it_date.getTime() < until.getTime()) {
                    var it_date_ts = it_date.getTime();
                    if ((this.rrule_until !== false && it_date_ts > this.rrule_until) ||
                        (filter_end_ts !== undefined && it_date_ts > filter_end_ts)) {
                        break period_loop;
                    }

                    if (this.check_day(it_date)) {

                        for (h_it = 0; h_it < this.rrule_byhour.length; h_it++) {
                            for (min_it = 0; min_it < this.rrule_byminute.length; min_it++) {
                                for (s_it = 0; s_it < this.rrule_bysecond.length; s_it++) {
                                    date_to_push = new Date(it_date.getFullYear(), it_date.getMonth(), it_date.getDate(), this.rrule_byhour[h_it], this.rrule_byminute[min_it], this.rrule_bysecond[s_it]);
                                    ts_to_push = date_to_push.getTime();
                                    if (this.rrule_bysetpos !== false && !rhc_in_array(this.current_pos.toString(), this.rrule_bysetpos) ) {	
                                        this.current_pos++;
                                        this.old_pos.push(date_to_push);
                                        continue;
                                    }
                                    if ((this.rrule_until !== false && ts_to_push > this.rrule_until) ||
                                        (filter_end_ts !== undefined && ts_to_push > filter_end_ts)) {
                                        break period_loop;
                                    }
                                    if (ts_to_push >= this.start_ts) {
                                        if (filter_begin_ts === undefined || ts_to_push >= filter_begin_ts) {
                                            occurrences.push(date_to_push);
                                        }
                                        count++;
                                    }

                                    this.current_pos++;
                                    this.old_pos.push(date_to_push);

                                    if (this.rrule_count !== false && count >= this.rrule_count) {
                                        break period_loop;
                                    }
                                }
                            }
                        }
                    }
                    it_date = new Date(it_date);
                    it_date.setDate(it_date.getDate() + 1);
                }
                // process negative values of rrule_bysetpos
                if (this.rrule_bysetpos instanceof Array) {
                    for (var it_pos = 0; it_pos < this.rrule_bysetpos.length; it_pos++) {
                        var pos = parseInt(this.rrule_bysetpos[it_pos], 10);
                        if (pos < 0) {
                            pos = Math.abs(pos);
                            var last_matching_dates = this.old_pos.reverse();
                            var matching_date = last_matching_dates[pos - 1];
                            if (matching_date && matching_date >= this.start_ts) {
                                occurrences.push(matching_date);
                                count++;
                            }
                            if (this.rrule_count !== false && count >= this.rrule_count) {
                                break period_loop;
                            }
                        }
                    }
                }
            }
        }

        count_period++;
        current_date = this.next_period(current_date);
    }

	//add rdates
	if(this.repeat_dates.length>0){		
		for (var i = 0; i< this.repeat_dates.length; i++){
			var occurrence = new Date(this.repeat_dates[i]);
			if ( !rhc_in_array(ts,occurrences) ){
				occurrences.push(occurrence);
			}
		}
		occurrences.sort();	
	}
	
	// removes exdates
    var nb_occurrences = occurrences.length;
    var occurrences_without_exdates = [];
    for (var i = 0; i < nb_occurrences; i++) {
        var occurrence = occurrences[i];
        var ts = occurrence.getTime();
        if ( !rhc_in_array(ts,this.exception_dates) ) {
            occurrences_without_exdates.push(this.test_mode ? ts : occurrence);
        }
    }
	
    return occurrences_without_exdates;
}

Scheduler.prototype.next_period = function(date) {
    switch (this.rrule_freq) {
        case "DAILY":
            var new_date = new Date(date);
            new_date.setDate(date.getDate() + 1);
            return new_date;
        case "WEEKLY":
            return date.nextWeek();
        case "MONTHLY":
            new_date = new Date(date);
            new_date.setMonth(date.getMonth() + 1, 1);
            return new_date;
        case "YEARLY":
            new_date = new Date(date);
            new_date.setFullYear(date.getFullYear() + 1);
            return new_date;
    }

}

// check validity of selected rules
Scheduler.prototype.check_rules = function(day, d, m, week_no, h, min, s) {
    switch (this.rrule_freq) {
        case "DAILY":
            return ((m === undefined || this.rrule_bymonth === false || rhc_in_array(m.toString(), this.rrule_bymonth) ) &&
                (d === undefined || this.rrule_bymonthday === false || rhc_in_array( d.toString(), this.rrule_bymonthday ) ) &&
                (day === undefined || this.rrule_byday === false || rhc_in_array( day.toString(), this.rrule_byday ) ));
        case "WEEKLY":
            return ((m === undefined || this.rrule_bymonth === false || rhc_in_array(m.toString(), this.rrule_bymonth ) ) &&
                (d === undefined || this.rrule_bymonthday === false || rhc_in_array(d.toString(), this.rrule_bymonthday ) ));
        case "MONTHLY":
            return (m === undefined || this.rrule_bymonth === false || rhc_in_array( m.toString(), this.rrule_bymonth ) );
        case "YEARLY":
            return true;
    }
}

// retourns occurrences in the range [ begin_date, end_date ] as Date array (test mode => timestamp array)
Scheduler.prototype.occurrences_between = function(begin_date, end_date) {
    var begin_ts = begin_date.getTime();
    var end_ts = end_date.getTime();

    return this.all_occurrences(begin_ts, end_ts);
}

Scheduler.prototype.check_day = function(date) {
    var is_yearly = (this.rrule_freq == "YEARLY");
    var is_weekly = (this.rrule_freq == "WEEKLY");

    var day = this.dayFromDayNo[date.getDay()];
    var d = date.getDate();
    var m = date.getMonth() + 1;
    var y = date.getFullYear();
    if (is_yearly) {
        var week_no = date.getWeekNo(this.rrule_wkst == "MO" ? 1 : 0); // 1 to 53
    }

    // check rrule_bymonth
    if (this.rrule_bymonth !== false) {
        if ( !rhc_in_array(m.toString(), this.rrule_bymonth) ) {
            return false;
        }
    }

    // check rrule_byday
    if (this.rrule_byday !== false) {
        if (is_weekly) {
            if ( !rhc_in_array( day, this.rrule_byday) ) {
                return false;
            }
        } else {
            found = false;
            for (var it_wd = 0; it_wd < this.rrule_byday.length; it_wd++) {
                var rule_byday = /([+0-9-]*)([A-Z]+)/.exec(this.rrule_byday[it_wd]);
                var matching_dates = Date.findNthWeekDays(y, is_yearly ? false : m - 1, rule_byday[1] ? rule_byday[1] : 0, this.dayNoFromDay[rule_byday[2]]);

                for (var it = 0; it < matching_dates.length; it++) {
                    if (date.isSameDate(matching_dates[it])) {
                        found = true;
                        break;
                    }
                }
            }
            if (!found) {
                return false;
            }
        }
    }

    if (!is_weekly) {
        // check rrule_bymonthday
        if (this.rrule_bymonthday !== false) {
            var month_days_count = Date.countMonthDays(y, m - 1);
            var d_neg = d - 1 - month_days_count;
            var found = false;
            for (var it_md = 0; it_md < this.rrule_bymonthday.length; it_md++) {
                var md = parseInt(this.rrule_bymonthday[it_md], 10);
                if (d == md || d_neg == md) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                return false;
            }
        }
    }

    if (is_yearly) {

        // check rrule_byyearday
        if (this.rrule_byyearday !== false) {
            found = false;
            for (var it_yd = 0; it_yd < this.rrule_byyearday.length; it_yd++) {
                var year_day = this.rrule_byyearday[it_yd];
                if (year_day > 0) {
                    var year_date = new Date(y, 0, parseInt(year_day, 10));
                    if (date.isSameDate(year_date)) {
                        found = true;
                        break;
                    }
                } else if (year_day < 0) {
                    year_date = new Date(y + 1, 0, 1 + parseInt(year_day, 10));
                    if (date.isSameDate(year_date)) {
                        found = true;
                        break;
                    }
                } else {
                    continue;
                }
            }
            if (!found) {
                return false;
            }
        }

        // check rrule_byweekno
        if (this.rrule_byweekno !== false) {
            found = false;
            for (var it_wkno = 0; it_wkno < this.rrule_byweekno.length; it_wkno++) {
                var a_week_no = this.rrule_byweekno[it_wkno];
                if (a_week_no)
                if (a_week_no > 0) {
                    if (a_week_no == week_no) {
                        found = true;
                        break;
                    }
                } else if (a_week_no < 0) {
                    var year_week_count = new Date(y, 11, 31).getWeekNo();
                    if (week_no == year_week_count + 1 + a_week_no) {
                        found = true;
                        break;
                    }
                } else {
                    continue;
                }
            }
            if (!found) {
                return false;
            }
        }
    }

    // check rrule_bysetpos processed in period_loop
    return true;
}

//---modified for limited output when using until, but we only need a certain amount _limit
// retourns all occurrences as Date array (test mode => timestamp array)
Scheduler.prototype.limited_occurrences = function(filter_begin_ts, filter_end_ts, _limit) {
    var occurrences = [];
    if ((filter_begin_ts === undefined || filter_end_ts === undefined) &&
        this.rrule_count === false && this.rrule_until === false) { 
        return null; // infinity of results => must be processed with filter_begin_ts & filter_end_ts
    }

    var current_date = this.start_date;
    var count = 0; // used to filter by rrule_count
    var count_period = 0; // used to process intervals
    
period_loop:
    while ((this.rrule_count === false || count < this.rrule_count)
    && (this.rrule_until === false || current_date.getTime() <= this.rrule_until)
    && (filter_end_ts === undefined || current_date.getTime() <= filter_end_ts)) {

		//this is the only thing added:
		if( occurrences.length >= _limit )
			 break period_loop;
	
        var day = this.dayFromDayNo[current_date.getDay()];
        var d = current_date.getDate();
        var m = current_date.getMonth() + 1;
        var y = current_date.getFullYear();
        var week_no = current_date.getWeekNo(this.rrule_wkst == "MO" ? 1 : 0); // 1 to 53
        var h = current_date.getHours();
        var min = current_date.getMinutes();
        var s = current_date.getSeconds();

        this.current_pos = 1; // used in rrule_bysetpos
        this.old_pos = []; // used when bysetpos is a negative number

        if (count_period % this.rrule_interval == 0 && this.check_rules(day, d, m, week_no, h, min, s)) { // current date matches interval AND rules byday, bymonthday, bymonth
            if (this.rrule_freq == "DAILY") {
                for (var h_it = 0; h_it < this.rrule_byhour.length; h_it++) {
                    for (var min_it = 0; min_it < this.rrule_byminute.length; min_it++) {
                        for (var s_it = 0; s_it < this.rrule_bysecond.length; s_it++) {
                            var date_to_push = new Date(y, (m-1), d, this.rrule_byhour[h_it], this.rrule_byminute[min_it], this.rrule_bysecond[s_it]);
                            var ts_to_push = date_to_push.getTime();
                            if (this.rrule_bysetpos !== false && !rhc_in_array(this.current_pos.toString(), this.rrule_bysetpos) ) {
                                this.current_pos++;
                                this.old_pos.push(date_to_push);
                                continue;
                            }
                            if ((this.rrule_until !== false && ts_to_push > this.rrule_until) ||
                                (filter_end_ts !== undefined && ts_to_push > filter_end_ts)) {
                                break period_loop;
                            }
                            if (ts_to_push >= this.start_ts) {
                                if (filter_begin_ts === undefined || ts_to_push >= filter_begin_ts) {
                                    occurrences.push(date_to_push);
                                }
                                count++;
                            }
                            
                            this.current_pos++;
                            this.old_pos.push(date_to_push);

                            if (this.rrule_count !== false && count >= this.rrule_count) {
                                break period_loop;
                            }
                        }
                    }
                }
            } else if ( rhc_in_array(this.rrule_freq, ["WEEKLY", "MONTHLY", "YEARLY"]) ) {
                switch (this.rrule_freq) {
                    case "WEEKLY":
                        var period_begin = Date.fromWeek(week_no, y, this.rrule_wkst == "MO" ? 1 : 0);
                        var until = Date.fromWeek(week_no + 1, y, this.rrule_wkst == "MO" ? 1 : 0);
                        break;
                    case "MONTHLY":
                        period_begin = new Date(y, m - 1, 1)
                        until = new Date(y, m, 1);
                        break;
                    case "YEARLY":
                        period_begin = new Date(y, 0, 1);
                        until = new Date(y + 1, 0, 1);
                        break;
                }

                var it_date = period_begin;

                while (it_date.getTime() < until.getTime()) {
                    var it_date_ts = it_date.getTime();
                    if ((this.rrule_until !== false && it_date_ts > this.rrule_until) ||
                        (filter_end_ts !== undefined && it_date_ts > filter_end_ts)) {
                        break period_loop;
                    }

                    if (this.check_day(it_date)) {

                        for (h_it = 0; h_it < this.rrule_byhour.length; h_it++) {
                            for (min_it = 0; min_it < this.rrule_byminute.length; min_it++) {
                                for (s_it = 0; s_it < this.rrule_bysecond.length; s_it++) {
                                    date_to_push = new Date(it_date.getFullYear(), it_date.getMonth(), it_date.getDate(), this.rrule_byhour[h_it], this.rrule_byminute[min_it], this.rrule_bysecond[s_it]);
                                    ts_to_push = date_to_push.getTime();
                                    if (this.rrule_bysetpos !== false && !rhc_in_array(this.current_pos.toString(), this.rrule_bysetpos) ) {
                                        this.current_pos++;
                                        this.old_pos.push(date_to_push);
                                        continue;
                                    }
                                    if ((this.rrule_until !== false && ts_to_push > this.rrule_until) ||
                                        (filter_end_ts !== undefined && ts_to_push > filter_end_ts)) {
                                        break period_loop;
                                    }
                                    if (ts_to_push >= this.start_ts) {
                                        if (filter_begin_ts === undefined || ts_to_push >= filter_begin_ts) {
                                            occurrences.push(date_to_push);
                                        }
                                        count++;
                                    }

                                    this.current_pos++;
                                    this.old_pos.push(date_to_push);

                                    if (this.rrule_count !== false && count >= this.rrule_count) {
                                        break period_loop;
                                    }
                                }
                            }
                        }
                    }
                    it_date = new Date(it_date);
                    it_date.setDate(it_date.getDate() + 1);
                }
                // process negative values of rrule_bysetpos
                if (this.rrule_bysetpos instanceof Array) {
                    for (var it_pos = 0; it_pos < this.rrule_bysetpos.length; it_pos++) {
                        var pos = parseInt(this.rrule_bysetpos[it_pos], 10);
                        if (pos < 0) {
                            pos = Math.abs(pos);
                            var last_matching_dates = this.old_pos.reverse();
                            var matching_date = last_matching_dates[pos - 1];
                            if (matching_date && matching_date >= this.start_ts) {
                                occurrences.push(matching_date);
                                count++;
                            }
                            if (this.rrule_count !== false && count >= this.rrule_count) {
                                break period_loop;
                            }
                        }
                    }
                }
            }
        }

        count_period++;
        current_date = this.next_period(current_date);
    }
	
	//add rdates
	if(this.repeat_dates.length>0){		
		for (var i = 0; i< this.repeat_dates.length; i++){
			var occurrence = new Date(this.repeat_dates[i]);
			if ( !rhc_in_array(ts, occurrences) ){
				occurrences.push(occurrence);
			}
		}
		occurrences.sort();	
	}
	
    // removes exdates
    var nb_occurrences = occurrences.length;
    var occurrences_without_exdates = [];
    for (var i = 0; i < nb_occurrences; i++) {
        var occurrence = occurrences[i];
        var ts = occurrence.getTime();
        if ( !rhc_in_array(ts, this.exception_dates) ) {
            occurrences_without_exdates.push(this.test_mode ? ts : occurrence);
        }
    }
    return occurrences_without_exdates;
}

/* Added by AL, support for RDATE*/
Scheduler.prototype.add_rdates = function(dates) {
    var nb_date = dates.length;
    for (var i = 0; i < nb_date; i++) {
        this.repeat_dates.push(dates[i].getTime());
    }
    this.repeat_dates.sort();
}