From e379d42ab47dc1a97a53a3b7d14421b0f529834e Mon Sep 17 00:00:00 2001 From: darktrojan Date: Thu, 18 Apr 2024 00:56:34 +0000 Subject: [PATCH] deploy: ae06bf4621fa015948faec6412e9b8138caaf331 --- api/ICAL.RecurIterator.html | 2 +- api/recur_iterator.js.html | 191 +++++++++++++++++++++--------------- 2 files changed, 112 insertions(+), 81 deletions(-) diff --git a/api/ICAL.RecurIterator.html b/api/ICAL.RecurIterator.html index 80428921..59f1608b 100644 --- a/api/ICAL.RecurIterator.html +++ b/api/ICAL.RecurIterator.html @@ -1,3 +1,3 @@ Class: RecurIterator
On this page

ICAL. RecurIterator

An iterator for a single recurrence rule. This class usually doesn't have to be instanciated directly, the convenience method ICAL.Recur#iterator can be used.

Constructor

new RecurIterator(options)

Creates a new ICAL.RecurIterator instance. The options object may contain additional members when resuming iteration from a previous run.

Parameters:
NameTypeDescription
optionsObject

The iterator options

Properties
NameTypeAttributesDescription
ruleICAL.Recur

The rule to iterate.

dtstartICAL.Time

The start date of the event.

initializedBoolean<optional>

When true, assume that options are from a previously constructed iterator. Initialization will not be repeated.

Members

completed :Boolean

True when iteration is finished.

Type:
  • Boolean

dtstart :ICAL.Time

The start date of the event being iterated.

last :ICAL.Time

The last occurrence that was returned from the ICAL.RecurIterator#next method.

occurrence_number :Number

The sequence number from the occurrence

Type:
  • Number

rule :ICAL.Recur

The rule that is being iterated

Methods

fromData(options)

Initialize the recurrence iterator from the passed data object. This method is usually not called directly, you can initialize the iterator through the constructor.

Parameters:
NameTypeDescription
optionsObject

The iterator options

Properties
NameTypeAttributesDescription
ruleICAL.Recur

The rule to iterate.

dtstartICAL.Time

The start date of the event.

initializedBoolean<optional>

When true, assume that options are from a previously constructed iterator. Initialization will not be repeated.

next() → {ICAL.Time}

Retrieve the next occurrence from the iterator.

Returns:
Type: 
ICAL.Time

ruleDayOfWeek(dow, aWeekStartopt)

Parameters:
NameTypeAttributesDescription
dow

(eg: '1TU', '-1MO')

aWeekStartICAL.Time.weekDay<optional>

The week start weekday

Returns:

[pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart

toJSON() → {Object}

Convert iterator into a serialize-able object. Will preserve current iteration sequence to ensure the seamless continuation of the recurrence rule.

Returns:
Type: 
Object
\ No newline at end of file +
On this page

ICAL. RecurIterator

An iterator for a single recurrence rule. This class usually doesn't have to be instanciated directly, the convenience method ICAL.Recur#iterator can be used.

Constructor

new RecurIterator(options)

Creates a new ICAL.RecurIterator instance. The options object may contain additional members when resuming iteration from a previous run.

Parameters:
NameTypeDescription
optionsObject

The iterator options

Properties
NameTypeAttributesDescription
ruleICAL.Recur

The rule to iterate.

dtstartICAL.Time

The start date of the event.

initializedBoolean<optional>

When true, assume that options are from a previously constructed iterator. Initialization will not be repeated.

Members

completed :Boolean

True when iteration is finished.

Type:
  • Boolean

dtstart :ICAL.Time

The start date of the event being iterated.

last :ICAL.Time

The last occurrence that was returned from the ICAL.RecurIterator#next method.

occurrence_number :Number

The sequence number from the occurrence

Type:
  • Number

rule :ICAL.Recur

The rule that is being iterated

Methods

fromData(options)

Initialize the recurrence iterator from the passed data object. This method is usually not called directly, you can initialize the iterator through the constructor.

Parameters:
NameTypeDescription
optionsObject

The iterator options

Properties
NameTypeAttributesDescription
ruleICAL.Recur

The rule to iterate.

dtstartICAL.Time

The start date of the event.

initializedBoolean<optional>

When true, assume that options are from a previously constructed iterator. Initialization will not be repeated.

next() → {ICAL.Time}

Retrieve the next occurrence from the iterator.

Returns:
Type: 
ICAL.Time

ruleDayOfWeek(dow, aWeekStartopt)

Parameters:
NameTypeAttributesDescription
dow

(eg: '1TU', '-1MO')

aWeekStartICAL.Time.weekDay<optional>

The week start weekday

Returns:

[pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart

toJSON() → {Object}

Convert iterator into a serialize-able object. Will preserve current iteration sequence to ensure the seamless continuation of the recurrence rule.

Returns:
Type: 
Object
\ No newline at end of file diff --git a/api/recur_iterator.js.html b/api/recur_iterator.js.html index 6ef66645..6e3a2ed2 100644 --- a/api/recur_iterator.js.html +++ b/api/recur_iterator.js.html @@ -235,7 +235,7 @@ this.last.second = this.setup_defaults("BYSECOND", "SECONDLY", this.dtstart.second); this.last.minute = this.setup_defaults("BYMINUTE", "MINUTELY", this.dtstart.minute); this.last.hour = this.setup_defaults("BYHOUR", "HOURLY", this.dtstart.hour); - let dayOffset = this.last.day = this.setup_defaults("BYMONTHDAY", "DAILY", this.dtstart.day); + this.last.day = this.setup_defaults("BYMONTHDAY", "DAILY", this.dtstart.day); this.last.month = this.setup_defaults("BYMONTH", "MONTHLY", this.dtstart.month); if (this.rule.freq == "WEEKLY") { @@ -264,77 +264,79 @@ this._nextByYearDay(); } - if (this.rule.freq == "MONTHLY" && this.has_by_data("BYDAY")) { - let tempLast = null; - let initLast = this.last.clone(); - let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); - - // Check every weekday in BYDAY with relative dow and pos. - for (let bydow of this.by_data.BYDAY) { - this.last = initLast.clone(); - let [pos, dow] = this.ruleDayOfWeek(bydow); - let dayOfMonth = this.last.nthWeekDay(dow, pos); - - // If |pos| >= 6, the byday is invalid for a monthly rule. - if (pos >= 6 || pos <= -6) { - throw new Error("Malformed values in BYDAY part"); - } + if (this.rule.freq == "MONTHLY") { + if (this.has_by_data("BYDAY")) { + let tempLast = null; + let initLast = this.last.clone(); + let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + + // Check every weekday in BYDAY with relative dow and pos. + for (let bydow of this.by_data.BYDAY) { + this.last = initLast.clone(); + let [pos, dow] = this.ruleDayOfWeek(bydow); + let dayOfMonth = this.last.nthWeekDay(dow, pos); + + // If |pos| >= 6, the byday is invalid for a monthly rule. + if (pos >= 6 || pos <= -6) { + throw new Error("Malformed values in BYDAY part"); + } - // If a Byday with pos=+/-5 is not in the current month it - // must be searched in the next months. - if (dayOfMonth > daysInMonth || dayOfMonth <= 0) { - // Skip if we have already found a "last" in this month. - if (tempLast && tempLast.month == initLast.month) { - continue; + // If a Byday with pos=+/-5 is not in the current month it + // must be searched in the next months. + if (dayOfMonth > daysInMonth || dayOfMonth <= 0) { + // Skip if we have already found a "last" in this month. + if (tempLast && tempLast.month == initLast.month) { + continue; + } + while (dayOfMonth > daysInMonth || dayOfMonth <= 0) { + this.increment_month(); + daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + dayOfMonth = this.last.nthWeekDay(dow, pos); + } } - while (dayOfMonth > daysInMonth || dayOfMonth <= 0) { - this.increment_month(); - daysInMonth = Time.daysInMonth(this.last.month, this.last.year); - dayOfMonth = this.last.nthWeekDay(dow, pos); + + this.last.day = dayOfMonth; + if (!tempLast || this.last.compare(tempLast) < 0) { + tempLast = this.last.clone(); } } - - this.last.day = dayOfMonth; - if (!tempLast || this.last.compare(tempLast) < 0) { - tempLast = this.last.clone(); + this.last = tempLast.clone(); + + //XXX: This feels like a hack, but we need to initialize + // the BYMONTHDAY case correctly and byDayAndMonthDay handles + // this case. It accepts a special flag which will avoid incrementing + // the initial value without the flag days that match the start time + // would be missed. + if (this.has_by_data('BYMONTHDAY')) { + this._byDayAndMonthDay(true); } - } - this.last = tempLast.clone(); - - //XXX: This feels like a hack, but we need to initialize - // the BYMONTHDAY case correctly and byDayAndMonthDay handles - // this case. It accepts a special flag which will avoid incrementing - // the initial value without the flag days that match the start time - // would be missed. - if (this.has_by_data('BYMONTHDAY')) { - this._byDayAndMonthDay(true); - } - - if (this.last.day > daysInMonth || this.last.day == 0) { - throw new Error("Malformed values in BYDAY part"); - } - } else if (this.has_by_data("BYMONTHDAY")) { - // Attempting to access `this.last.day` will cause the date to be normalised. - // So it will never be a negative value or more than the number of days in the month. - // We keep the value in a separate variable instead. - // Change the day value so that normalisation won't change the month. - this.last.day = 1; - let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + if (this.last.day > daysInMonth || this.last.day == 0) { + throw new Error("Malformed values in BYDAY part"); + } + } else if (this.has_by_data("BYMONTHDAY")) { + // Change the day value so that normalisation won't change the month. + this.last.day = 1; - if (dayOffset < 0) { - // A negative value represents days before the end of the month. - this.last.day = daysInMonth + dayOffset + 1; - } else if (this.by_data.BYMONTHDAY[0] > daysInMonth) { - // There's no occurrence in this month, find the next valid month. - // The longest possible sequence of skipped months is February-April-June, - // so we might need to call next_month up to three times. - if (!this.next_month() && !this.next_month() && !this.next_month()) { - throw new Error("No possible occurrences"); + // Get a sorted list of days in the starting month that match the rule. + let normalized = this.normalizeByMonthDayRules( + this.last.year, + this.last.month, + this.rule.parts.BYMONTHDAY + ).filter(d => d >= this.last.day); + + if (normalized.length) { + // There's at least one valid day, use it. + this.last.day = normalized[0]; + this.by_data.BYMONTHDAY = normalized; + } else { + // There's no occurrence in this month, find the next valid month. + // The longest possible sequence of skipped months is February-April-June, + // so we might need to call next_month up to three times. + if (!this.next_month() && !this.next_month() && !this.next_month()) { + throw new Error("No possible occurrences"); + } } - } else { - // Otherwise, reset the day. - this.last.day = dayOffset; } } } @@ -515,7 +517,10 @@ let rule; for (; ruleIdx < len; ruleIdx++) { - rule = rules[ruleIdx]; + rule = parseInt(rules[ruleIdx], 10); + if (isNaN(rule)) { + throw new Error('Invalid BYMONTHDAY value'); + } // if this rule falls outside of given // month discard it. @@ -749,6 +754,9 @@ if (this.by_indices.BYMONTHDAY >= this.by_data.BYMONTHDAY.length) { this.by_indices.BYMONTHDAY = 0; this.increment_month(); + if (this.by_indices.BYMONTHDAY >= this.by_data.BYMONTHDAY.length) { + return 0; + } } let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); @@ -764,7 +772,6 @@ } else { this.last.day = day; } - } else { this.increment_month(); let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); @@ -837,7 +844,6 @@ } next_year() { - if (this.next_hour() == 0) { return 0; } @@ -846,6 +852,13 @@ this.days_index = 0; do { this.increment_year(this.rule.interval); + if (this.has_by_data("BYMONTHDAY")) { + this.by_data.BYMONTHDAY = this.normalizeByMonthDayRules( + this.last.year, + this.last.month, + this.rule.parts.BYMONTHDAY + ); + } this.expand_year_days(this.last.year); } while (this.days.length == 0); } @@ -856,19 +869,19 @@ } _nextByYearDay() { - let doy = this.days[this.days_index]; - let year = this.last.year; - if (doy < 1) { - // Time.fromDayOfYear(doy, year) indexes relative to the - // start of the given year. That is different from the - // semantics of BYYEARDAY where negative indexes are an - // offset from the end of the given year. - doy += 1; - year += 1; - } - let next = Time.fromDayOfYear(doy, year); - this.last.day = next.day; - this.last.month = next.month; + let doy = this.days[this.days_index]; + let year = this.last.year; + if (doy < 1) { + // Time.fromDayOfYear(doy, year) indexes relative to the + // start of the given year. That is different from the + // semantics of BYYEARDAY where negative indexes are an + // offset from the end of the given year. + doy += 1; + year += 1; + } + let next = Time.fromDayOfYear(doy, year); + this.last.day = next.day; + this.last.month = next.month; } /** @@ -955,9 +968,19 @@ this.increment_year(years); } } + + if (this.has_by_data("BYMONTHDAY")) { + this.by_data.BYMONTHDAY = this.normalizeByMonthDayRules( + this.last.year, + this.last.month, + this.rule.parts.BYMONTHDAY + ); + } } increment_year(inc) { + // Don't jump into the next month if this.last is Feb 29. + this.last.day = 1; this.last.year += inc; } @@ -1179,6 +1202,14 @@ } else { this.days = []; } + + let daysInYear = Time.isLeapYear(aYear) ? 366 : 365; + this.days.sort((a, b) => { + if (a < 0) a += daysInYear + 1; + if (b < 0) b += daysInYear + 1; + return a - b; + }); + return 0; }