Skip to content

Commit

Permalink
Fix for Multi-day weekly recurring event missing first instance
Browse files Browse the repository at this point in the history
  • Loading branch information
Arun Pandian authored and kewisch committed Oct 6, 2019
1 parent 2d08c79 commit 7b8d59d
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 47 deletions.
57 changes: 35 additions & 22 deletions build/ical.js
Original file line number Diff line number Diff line change
Expand Up @@ -4944,10 +4944,13 @@ ICAL.TimezoneService = (function() {

/**
* Calculate the day of week.
* @param {ICAL.Time.weekDay=} aWeekStart
* The week start weekday, defaults to SUNDAY
* @return {ICAL.Time.weekDay}
*/
dayOfWeek: function icaltime_dayOfWeek() {
var dowCacheKey = (this.year << 9) + (this.month << 5) + this.day;
dayOfWeek: function icaltime_dayOfWeek(aWeekStart) {
var firstDow = aWeekStart || ICAL.Time.SUNDAY;
var dowCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + firstDow;
if (dowCacheKey in ICAL.Time._dowCache) {
return ICAL.Time._dowCache[dowCacheKey];
}
Expand All @@ -4965,8 +4968,8 @@ ICAL.TimezoneService = (function() {
h += 5;
}

// Normalize to 1 = sunday
h = ((h + 6) % 7) + 1;
// Normalize to 1 = wkst
h = ((h + 7 - firstDow) % 7) + 1;
ICAL.Time._dowCache[dowCacheKey] = h;
return h;
},
Expand Down Expand Up @@ -6596,26 +6599,35 @@ ICAL.TimezoneService = (function() {
* into a numeric value of that day.
*
* @param {String} string The iCalendar day name
* @param {ICAL.Time.weekDay=} aWeekStart
* The week start weekday, defaults to SUNDAY
* @return {Number} Numeric value of given day
*/
ICAL.Recur.icalDayToNumericDay = function toNumericDay(string) {
ICAL.Recur.icalDayToNumericDay = function toNumericDay(string, aWeekStart) {
//XXX: this is here so we can deal
// with possibly invalid string values.

return DOW_MAP[string];
var firstDow = aWeekStart || ICAL.Time.SUNDAY;
return ((DOW_MAP[string] - firstDow + 7) % 7) + 1;
};

/**
* Convert a numeric day value into its ical representation (SU, MO, etc..)
*
* @param {Number} num Numeric value of given day
* @param {ICAL.Time.weekDay=} aWeekStart
* The week start weekday, defaults to SUNDAY
* @return {String} The ICAL day value, e.g SU,MO,...
*/
ICAL.Recur.numericDayToIcalDay = function toIcalDay(num) {
ICAL.Recur.numericDayToIcalDay = function toIcalDay(num, aWeekStart) {
//XXX: this is here so we can deal with possibly invalid number values.
// Also, this allows consistent mapping between day numbers and day
// names for external users.
return REVERSE_DOW_MAP[num];
var firstDow = aWeekStart || ICAL.Time.SUNDAY;
var dow = (num + firstDow - ICAL.Time.SUNDAY);
if (dow > 7) {
dow -= 7;
}
return REVERSE_DOW_MAP[dow];
};

var VALID_DAY_NAMES = /^(SU|MO|TU|WE|TH|FR|SA)$/;
Expand Down Expand Up @@ -6955,7 +6967,7 @@ ICAL.RecurIterator = (function() {
if ("BYDAY" in parts) {
// libical does this earlier when the rule is loaded, but we postpone to
// now so we can preserve the original order.
this.sort_byday_rules(parts.BYDAY, this.rule.wkst);
this.sort_byday_rules(parts.BYDAY);
}

// If the BYYEARDAY appares, no other date rule part may appear
Expand Down Expand Up @@ -6998,11 +7010,11 @@ ICAL.RecurIterator = (function() {

if (this.rule.freq == "WEEKLY") {
if ("BYDAY" in parts) {
var bydayParts = this.ruleDayOfWeek(parts.BYDAY[0]);
var bydayParts = this.ruleDayOfWeek(parts.BYDAY[0], this.rule.wkst);
var pos = bydayParts[0];
var dow = bydayParts[1];
var wkdy = dow - this.last.dayOfWeek();
if ((this.last.dayOfWeek() < dow && wkdy >= 0) || wkdy < 0) {
var wkdy = dow - this.last.dayOfWeek(this.rule.wkst);
if ((this.last.dayOfWeek(this.rule.wkst) < dow && wkdy >= 0) || wkdy < 0) {
// Initial time is after first day of BYDAY data
this.last.day += wkdy;
}
Expand Down Expand Up @@ -7622,11 +7634,16 @@ ICAL.RecurIterator = (function() {
this.last.month = next.month;
},

ruleDayOfWeek: function ruleDayOfWeek(dow) {
/**
* @param dow (eg: '1TU', '-1MO')
* @param {ICAL.Time.weekDay=} aWeekStart The week start weekday
* @return [pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart
*/
ruleDayOfWeek: function ruleDayOfWeek(dow, aWeekStart) {
var matches = dow.match(/([+-]?[0-9])?(MO|TU|WE|TH|FR|SA|SU)/);
if (matches) {
var pos = parseInt(matches[1] || 0, 10);
dow = ICAL.Recur.icalDayToNumericDay(matches[2]);
dow = ICAL.Recur.icalDayToNumericDay(matches[2], aWeekStart);
return [pos, dow];
} else {
return [0, 0];
Expand Down Expand Up @@ -8079,15 +8096,11 @@ ICAL.RecurIterator = (function() {
return false;
},

sort_byday_rules: function icalrecur_sort_byday_rules(aRules, aWeekStart) {
sort_byday_rules: function icalrecur_sort_byday_rules(aRules) {
for (var i = 0; i < aRules.length; i++) {
for (var j = 0; j < i; j++) {
var one = this.ruleDayOfWeek(aRules[j])[1];
var two = this.ruleDayOfWeek(aRules[i])[1];
one -= aWeekStart;
two -= aWeekStart;
if (one < 0) one += 7;
if (two < 0) two += 7;
var one = this.ruleDayOfWeek(aRules[j], this.rule.wkst)[1];
var two = this.ruleDayOfWeek(aRules[i], this.rule.wkst)[1];

if (one > two) {
var tmp = aRules[i];
Expand Down
2 changes: 1 addition & 1 deletion build/ical.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/ical.min.js.map

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions lib/ical/recur.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,26 +379,35 @@
* into a numeric value of that day.
*
* @param {String} string The iCalendar day name
* @param {ICAL.Time.weekDay=} aWeekStart
* The week start weekday, defaults to SUNDAY
* @return {Number} Numeric value of given day
*/
ICAL.Recur.icalDayToNumericDay = function toNumericDay(string) {
ICAL.Recur.icalDayToNumericDay = function toNumericDay(string, aWeekStart) {
//XXX: this is here so we can deal
// with possibly invalid string values.

return DOW_MAP[string];
var firstDow = aWeekStart || ICAL.Time.SUNDAY;
return ((DOW_MAP[string] - firstDow + 7) % 7) + 1;
};

/**
* Convert a numeric day value into its ical representation (SU, MO, etc..)
*
* @param {Number} num Numeric value of given day
* @param {ICAL.Time.weekDay=} aWeekStart
* The week start weekday, defaults to SUNDAY
* @return {String} The ICAL day value, e.g SU,MO,...
*/
ICAL.Recur.numericDayToIcalDay = function toIcalDay(num) {
ICAL.Recur.numericDayToIcalDay = function toIcalDay(num, aWeekStart) {
//XXX: this is here so we can deal with possibly invalid number values.
// Also, this allows consistent mapping between day numbers and day
// names for external users.
return REVERSE_DOW_MAP[num];
var firstDow = aWeekStart || ICAL.Time.SUNDAY;
var dow = (num + firstDow - ICAL.Time.SUNDAY);
if (dow > 7) {
dow -= 7;
}
return REVERSE_DOW_MAP[dow];
};

var VALID_DAY_NAMES = /^(SU|MO|TU|WE|TH|FR|SA)$/;
Expand Down
27 changes: 14 additions & 13 deletions lib/ical/recur_iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ ICAL.RecurIterator = (function() {
if ("BYDAY" in parts) {
// libical does this earlier when the rule is loaded, but we postpone to
// now so we can preserve the original order.
this.sort_byday_rules(parts.BYDAY, this.rule.wkst);
this.sort_byday_rules(parts.BYDAY);
}

// If the BYYEARDAY appares, no other date rule part may appear
Expand Down Expand Up @@ -219,11 +219,11 @@ ICAL.RecurIterator = (function() {

if (this.rule.freq == "WEEKLY") {
if ("BYDAY" in parts) {
var bydayParts = this.ruleDayOfWeek(parts.BYDAY[0]);
var bydayParts = this.ruleDayOfWeek(parts.BYDAY[0], this.rule.wkst);
var pos = bydayParts[0];
var dow = bydayParts[1];
var wkdy = dow - this.last.dayOfWeek();
if ((this.last.dayOfWeek() < dow && wkdy >= 0) || wkdy < 0) {
var wkdy = dow - this.last.dayOfWeek(this.rule.wkst);
if ((this.last.dayOfWeek(this.rule.wkst) < dow && wkdy >= 0) || wkdy < 0) {
// Initial time is after first day of BYDAY data
this.last.day += wkdy;
}
Expand Down Expand Up @@ -843,11 +843,16 @@ ICAL.RecurIterator = (function() {
this.last.month = next.month;
},

ruleDayOfWeek: function ruleDayOfWeek(dow) {
/**
* @param dow (eg: '1TU', '-1MO')
* @param {ICAL.Time.weekDay=} aWeekStart The week start weekday
* @return [pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart
*/
ruleDayOfWeek: function ruleDayOfWeek(dow, aWeekStart) {
var matches = dow.match(/([+-]?[0-9])?(MO|TU|WE|TH|FR|SA|SU)/);
if (matches) {
var pos = parseInt(matches[1] || 0, 10);
dow = ICAL.Recur.icalDayToNumericDay(matches[2]);
dow = ICAL.Recur.icalDayToNumericDay(matches[2], aWeekStart);
return [pos, dow];
} else {
return [0, 0];
Expand Down Expand Up @@ -1300,15 +1305,11 @@ ICAL.RecurIterator = (function() {
return false;
},

sort_byday_rules: function icalrecur_sort_byday_rules(aRules, aWeekStart) {
sort_byday_rules: function icalrecur_sort_byday_rules(aRules) {
for (var i = 0; i < aRules.length; i++) {
for (var j = 0; j < i; j++) {
var one = this.ruleDayOfWeek(aRules[j])[1];
var two = this.ruleDayOfWeek(aRules[i])[1];
one -= aWeekStart;
two -= aWeekStart;
if (one < 0) one += 7;
if (two < 0) two += 7;
var one = this.ruleDayOfWeek(aRules[j], this.rule.wkst)[1];
var two = this.ruleDayOfWeek(aRules[i], this.rule.wkst)[1];

if (one > two) {
var tmp = aRules[i];
Expand Down
11 changes: 7 additions & 4 deletions lib/ical/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,13 @@

/**
* Calculate the day of week.
* @param {ICAL.Time.weekDay=} aWeekStart
* The week start weekday, defaults to SUNDAY
* @return {ICAL.Time.weekDay}
*/
dayOfWeek: function icaltime_dayOfWeek() {
var dowCacheKey = (this.year << 9) + (this.month << 5) + this.day;
dayOfWeek: function icaltime_dayOfWeek(aWeekStart) {
var firstDow = aWeekStart || ICAL.Time.SUNDAY;
var dowCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + firstDow;
if (dowCacheKey in ICAL.Time._dowCache) {
return ICAL.Time._dowCache[dowCacheKey];
}
Expand All @@ -246,8 +249,8 @@
h += 5;
}

// Normalize to 1 = sunday
h = ((h + 6) % 7) + 1;
// Normalize to 1 = wkst
h = ((h + 7 - firstDow) % 7) + 1;
ICAL.Time._dowCache[dowCacheKey] = h;
return h;
},
Expand Down
25 changes: 25 additions & 0 deletions test/recur_iterator_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,31 @@ suite('recur_iterator', function() {
'2015-01-01T08:00:00'
]
});

//Weekly Sunday, Monday, Tuesday with count=3
testRRULE('FREQ=WEEKLY;COUNT=3;BYDAY=MO,SU,TU', {
dtStart: '2017-07-30',
byCount: true,
dates: [
'2017-07-30',
'2017-07-31',
'2017-08-01'
]
});

//Weekly Sunday, Wednesday with count=5
testRRULE('FREQ=WEEKLY;COUNT=5;BYDAY=SU,WE', {
dtStart: '2017-04-23',
byCount: true,
dates: [
'2017-04-23',
'2017-04-26',
'2017-04-30',
'2017-05-03',
'2017-05-07'
]
});

//yearly, byMonth, byweekNo
/* TODO BYWEEKNO is not well supported
testRRULE('FREQ=YEARLY;BYMONTH=6,9;BYWEEKNO=23', {
Expand Down
Loading

0 comments on commit 7b8d59d

Please sign in to comment.