if (done) { return false; }
// (1) Make sure that builder is appropriately initialized so that
// we only generate instances in the next set
DateValue d0 = null;
if (null != pushback) {
d0 = pushback;
builder.year = d0.year();
builder.month = d0.month();
builder.day = d0.day();
pushback = null;
} else if (!first) {
// we need to skip ahead to the next item since we didn't exhaust
// the last period
switch (freq) {
case YEARLY:
if (!yearGenerator.generate(builder)) { return false; }
// $FALL-THROUGH$
case MONTHLY:
while (!monthGenerator.generate(builder)) {
if (!yearGenerator.generate(builder)) { return false; }
}
break;
case WEEKLY:
// consume because just incrementing date doesn't do anything
DateValue nextWeek =
Util.nextWeekStart(builder.toDateTime(), wkst);
do {
if (!serialInstanceGenerator.generate(builder)) {
return false;
}
} while (builder.compareTo(nextWeek) < 0);
d0 = builder.toDateTime();
break;
default:
break;
}
} else {
first = false;
}
// (2) Build a set of the dates in the year/month/week that match
// the other rule.
List<DateValue> dates = new ArrayList<DateValue>();
if (null != d0) { dates.add(d0); }
// Optimization: if min(bySetPos) > 0 then we already have absolute
// positions, so we don't need to generate all of the instances for
// the period.
// This speeds up things like the first weekday of the year:
// RRULE:FREQ=YEARLY;BYDAY=MO,TU,WE,TH,FR,BYSETPOS=1
// that would otherwise generate 260+ instances per one emitted
// TODO(msamuel): this may be premature. If needed, We could
// improve more generally by inferring a BYMONTH generator based on
// distribution of set positions within the year.
int limit = allPositive ? maxPos : Integer.MAX_VALUE;
while (limit > dates.size()) {
if (!serialInstanceGenerator.generate(builder)) {
// If we can't generate any, then make sure we return false
// once the instances we have generated are exhausted.
// If this is returning false due to some artificial limit, such
// as the 100 year limit in serialYearGenerator, then we exit
// via an exception because otherwise we would pick the wrong
// elements for some uSetPoses that contain negative elements.
done = true;
break;
}
DateValue d = builder.toDateTime();
boolean contained = false;
if (null == d0) {
d0 = d;
contained = true;
} else {
switch (freq) {
case WEEKLY:
int nb = TimeUtils.daysBetween(d, d0);
// Two dates (d, d0) are in the same week
// if there isn't a whole week in between them and the
// later day is later in the week than the earlier day.
contained =
nb < 7
&& ((7 + Weekday.valueOf(d).javaDayNum
- wkst.javaDayNum) % 7)
> ((7 + Weekday.valueOf(d0).javaDayNum
- wkst.javaDayNum) % 7);
break;
case MONTHLY:
contained =
d0.month() == d.month() && d0.year() == d.year();
break;
case YEARLY:
contained = d0.year() == d.year();
break;
default:
done = true;
return false;
}
}
if (contained) {
dates.add(d);
} else {
// reached end of the set
pushback = d; // save d so we can use it later
break;
}
}
// (3) Resolve the positions to absolute positions and order them
int[] absSetPos;
if (allPositive) {
absSetPos = uSetPos;
} else {
IntSet uAbsSetPos = new IntSet();
for (int j = 0; j < uSetPos.length; ++j) {
int p = uSetPos[j];
if (p < 0) { p = dates.size() + p + 1; }
uAbsSetPos.add(p);
}
absSetPos = uAbsSetPos.toIntArray();
}
candidates = new ArrayList<DateValue>();
for (int p : absSetPos) {
if (p >= 1 && p <= dates.size()) { // p is 1-indexed
candidates.add(dates.get(p - 1));
}
}
i = 0;
if (candidates.isEmpty()) {
// none in this region, so keep looking
candidates = null;
continue;
}
}
// (5) Emit a date. It will be checked against the end condition and
// dtStart elsewhere
DateValue d = candidates.get(i++);
builder.year = d.year();
builder.month = d.month();
builder.day = d.day();
if (d instanceof TimeValue) {
TimeValue t = (TimeValue) d;
builder.hour = t.hour();
builder.minute = t.minute();
builder.second = t.second();