// Copyright (C) 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.ical.iter;
import com.google.ical.values.DateValue;
import com.google.ical.values.TimeValue;
import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum;
import com.google.ical.util.DTBuilder;
import com.google.ical.util.Predicate;
import com.google.ical.util.Predicates;
import com.google.ical.util.TimeUtils;
/**
* predicates used to filter out dates produced by a generator that do not
* pass some secondary criterion. For example, the recurrence rule
* <tt>FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13</tt> should generate every friday the
* 13th. It is implemented as a generator that generates the 13th of every
* month -- a byMonthDay generator, and then the results of that are filtered
* by a byDayFilter that tests whether the date falls on Friday.
*
* <p>A filter returns true to indicate the item is included in the
* recurrence.</p>
*
* @author mikesamuel+svn@gmail.com (Mike Samuel)
*/
class Filters {
/**
* constructs a day filter based on a BYDAY rule.
* @param days non null
* @param weeksInYear are the week numbers meant to be weeks in the
* current year, or weeks in the current month.
*/
static Predicate<DateValue> byDayFilter(
final WeekdayNum[] days, final boolean weeksInYear, final Weekday wkst) {
return new Predicate<DateValue>() {
public boolean apply(DateValue date) {
Weekday dow = Weekday.valueOf(date);
int nDays;
// first day of the week in the given year or month
Weekday dow0;
// where does date appear in the year or month?
// in [0, lengthOfMonthOrYear - 1]
int instance;
if (weeksInYear) {
nDays = TimeUtils.yearLength(date.year());
dow0 = Weekday.firstDayOfWeekInMonth(date.year(), 1);
instance = TimeUtils.dayOfYear(
date.year(), date.month(), date.day());
} else {
nDays = TimeUtils.monthLength(date.year(), date.month());
dow0 = Weekday.firstDayOfWeekInMonth(date.year(), date.month());
instance = date.day() - 1;
}
// which week of the year or month does this date fall on?
// one-indexed
int dateWeekNo;
if (wkst.javaDayNum <= dow.javaDayNum) {
dateWeekNo = 1 + (instance / 7);
} else {
dateWeekNo = (instance / 7);
}
// TODO(msamuel): according to section 4.3.10
// Week number one of the calendar year is the first week which
// contains at least four (4) days in that calendar year. This
// rule part is only valid for YEARLY rules.
// That's mentioned under the BYWEEKNO rule, and there's no mention
// of it in the earlier discussion of the BYDAY rule.
// Does it apply to yearly week numbers calculated for BYDAY rules in
// a FREQ=YEARLY rule?
for (int i = days.length; --i >= 0;) {
WeekdayNum day = days[i];
if (day.wday == dow) {
int weekNo = day.num;
if (0 == weekNo) { return true; }
if (weekNo < 0) {
weekNo = Util.invertWeekdayNum(day, dow0, nDays);
}
if (dateWeekNo == weekNo) { return true; }
}
}
return false;
}
};
}
/**
* constructs a day filter based on a BYDAY rule.
* @param monthDays days of the month in [-31, 31] != 0
*/
static Predicate<DateValue> byMonthDayFilter(final int[] monthDays) {
return new Predicate<DateValue>() {
public boolean apply(DateValue date) {
int nDays = TimeUtils.monthLength(date.year(), date.month());
for (int i = monthDays.length; --i >= 0;) {
int day = monthDays[i];
if (day < 0) { day += nDays + 1; }
if (day == date.day()) { return true; }
}
return false;
}
};
}
/**
* constructs a filter that accepts only every interval-th week from the week
* containing dtStart.
* @param interval > 0 number of weeks
* @param wkst day of the week that the week starts on.
* @param dtStart non null
*/
static Predicate<DateValue> weekIntervalFilter(
final int interval, final Weekday wkst, final DateValue dtStart) {
return new Predicate<DateValue>() {
DateValue wkStart;
{
// the latest day with day of week wkst on or before dtStart
DTBuilder wkStartB = new DTBuilder(dtStart);
wkStartB.day -=
(7 + Weekday.valueOf(dtStart).javaDayNum - wkst.javaDayNum) % 7;
wkStart = wkStartB.toDate();
}
public boolean apply(DateValue date) {
int daysBetween = TimeUtils.daysBetween(date, wkStart);
if (daysBetween < 0) {
// date must be before dtStart. Shouldn't occur in practice.
daysBetween += (interval * 7 * (1 + daysBetween / (-7 * interval)));
}
int off = (daysBetween / 7) % interval;
return 0 == off;
}
};
}
private static final int LOW_24_BITS = ~(-1 << 24);
private static final long LOW_60_BITS = ~(-1L << 60);
/**
* constructs an hour filter based on a BYHOUR rule.
* @param hours hours of the day in [0, 23]
*/
static Predicate<DateValue> byHourFilter(int[] hours) {
int hoursByBit = 0;
for (int hour : hours) { hoursByBit |= 1 << hour; }
if ((hoursByBit & LOW_24_BITS) == LOW_24_BITS) {
return Predicates.alwaysTrue();
}
final int bitField = hoursByBit;
return new Predicate<DateValue>() {
public boolean apply(DateValue date) {
if (!(date instanceof TimeValue)) { return false; }
TimeValue tv = (TimeValue) date;
return (bitField & (1 << tv.hour())) != 0;
}
};
}
/**
* constructs a minute filter based on a BYMINUTE rule.
* @param minutes minutes of the hour in [0, 59]
*/
static Predicate<DateValue> byMinuteFilter(int[] minutes) {
long minutesByBit = 0;
for (int minute : minutes) { minutesByBit |= 1L << minute; }
if ((minutesByBit & LOW_60_BITS) == LOW_60_BITS) {
return Predicates.alwaysTrue();
}
final long bitField = minutesByBit;
return new Predicate<DateValue>() {
public boolean apply(DateValue date) {
if (!(date instanceof TimeValue)) { return false; }
TimeValue tv = (TimeValue) date;
return (bitField & (1L << tv.minute())) != 0;
}
};
}
/**
* constructs a second filter based on a BYMINUTE rule.
* @param seconds seconds of the minute in [0, 59]
*/
static Predicate<DateValue> bySecondFilter(int[] seconds) {
long secondsByBit = 0;
for (int second : seconds) { secondsByBit |= 1L << second; }
if ((secondsByBit & LOW_60_BITS) == LOW_60_BITS) {
return Predicates.alwaysTrue();
}
final long bitField = secondsByBit;
return new Predicate<DateValue>() {
public boolean apply(DateValue date) {
if (!(date instanceof TimeValue)) { return false; }
TimeValue tv = (TimeValue) date;
return (bitField & (1L << tv.second())) != 0;
}
};
}
private Filters() {
// uninstantiable
}
}