RecurrenceProcessorpublic class RecurrenceProcessor extends Object
Fields Summary |
---|
private android.text.format.Time | mIterator | private android.text.format.Time | mUntil | private StringBuilder | mStringBuilder | private android.text.format.Time | mGenerated | private DaySet | mDays | private static final int | MAX_ALLOWED_ITERATIONS | private static final String | TAG | private static final boolean | SPEW | private static final int | USE_ITERATOR | private static final int | USE_BYLIST | private static final int[] | DAYS_PER_MONTH | private static final int[] | DAYS_IN_YEAR_PRECEDING_MONTH |
Constructors Summary |
---|
public RecurrenceProcessor()
|
Methods Summary |
---|
public long[] | expand(android.text.format.Time dtstart, RecurrenceSet recur, long rangeStartMillis, long rangeEndMillis)Expands the recurrence within the given range using the given dtstart
value. Returns an array of longs where each element is a date in UTC
milliseconds. The return value is never null. If there are no dates
then an array of length zero is returned.
String timezone = dtstart.timezone;
mIterator.clear(timezone);
mGenerated.clear(timezone);
// We don't need to clear the mUntil (and it wouldn't do any good to
// do so) because the "until" date string is specified in UTC and that
// sets the timezone in the mUntil Time object.
mIterator.set(rangeStartMillis);
long rangeStartDateValue = normDateTimeComparisonValue(mIterator);
long rangeEndDateValue;
if (rangeEndMillis != -1) {
mIterator.set(rangeEndMillis);
rangeEndDateValue = normDateTimeComparisonValue(mIterator);
} else {
rangeEndDateValue = Long.MAX_VALUE;
}
TreeSet<Long> dtSet = new TreeSet<Long>();
if (recur.rrules != null) {
for (EventRecurrence rrule : recur.rrules) {
expand(dtstart, rrule, rangeStartDateValue,
rangeEndDateValue, true /* add */, dtSet);
}
}
if (recur.rdates != null) {
for (long dt : recur.rdates) {
// The dates are stored as milliseconds. We need to convert
// them to year/month/day values in the local timezone.
mIterator.set(dt);
long dtvalue = normDateTimeComparisonValue(mIterator);
dtSet.add(dtvalue);
}
}
if (recur.exrules != null) {
for (EventRecurrence exrule : recur.exrules) {
expand(dtstart, exrule, rangeStartDateValue,
rangeEndDateValue, false /* remove */, dtSet);
}
}
if (recur.exdates != null) {
for (long dt : recur.exdates) {
// The dates are stored as milliseconds. We need to convert
// them to year/month/day values in the local timezone.
mIterator.set(dt);
long dtvalue = normDateTimeComparisonValue(mIterator);
dtSet.remove(dtvalue);
}
}
if (dtSet.isEmpty()) {
// this can happen if the recurrence does not occur within the
// expansion window.
return new long[0];
}
// The values in dtSet are represented in a special form that is useful
// for fast comparisons and that is easy to generate from year/month/day
// values. We need to convert these to UTC milliseconds and also to
// ensure that the dates are valid.
int len = dtSet.size();
long[] dates = new long[len];
int i = 0;
for (Long val: dtSet) {
setTimeFromLongValue(mIterator, val);
dates[i++] = mIterator.toMillis(true /* ignore isDst */);
}
return dates;
| public void | expand(android.text.format.Time dtstart, EventRecurrence r, long rangeStartDateValue, long rangeEndDateValue, boolean add, java.util.TreeSet out)Run the recurrence algorithm. Processes events defined in the local
timezone of the event. Return a list of iCalendar DATETIME
strings containing the start date/times of the occurrences; the output
times are defined in the local timezone of the event.
If you want all of the events, pass Long.MAX_VALUE for rangeEndDateValue. If you pass
Long.MAX_VALUE for rangeEnd, and the event doesn't have a COUNT or UNTIL field,
you'll get a DateException.
unsafeNormalize(dtstart);
long dtstartDateValue = normDateTimeComparisonValue(dtstart);
int count = 0;
// add the dtstart instance to the recurrence, if within range.
// For example, if dtstart is Mar 1, 2010 and the range is Jan 1 - Apr 1,
// then add it here and increment count. If the range is earlier or later,
// then don't add it here. In that case, count will be incremented later
// inside the loop. It is important that count gets incremented exactly
// once here or in the loop for dtstart.
//
// NOTE: if DTSTART is not synchronized with the recurrence rule, the first instance
// we return will not fit the RRULE pattern.
if (add && dtstartDateValue >= rangeStartDateValue
&& dtstartDateValue < rangeEndDateValue) {
out.add(dtstartDateValue);
++count;
}
Time iterator = mIterator;
Time until = mUntil;
StringBuilder sb = mStringBuilder;
Time generated = mGenerated;
DaySet days = mDays;
try {
days.setRecurrence(r);
if (rangeEndDateValue == Long.MAX_VALUE && r.until == null && r.count == 0) {
throw new DateException(
"No range end provided for a recurrence that has no UNTIL or COUNT.");
}
// the top-level frequency
int freqField;
int freqAmount = r.interval;
int freq = r.freq;
switch (freq)
{
case EventRecurrence.SECONDLY:
freqField = Time.SECOND;
break;
case EventRecurrence.MINUTELY:
freqField = Time.MINUTE;
break;
case EventRecurrence.HOURLY:
freqField = Time.HOUR;
break;
case EventRecurrence.DAILY:
freqField = Time.MONTH_DAY;
break;
case EventRecurrence.WEEKLY:
freqField = Time.MONTH_DAY;
freqAmount = 7 * r.interval;
if (freqAmount <= 0) {
freqAmount = 7;
}
break;
case EventRecurrence.MONTHLY:
freqField = Time.MONTH;
break;
case EventRecurrence.YEARLY:
freqField = Time.YEAR;
break;
default:
throw new DateException("bad freq=" + freq);
}
if (freqAmount <= 0) {
freqAmount = 1;
}
int bymonthCount = r.bymonthCount;
boolean usebymonth = useBYX(freq, EventRecurrence.MONTHLY, bymonthCount);
boolean useDays = freq >= EventRecurrence.WEEKLY &&
(r.bydayCount > 0 || r.bymonthdayCount > 0);
int byhourCount = r.byhourCount;
boolean usebyhour = useBYX(freq, EventRecurrence.HOURLY, byhourCount);
int byminuteCount = r.byminuteCount;
boolean usebyminute = useBYX(freq, EventRecurrence.MINUTELY, byminuteCount);
int bysecondCount = r.bysecondCount;
boolean usebysecond = useBYX(freq, EventRecurrence.SECONDLY, bysecondCount);
// initialize the iterator
iterator.set(dtstart);
if (freqField == Time.MONTH) {
if (useDays) {
// if it's monthly, and we're going to be generating
// days, set the iterator day field to 1 because sometimes
// we'll skip months if it's greater than 28.
// XXX Do we generate days for MONTHLY w/ BYHOUR? If so,
// we need to do this then too.
iterator.monthDay = 1;
}
}
long untilDateValue;
if (r.until != null) {
// Ensure that the "until" date string is specified in UTC.
String untilStr = r.until;
// 15 is length of date-time without trailing Z e.g. "20090204T075959"
// A string such as 20090204 is a valid UNTIL (see RFC 2445) and the
// Z should not be added.
if (untilStr.length() == 15) {
untilStr = untilStr + 'Z";
}
// The parse() method will set the timezone to UTC
until.parse(untilStr);
// We need the "until" year/month/day values to be in the same
// timezone as all the generated dates so that we can compare them
// using the values returned by normDateTimeComparisonValue().
until.switchTimezone(dtstart.timezone);
untilDateValue = normDateTimeComparisonValue(until);
} else {
untilDateValue = Long.MAX_VALUE;
}
sb.ensureCapacity(15);
sb.setLength(15); // TODO: pay attention to whether or not the event
// is an all-day one.
if (SPEW) {
Log.i(TAG, "expand called w/ rangeStart=" + rangeStartDateValue
+ " rangeEnd=" + rangeEndDateValue);
}
// go until the end of the range or we're done with this event
boolean eventEnded = false;
int failsafe = 0; // Avoid infinite loops
events: {
while (true) {
int monthIndex = 0;
if (failsafe++ > MAX_ALLOWED_ITERATIONS) { // Give up after about 1 second of processing
Log.w(TAG, "Recurrence processing stuck with r=" + r + " rangeStart="
+ rangeStartDateValue + " rangeEnd=" + rangeEndDateValue);
break;
}
unsafeNormalize(iterator);
int iteratorYear = iterator.year;
int iteratorMonth = iterator.month + 1;
int iteratorDay = iterator.monthDay;
int iteratorHour = iterator.hour;
int iteratorMinute = iterator.minute;
int iteratorSecond = iterator.second;
// year is never expanded -- there is no BYYEAR
generated.set(iterator);
if (SPEW) Log.i(TAG, "year=" + generated.year);
do { // month
int month = usebymonth
? r.bymonth[monthIndex]
: iteratorMonth;
month--;
if (SPEW) Log.i(TAG, " month=" + month);
int dayIndex = 1;
int lastDayToExamine = 0;
// Use this to handle weeks that overlap the end of the month.
// Keep the year and month that days is for, and generate it
// when needed in the loop
if (useDays) {
// Determine where to start and end, don't worry if this happens
// to be before dtstart or after the end, because that will be
// filtered in the inner loop
if (freq == EventRecurrence.WEEKLY) {
/*
* iterator.weekDay indicates the day of the week (0-6, SU-SA).
* Because dayIndex might start in the middle of a week, and we're
* interested in treating a week as a unit, we want to move
* backward to the start of the week. (This could make the
* dayIndex negative, which will be corrected by normalization
* later on.)
*
* The day that starts the week is determined by WKST, which
* defaults to MO.
*
* Example: dayIndex is Tuesday the 8th, and weeks start on
* Thursdays. Tuesday is day 2, Thursday is day 4, so we
* want to move back (2 - 4 + 7) % 7 = 5 days to the previous
* Thursday. If weeks started on Mondays, we would only
* need to move back (2 - 1 + 7) % 7 = 1 day.
*/
int weekStartAdj = (iterator.weekDay -
EventRecurrence.day2TimeDay(r.wkst) + 7) % 7;
dayIndex = iterator.monthDay - weekStartAdj;
lastDayToExamine = dayIndex + 6;
} else {
lastDayToExamine = generated
.getActualMaximum(Time.MONTH_DAY);
}
if (SPEW) Log.i(TAG, "dayIndex=" + dayIndex
+ " lastDayToExamine=" + lastDayToExamine
+ " days=" + days);
}
do { // day
int day;
if (useDays) {
if (!days.get(iterator, dayIndex)) {
dayIndex++;
continue;
} else {
day = dayIndex;
}
} else {
day = iteratorDay;
}
if (SPEW) Log.i(TAG, " day=" + day);
// hour
int hourIndex = 0;
do {
int hour = usebyhour
? r.byhour[hourIndex]
: iteratorHour;
if (SPEW) Log.i(TAG, " hour=" + hour + " usebyhour=" + usebyhour);
// minute
int minuteIndex = 0;
do {
int minute = usebyminute
? r.byminute[minuteIndex]
: iteratorMinute;
if (SPEW) Log.i(TAG, " minute=" + minute);
// second
int secondIndex = 0;
do {
int second = usebysecond
? r.bysecond[secondIndex]
: iteratorSecond;
if (SPEW) Log.i(TAG, " second=" + second);
// we do this here each time, because if we distribute it, we find the
// month advancing extra times, as we set the month to the 32nd, 33rd, etc.
// days.
generated.set(second, minute, hour, day, month, iteratorYear);
unsafeNormalize(generated);
long genDateValue = normDateTimeComparisonValue(generated);
// sometimes events get generated (BYDAY, BYHOUR, etc.) that
// are before dtstart. Filter these. I believe this is correct,
// but Google Calendar doesn't seem to always do this.
if (genDateValue >= dtstartDateValue) {
// filter and then add
// TODO: we don't check for stop conditions (like
// passing the "end" date) unless the filter
// allows the event. Could stop sooner.
int filtered = filter(r, generated);
if (0 == filtered) {
// increase the count as long
// as this isn't the same
// as the first instance
// specified by the DTSTART
// (for RRULEs -- additive).
// This condition must be the complement of the
// condition for incrementing count at the
// beginning of the method, so if we don't
// increment count there, we increment it here.
// For example, if add is set and dtstartDateValue
// is inside the start/end range, then it was added
// and count was incremented at the beginning.
// If dtstartDateValue is outside the range or add
// is not set, then we must increment count here.
if (!(dtstartDateValue == genDateValue
&& add
&& dtstartDateValue >= rangeStartDateValue
&& dtstartDateValue < rangeEndDateValue)) {
++count;
}
// one reason we can stop is that
// we're past the until date
if (genDateValue > untilDateValue) {
if (SPEW) {
Log.i(TAG, "stopping b/c until="
+ untilDateValue
+ " generated="
+ genDateValue);
}
break events;
}
// or we're past rangeEnd
if (genDateValue >= rangeEndDateValue) {
if (SPEW) {
Log.i(TAG, "stopping b/c rangeEnd="
+ rangeEndDateValue
+ " generated=" + generated);
}
break events;
}
if (genDateValue >= rangeStartDateValue) {
if (SPEW) {
Log.i(TAG, "adding date=" + generated + " filtered=" + filtered);
}
if (add) {
out.add(genDateValue);
} else {
out.remove(genDateValue);
}
}
// another is that count is high enough
if (r.count > 0 && r.count == count) {
//Log.i(TAG, "stopping b/c count=" + count);
break events;
}
}
}
secondIndex++;
} while (usebysecond && secondIndex < bysecondCount);
minuteIndex++;
} while (usebyminute && minuteIndex < byminuteCount);
hourIndex++;
} while (usebyhour && hourIndex < byhourCount);
dayIndex++;
} while (useDays && dayIndex <= lastDayToExamine);
monthIndex++;
} while (usebymonth && monthIndex < bymonthCount);
// Add freqAmount to freqField until we get another date that we want.
// We don't want to "generate" dates with the iterator.
// XXX: We do this for days, because there is a varying number of days
// per month
int oldDay = iterator.monthDay;
generated.set(iterator); // just using generated as a temporary.
int n = 1;
while (true) {
int value = freqAmount * n;
switch (freqField) {
case Time.SECOND:
iterator.second += value;
break;
case Time.MINUTE:
iterator.minute += value;
break;
case Time.HOUR:
iterator.hour += value;
break;
case Time.MONTH_DAY:
iterator.monthDay += value;
break;
case Time.MONTH:
iterator.month += value;
break;
case Time.YEAR:
iterator.year += value;
break;
case Time.WEEK_DAY:
iterator.monthDay += value;
break;
case Time.YEAR_DAY:
iterator.monthDay += value;
break;
default:
throw new RuntimeException("bad field=" + freqField);
}
unsafeNormalize(iterator);
if (freqField != Time.YEAR && freqField != Time.MONTH) {
break;
}
if (iterator.monthDay == oldDay) {
break;
}
n++;
iterator.set(generated);
}
}
}
}
catch (DateException e) {
Log.w(TAG, "DateException with r=" + r + " rangeStart=" + rangeStartDateValue
+ " rangeEnd=" + rangeEndDateValue);
throw e;
}
catch (RuntimeException t) {
Log.w(TAG, "RuntimeException with r=" + r + " rangeStart=" + rangeStartDateValue
+ " rangeEnd=" + rangeEndDateValue);
throw t;
}
| private static int | filter(EventRecurrence r, android.text.format.Time iterator)Filter out the ones for events whose BYxxx rule is for
a period greater than or equal to the period of the FREQ.
Returns 0 if the event should not be filtered out
Returns something else (a rule number which is useful for debugging)
if the event should not be returned
boolean found;
int freq = r.freq;
if (EventRecurrence.MONTHLY >= freq) {
// BYMONTH
if (r.bymonthCount > 0) {
found = listContains(r.bymonth, r.bymonthCount,
iterator.month + 1);
if (!found) {
return 1;
}
}
}
if (EventRecurrence.WEEKLY >= freq) {
// BYWEEK -- this is just a guess. I wonder how many events
// acutally use BYWEEKNO.
if (r.byweeknoCount > 0) {
found = listContains(r.byweekno, r.byweeknoCount,
iterator.getWeekNumber(),
iterator.getActualMaximum(Time.WEEK_NUM));
if (!found) {
return 2;
}
}
}
if (EventRecurrence.DAILY >= freq) {
// BYYEARDAY
if (r.byyeardayCount > 0) {
found = listContains(r.byyearday, r.byyeardayCount,
iterator.yearDay, iterator.getActualMaximum(Time.YEAR_DAY));
if (!found) {
return 3;
}
}
// BYMONTHDAY
if (r.bymonthdayCount > 0 ) {
found = listContains(r.bymonthday, r.bymonthdayCount,
iterator.monthDay,
iterator.getActualMaximum(Time.MONTH_DAY));
if (!found) {
return 4;
}
}
// BYDAY -- when filtering, we ignore the number field, because it
// only is meaningful when creating more events.
byday:
if (r.bydayCount > 0) {
int a[] = r.byday;
int N = r.bydayCount;
int v = EventRecurrence.timeDay2Day(iterator.weekDay);
for (int i=0; i<N; i++) {
if (a[i] == v) {
break byday;
}
}
return 5;
}
}
if (EventRecurrence.HOURLY >= freq) {
// BYHOUR
found = listContains(r.byhour, r.byhourCount,
iterator.hour,
iterator.getActualMaximum(Time.HOUR));
if (!found) {
return 6;
}
}
if (EventRecurrence.MINUTELY >= freq) {
// BYMINUTE
found = listContains(r.byminute, r.byminuteCount,
iterator.minute,
iterator.getActualMaximum(Time.MINUTE));
if (!found) {
return 7;
}
}
if (EventRecurrence.SECONDLY >= freq) {
// BYSECOND
found = listContains(r.bysecond, r.bysecondCount,
iterator.second,
iterator.getActualMaximum(Time.SECOND));
if (!found) {
return 8;
}
}
if (r.bysetposCount > 0) {
bysetpos:
// BYSETPOS - we only handle rules like FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1
if (freq == EventRecurrence.MONTHLY && r.bydayCount > 0) {
// Check for stuff like BYDAY=1TU
for (int i = r.bydayCount - 1; i >= 0; i--) {
if (r.bydayNum[i] != 0) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "BYSETPOS not supported with these rules: " + r);
}
break bysetpos;
}
}
if (!filterMonthlySetPos(r, iterator)) {
// Not allowed, filter it out.
return 9;
}
} else {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "BYSETPOS not supported with these rules: " + r);
}
}
// BYSETPOS was defined but we don't know how to handle it. Do no filtering based
// on it.
}
// if we got to here, we didn't filter it out
return 0;
| private static boolean | filterMonthlySetPos(EventRecurrence r, android.text.format.Time instance)Filters out instances that don't match the BYSETPOS clause of a monthly recurrence rule.
This is an awkward and inefficient way to go about it.
/*
* Compute the day of the week for the first day of the month. "instance" has a
* day number and a DotW, so we compute the DotW of the 1st from that. Note DotW
* is 0-6, where 0=SUNDAY.
*
* The basic calculation is to take the instance's "day of the week" number, subtract
* (day of the month - 1) mod 7, and then make sure it's positive. We can simplify
* that with some algebra.
*/
int dotw = (instance.weekDay - instance.monthDay + 36) % 7;
/*
* The byday[] values are specified as bits, so we can just OR them all
* together.
*/
int bydayMask = 0;
for (int i = 0; i < r.bydayCount; i++) {
bydayMask |= r.byday[i];
}
/*
* Generate a set according to the BYDAY rules. For each day of the month, determine
* if its day of the week is included. If so, append it to the day set.
*/
int maxDay = instance.getActualMaximum(Time.MONTH_DAY);
int daySet[] = new int[maxDay];
int daySetLength = 0;
for (int md = 1; md <= maxDay; md++) {
// For each month day, see if it's part of the set. (This makes some assumptions
// about the exact form of the DotW constants.)
int dayBit = EventRecurrence.SU << dotw;
if ((bydayMask & dayBit) != 0) {
daySet[daySetLength++] = md;
}
dotw++;
if (dotw == 7)
dotw = 0;
}
/*
* Now walk through the BYSETPOS list and see if the instance is equal to any of the
* specified daySet entries.
*/
for (int i = r.bysetposCount - 1; i >= 0; i--) {
int index = r.bysetpos[i];
if (index > 0) {
if (index > daySetLength) {
continue; // out of range
}
if (daySet[index-1] == instance.monthDay) {
return true;
}
} else if (index < 0) {
if (daySetLength + index < 0) {
continue; // out of range
}
if (daySet[daySetLength + index] == instance.monthDay) {
return true;
}
} else {
// should have been caught by parser
throw new RuntimeException("invalid bysetpos value");
}
}
return false;
| int | generateByList(int count, int freq, int byFreq)Return whether we should make this list from the BYxxx list or
from the component of the iterator.
if (byFreq >= freq) {
return USE_ITERATOR;
} else {
if (count == 0) {
return USE_ITERATOR;
} else {
return USE_BYLIST;
}
}
| public long | getLastOccurence(android.text.format.Time dtstart, RecurrenceSet recur)Returns the time (millis since epoch) of the last occurrence,
or -1 if the event repeats forever. If there are no occurrences
(because the exrule or exdates cancel all the occurrences) and the
event does not repeat forever, then 0 is returned.
This computes a conservative estimate of the last occurrence. That is,
the time of the actual last occurrence might be earlier than the time
returned by this method.
return getLastOccurence(dtstart, null /* no limit */, recur);
| public long | getLastOccurence(android.text.format.Time dtstart, android.text.format.Time maxtime, RecurrenceSet recur)Returns the time (millis since epoch) of the last occurrence,
or -1 if the event repeats forever. If there are no occurrences
(because the exrule or exdates cancel all the occurrences) and the
event does not repeat forever, then 0 is returned.
This computes a conservative estimate of the last occurrence. That is,
the time of the actual last occurrence might be earlier than the time
returned by this method.
long lastTime = -1;
boolean hasCount = false;
// first see if there are any "until"s specified. if so, use the latest
// until / rdate.
if (recur.rrules != null) {
for (EventRecurrence rrule : recur.rrules) {
if (rrule.count != 0) {
hasCount = true;
} else if (rrule.until != null) {
// according to RFC 2445, until must be in UTC.
mIterator.parse(rrule.until);
long untilTime = mIterator.toMillis(false /* use isDst */);
if (untilTime > lastTime) {
lastTime = untilTime;
}
}
}
if (lastTime != -1 && recur.rdates != null) {
for (long dt : recur.rdates) {
if (dt > lastTime) {
lastTime = dt;
}
}
}
// If there were only "until"s and no "count"s, then return the
// last "until" date or "rdate".
if (lastTime != -1 && !hasCount) {
return lastTime;
}
} else if (recur.rdates != null &&
recur.exrules == null && recur.exdates == null) {
// if there are only rdates, we can just pick the last one.
for (long dt : recur.rdates) {
if (dt > lastTime) {
lastTime = dt;
}
}
return lastTime;
}
// Expand the complete recurrence if there were any counts specified,
// or if there were rdates specified.
if (hasCount || recur.rdates != null || maxtime != null) {
// The expansion might not contain any dates if the exrule or
// exdates cancel all the generated dates.
long[] dates = expand(dtstart, recur,
dtstart.toMillis(false /* use isDst */) /* range start */,
(maxtime != null) ?
maxtime.toMillis(false /* use isDst */) : -1 /* range end */);
// The expansion might not contain any dates if exrule or exdates
// cancel all the generated dates.
if (dates.length == 0) {
return 0;
}
return dates[dates.length - 1];
}
return -1;
| static boolean | isLeapYear(int year)Returns true if the given year is a leap year.
return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
| private static boolean | listContains(int[] a, int N, int v)a -- list of values
N -- number of values to use in a
v -- value to check for
for (int i=0; i<N; i++) {
if (a[i] == v) {
return true;
}
}
return false;
| private static boolean | listContains(int[] a, int N, int v, int max)a -- list of values
N -- number of values to use in a
v -- value to check for
max -- if a value in a is negative, add that negative value
to max and compare that instead; this is how we deal with
negative numbers being offsets from the end value
for (int i=0; i<N; i++) {
int w = a[i];
if (w > 0) {
if (w == v) {
return true;
}
} else {
max += w; // w is negative
if (max == v) {
return true;
}
}
}
return false;
| static int | monthLength(int year, int month)Returns the number of days in the given month of the given year.
int n = DAYS_PER_MONTH[month];
if (n != 28) {
return n;
}
return isLeapYear(year) ? 29 : 28;
| private static final long | normDateTimeComparisonValue(android.text.format.Time normalized)Converts a normalized Time value to a 64-bit long. The mapping of Time
values to longs provides a total ordering on the Time values so that
two Time values can be compared efficiently by comparing their 64-bit
long values. This is faster than converting the Time values to UTC
millliseconds.
// 37 bits for the year, 4 bits for the month, 5 bits for the monthDay,
// 5 bits for the hour, 6 bits for the minute, 6 bits for the second.
return ((long)normalized.year << 26) + (normalized.month << 22)
+ (normalized.monthDay << 17) + (normalized.hour << 12)
+ (normalized.minute << 6) + normalized.second;
| private static final void | setTimeFromLongValue(android.text.format.Time date, long val)
date.year = (int) (val >> 26);
date.month = (int) (val >> 22) & 0xf;
date.monthDay = (int) (val >> 17) & 0x1f;
date.hour = (int) (val >> 12) & 0x1f;
date.minute = (int) (val >> 6) & 0x3f;
date.second = (int) (val & 0x3f);
| static void | unsafeNormalize(android.text.format.Time date)Normalizes the date fields to give a valid date, but if the time falls
in the invalid window during a transition out of Daylight Saving Time
when time jumps forward an hour, then the "normalized" value will be
invalid.
This method also computes the weekDay and yearDay fields.
This method does not modify the fields isDst, or gmtOff.
int second = date.second;
int minute = date.minute;
int hour = date.hour;
int monthDay = date.monthDay;
int month = date.month;
int year = date.year;
int addMinutes = ((second < 0) ? (second - 59) : second) / 60;
second -= addMinutes * 60;
minute += addMinutes;
int addHours = ((minute < 0) ? (minute - 59) : minute) / 60;
minute -= addHours * 60;
hour += addHours;
int addDays = ((hour < 0) ? (hour - 23) : hour) / 24;
hour -= addDays * 24;
monthDay += addDays;
// We want to make "monthDay" positive. We do this by subtracting one
// from the year and adding a year's worth of days to "monthDay" in
// the following loop while "monthDay" <= 0.
while (monthDay <= 0) {
// If month is after Feb, then add this year's length so that we
// include this year's leap day, if any.
// Otherwise (the month is Feb or earlier), add last year's length.
// Subtract one from the year in either case. This gives the same
// effective date but makes monthDay (the day of the month) much
// larger. Eventually (usually in one iteration) monthDay will
// be positive.
int days = month > 1 ? yearLength(year) : yearLength(year - 1);
monthDay += days;
year -= 1;
}
// At this point, monthDay >= 1. Normalize the month to the range [0,11].
if (month < 0) {
int years = (month + 1) / 12 - 1;
year += years;
month -= 12 * years;
} else if (month >= 12) {
int years = month / 12;
year += years;
month -= 12 * years;
}
// At this point, month is in the range [0,11] and monthDay >= 1.
// Now loop until the monthDay is in the correct range for the month.
while (true) {
// On January, check if we can jump forward a whole year.
if (month == 0) {
int yearLength = yearLength(year);
if (monthDay > yearLength) {
year++;
monthDay -= yearLength;
}
}
int monthLength = monthLength(year, month);
if (monthDay > monthLength) {
monthDay -= monthLength;
month++;
if (month >= 12) {
month -= 12;
year++;
}
} else break;
}
// At this point, monthDay <= the length of the current month and is
// in the range [1,31].
date.second = second;
date.minute = minute;
date.hour = hour;
date.monthDay = monthDay;
date.month = month;
date.year = year;
date.weekDay = weekDay(year, month, monthDay);
date.yearDay = yearDay(year, month, monthDay);
| private static boolean | useBYX(int freq, int freqConstant, int count)
return freq > freqConstant && count > 0;
| static int | weekDay(int year, int month, int day)Computes the weekday, a number in the range [0,6] where Sunday=0, from
the given year, month, and day.
if (month <= 1) {
month += 12;
year -= 1;
}
return (day + (13 * month - 14) / 5 + year + year/4 - year/100 + year/400) % 7;
| static int | yearDay(int year, int month, int day)Computes the 0-based "year day", given the year, month, and day.
int yearDay = DAYS_IN_YEAR_PRECEDING_MONTH[month] + day - 1;
if (month >= 2 && isLeapYear(year)) {
yearDay += 1;
}
return yearDay;
| static int | yearLength(int year)Returns the number of days in the given year.
return isLeapYear(year) ? 366 : 365;
|
|