0001: /*
0002: * Copyright (c) 2000, Columbia University. All rights reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * 1. Redistributions of source code must retain the above copyright
0008: * notice, this list of conditions and the following disclaimer.
0009: *
0010: * 2. Redistributions in binary form must reproduce the above copyright
0011: * notice, this list of conditions and the following disclaimer in the
0012: * documentation and/or other materials provided with the distribution.
0013: *
0014: * 3. Neither the name of the University nor the names of its contributors
0015: * may be used to endorse or promote products derived from this software
0016: * without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
0019: * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
0020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
0022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
0025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
0026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
0027: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
0028: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: */
0030:
0031: package com.liferay.portal.kernel.cal;
0032:
0033: import com.liferay.portal.kernel.util.CalendarFactoryUtil;
0034: import com.liferay.portal.kernel.util.StringMaker;
0035:
0036: import java.io.Serializable;
0037:
0038: import java.util.Calendar;
0039: import java.util.Date;
0040: import java.util.TimeZone;
0041:
0042: /**
0043: * <a href="Recurrence.java.html"><b><i>View Source</i></b></a>
0044: *
0045: * @author Jonathan Lennox
0046: *
0047: */
0048: public class Recurrence implements Serializable {
0049:
0050: /**
0051: * Field DAILY
0052: */
0053: public final static int DAILY = 3;
0054:
0055: /**
0056: * Field WEEKLY
0057: */
0058: public final static int WEEKLY = 4;
0059:
0060: /**
0061: * Field MONTHLY
0062: */
0063: public final static int MONTHLY = 5;
0064:
0065: /**
0066: * Field YEARLY
0067: */
0068: public final static int YEARLY = 6;
0069:
0070: /**
0071: * Field NO_RECURRENCE
0072: */
0073: public final static int NO_RECURRENCE = 7;
0074:
0075: /**
0076: * Field dtStart
0077: */
0078: protected Calendar dtStart;
0079:
0080: /**
0081: * Field duration
0082: */
0083: protected Duration duration;
0084:
0085: /**
0086: * Field frequency
0087: */
0088: protected int frequency;
0089:
0090: /**
0091: * Field interval
0092: */
0093: protected int interval;
0094:
0095: /**
0096: * Field interval
0097: */
0098: protected int occurrence = 0;
0099:
0100: /**
0101: * Field until
0102: */
0103: protected Calendar until;
0104:
0105: /**
0106: * Field byDay
0107: */
0108: protected DayAndPosition[] byDay;
0109:
0110: /**
0111: * Field byMonthDay
0112: */
0113: protected int[] byMonthDay;
0114:
0115: /**
0116: * Field byYearDay
0117: */
0118: protected int[] byYearDay;
0119:
0120: /**
0121: * Field byWeekNo
0122: */
0123: protected int[] byWeekNo;
0124:
0125: /**
0126: * Field byMonth
0127: */
0128: protected int[] byMonth;
0129:
0130: /**
0131: * Constructor Recurrence
0132: *
0133: *
0134: */
0135: public Recurrence() {
0136: this (null, new Duration(), NO_RECURRENCE);
0137: }
0138:
0139: /**
0140: * Constructor Recurrence
0141: *
0142: *
0143: * @param start
0144: * @param dur
0145: *
0146: */
0147: public Recurrence(Calendar start, Duration dur) {
0148: this (start, dur, NO_RECURRENCE);
0149: }
0150:
0151: /**
0152: * Constructor Recurrence
0153: *
0154: *
0155: * @param start
0156: * @param dur
0157: * @param freq
0158: *
0159: */
0160: public Recurrence(Calendar start, Duration dur, int freq) {
0161: setDtStart(start);
0162:
0163: duration = (Duration) dur.clone();
0164: frequency = freq;
0165: interval = 1;
0166: }
0167:
0168: /* Accessors */
0169:
0170: /**
0171: * Method getDtStart
0172: *
0173: *
0174: * @return Calendar
0175: *
0176: */
0177: public Calendar getDtStart() {
0178: return (Calendar) dtStart.clone();
0179: }
0180:
0181: /**
0182: * Method setDtStart
0183: *
0184: *
0185: * @param start
0186: *
0187: */
0188: public void setDtStart(Calendar start) {
0189: int oldStart;
0190:
0191: if (dtStart != null) {
0192: oldStart = dtStart.getFirstDayOfWeek();
0193: } else {
0194: oldStart = Calendar.MONDAY;
0195: }
0196:
0197: if (start == null) {
0198: dtStart = CalendarFactoryUtil.getCalendar(TimeZone
0199: .getTimeZone("GMT"));
0200:
0201: dtStart.setTime(new Date(0L));
0202: } else {
0203: dtStart = (Calendar) start.clone();
0204:
0205: dtStart.clear(Calendar.ZONE_OFFSET);
0206: dtStart.clear(Calendar.DST_OFFSET);
0207: dtStart.setTimeZone(TimeZone.getTimeZone("GMT"));
0208: }
0209:
0210: dtStart.setMinimalDaysInFirstWeek(4);
0211: dtStart.setFirstDayOfWeek(oldStart);
0212: }
0213:
0214: /**
0215: * Method getDuration
0216: *
0217: *
0218: * @return Duration
0219: *
0220: */
0221: public Duration getDuration() {
0222: return (Duration) duration.clone();
0223: }
0224:
0225: /**
0226: * Method setDuration
0227: *
0228: *
0229: * @param d
0230: *
0231: */
0232: public void setDuration(Duration d) {
0233: duration = (Duration) d.clone();
0234: }
0235:
0236: /**
0237: * Method getDtEnd
0238: *
0239: *
0240: * @return Calendar
0241: *
0242: */
0243: public Calendar getDtEnd() {
0244:
0245: /*
0246: * Make dtEnd a cloned dtStart, so non-time fields of the Calendar
0247: * are accurate.
0248: */
0249: Calendar tempEnd = (Calendar) dtStart.clone();
0250:
0251: tempEnd.setTime(new Date(dtStart.getTime().getTime()
0252: + duration.getInterval()));
0253:
0254: return tempEnd;
0255: }
0256:
0257: /**
0258: * Method setDtEnd
0259: *
0260: *
0261: * @param end
0262: *
0263: */
0264: public void setDtEnd(Calendar end) {
0265: Calendar tempEnd = (Calendar) end.clone();
0266:
0267: tempEnd.clear(Calendar.ZONE_OFFSET);
0268: tempEnd.clear(Calendar.DST_OFFSET);
0269: tempEnd.setTimeZone(TimeZone.getTimeZone("GMT"));
0270: duration.setInterval(tempEnd.getTime().getTime()
0271: - dtStart.getTime().getTime());
0272: }
0273:
0274: /**
0275: * Method getFrequency
0276: *
0277: *
0278: * @return int
0279: *
0280: */
0281: public int getFrequency() {
0282: return frequency;
0283: }
0284:
0285: /**
0286: * Method setFrequency
0287: *
0288: *
0289: * @param freq
0290: *
0291: */
0292: public void setFrequency(int freq) {
0293: if ((frequency != DAILY) && (frequency != WEEKLY)
0294: && (frequency != MONTHLY) && (frequency != YEARLY)
0295: && (frequency != NO_RECURRENCE)) {
0296: throw new IllegalArgumentException("Invalid frequency");
0297: }
0298:
0299: frequency = freq;
0300: }
0301:
0302: /**
0303: * Method getInterval
0304: *
0305: *
0306: * @return int
0307: *
0308: */
0309: public int getInterval() {
0310: return interval;
0311: }
0312:
0313: /**
0314: * Method setInterval
0315: *
0316: *
0317: * @param intr
0318: *
0319: */
0320: public void setInterval(int intr) {
0321: interval = intr;
0322: }
0323:
0324: /**
0325: * Method getOccurrence
0326: *
0327: *
0328: * @return int
0329: *
0330: */
0331: public int getOccurrence() {
0332: return occurrence;
0333: }
0334:
0335: /**
0336: * Method setOccurrence
0337: *
0338: *
0339: * @param occur
0340: *
0341: */
0342: public void setOccurrence(int occur) {
0343: occurrence = occur;
0344: }
0345:
0346: /**
0347: * Method getUntil
0348: *
0349: *
0350: * @return Calendar
0351: *
0352: */
0353: public Calendar getUntil() {
0354: return ((until != null) ? (Calendar) until.clone() : null);
0355: }
0356:
0357: /**
0358: * Method setUntil
0359: *
0360: *
0361: * @param u
0362: *
0363: */
0364: public void setUntil(Calendar u) {
0365: if (u == null) {
0366: until = null;
0367:
0368: return;
0369: }
0370:
0371: until = (Calendar) u.clone();
0372:
0373: until.clear(Calendar.ZONE_OFFSET);
0374: until.clear(Calendar.DST_OFFSET);
0375: until.setTimeZone(TimeZone.getTimeZone("GMT"));
0376: }
0377:
0378: /**
0379: * Method getWeekStart
0380: *
0381: *
0382: * @return int
0383: *
0384: */
0385: public int getWeekStart() {
0386: return dtStart.getFirstDayOfWeek();
0387: }
0388:
0389: /**
0390: * Method setWeekStart
0391: *
0392: *
0393: * @param weekstart
0394: *
0395: */
0396: public void setWeekStart(int weekstart) {
0397: dtStart.setFirstDayOfWeek(weekstart);
0398: }
0399:
0400: /**
0401: * Method getByDay
0402: *
0403: *
0404: * @return DayAndPosition[]
0405: *
0406: */
0407: public DayAndPosition[] getByDay() {
0408: if (byDay == null) {
0409: return null;
0410: }
0411:
0412: DayAndPosition[] b = new DayAndPosition[byDay.length];
0413:
0414: /*
0415: * System.arraycopy isn't good enough -- we want to clone each
0416: * individual element.
0417: */
0418: for (int i = 0; i < byDay.length; i++) {
0419: b[i] = (DayAndPosition) byDay[i].clone();
0420: }
0421:
0422: return b;
0423: }
0424:
0425: /**
0426: * Method setByDay
0427: *
0428: *
0429: * @param b
0430: *
0431: */
0432: public void setByDay(DayAndPosition[] b) {
0433: if (b == null) {
0434: byDay = null;
0435:
0436: return;
0437: }
0438:
0439: byDay = new DayAndPosition[b.length];
0440:
0441: /*
0442: * System.arraycopy isn't good enough -- we want to clone each
0443: * individual element.
0444: */
0445: for (int i = 0; i < b.length; i++) {
0446: byDay[i] = (DayAndPosition) b[i].clone();
0447: }
0448: }
0449:
0450: /**
0451: * Method getByMonthDay
0452: *
0453: *
0454: * @return int[]
0455: *
0456: */
0457: public int[] getByMonthDay() {
0458: if (byMonthDay == null) {
0459: return null;
0460: }
0461:
0462: int[] b = new int[byMonthDay.length];
0463:
0464: System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
0465:
0466: return b;
0467: }
0468:
0469: /**
0470: * Method setByMonthDay
0471: *
0472: *
0473: * @param b
0474: *
0475: */
0476: public void setByMonthDay(int[] b) {
0477: if (b == null) {
0478: byMonthDay = null;
0479:
0480: return;
0481: }
0482:
0483: byMonthDay = new int[b.length];
0484:
0485: System.arraycopy(b, 0, byMonthDay, 0, b.length);
0486: }
0487:
0488: /**
0489: * Method getByYearDay
0490: *
0491: *
0492: * @return int[]
0493: *
0494: */
0495: public int[] getByYearDay() {
0496: if (byYearDay == null) {
0497: return null;
0498: }
0499:
0500: int[] b = new int[byYearDay.length];
0501:
0502: System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
0503:
0504: return b;
0505: }
0506:
0507: /**
0508: * Method setByYearDay
0509: *
0510: *
0511: * @param b
0512: *
0513: */
0514: public void setByYearDay(int[] b) {
0515: if (b == null) {
0516: byYearDay = null;
0517:
0518: return;
0519: }
0520:
0521: byYearDay = new int[b.length];
0522:
0523: System.arraycopy(b, 0, byYearDay, 0, b.length);
0524: }
0525:
0526: /**
0527: * Method getByWeekNo
0528: *
0529: *
0530: * @return int[]
0531: *
0532: */
0533: public int[] getByWeekNo() {
0534: if (byWeekNo == null) {
0535: return null;
0536: }
0537:
0538: int[] b = new int[byWeekNo.length];
0539:
0540: System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
0541:
0542: return b;
0543: }
0544:
0545: /**
0546: * Method setByWeekNo
0547: *
0548: *
0549: * @param b
0550: *
0551: */
0552: public void setByWeekNo(int[] b) {
0553: if (b == null) {
0554: byWeekNo = null;
0555:
0556: return;
0557: }
0558:
0559: byWeekNo = new int[b.length];
0560:
0561: System.arraycopy(b, 0, byWeekNo, 0, b.length);
0562: }
0563:
0564: /**
0565: * Method getByMonth
0566: *
0567: *
0568: * @return int[]
0569: *
0570: */
0571: public int[] getByMonth() {
0572: if (byMonth == null) {
0573: return null;
0574: }
0575:
0576: int[] b = new int[byMonth.length];
0577:
0578: System.arraycopy(byMonth, 0, b, 0, byMonth.length);
0579:
0580: return b;
0581: }
0582:
0583: /**
0584: * Method setByMonth
0585: *
0586: *
0587: * @param b
0588: *
0589: */
0590: public void setByMonth(int[] b) {
0591: if (b == null) {
0592: byMonth = null;
0593:
0594: return;
0595: }
0596:
0597: byMonth = new int[b.length];
0598:
0599: System.arraycopy(b, 0, byMonth, 0, b.length);
0600: }
0601:
0602: /**
0603: * Method isInRecurrence
0604: *
0605: *
0606: * @param current
0607: *
0608: * @return boolean
0609: *
0610: */
0611: public boolean isInRecurrence(Calendar current) {
0612: return isInRecurrence(current, false);
0613: }
0614:
0615: /**
0616: * Method isInRecurrence
0617: *
0618: *
0619: * @param current
0620: * @param debug
0621: *
0622: * @return boolean
0623: *
0624: */
0625: public boolean isInRecurrence(Calendar current, boolean debug) {
0626: Calendar myCurrent = (Calendar) current.clone();
0627:
0628: // Do all calculations in GMT. Keep other parameters consistent.
0629:
0630: myCurrent.clear(Calendar.ZONE_OFFSET);
0631: myCurrent.clear(Calendar.DST_OFFSET);
0632: myCurrent.setTimeZone(TimeZone.getTimeZone("GMT"));
0633: myCurrent.setMinimalDaysInFirstWeek(4);
0634: myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
0635:
0636: if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
0637:
0638: // The current time is earlier than the start time.
0639:
0640: if (debug) {
0641: System.err.println("current < start");
0642: }
0643:
0644: return false;
0645: }
0646:
0647: if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()
0648: + duration.getInterval()) {
0649:
0650: // We are within "duration" of dtStart.
0651:
0652: if (debug) {
0653: System.err.println("within duration of start");
0654: }
0655:
0656: return true;
0657: }
0658:
0659: Calendar candidate = getCandidateStartTime(myCurrent);
0660:
0661: /* Loop over ranges for the duration. */
0662:
0663: while (candidate.getTime().getTime() + duration.getInterval() > myCurrent
0664: .getTime().getTime()) {
0665: if (candidateIsInRecurrence(candidate, debug)) {
0666: return true;
0667: }
0668:
0669: /* Roll back to one second previous, and try again. */
0670:
0671: candidate.add(Calendar.SECOND, -1);
0672:
0673: /* Make sure we haven't rolled back to before dtStart. */
0674:
0675: if (candidate.getTime().getTime() < dtStart.getTime()
0676: .getTime()) {
0677: if (debug) {
0678: System.err.println("No candidates after dtStart");
0679: }
0680:
0681: return false;
0682: }
0683:
0684: candidate = getCandidateStartTime(candidate);
0685: }
0686:
0687: if (debug) {
0688: System.err.println("No matching candidates");
0689: }
0690:
0691: return false;
0692: }
0693:
0694: /**
0695: * Method candidateIsInRecurrence
0696: *
0697: *
0698: * @param candidate
0699: * @param debug
0700: *
0701: * @return boolean
0702: *
0703: */
0704: protected boolean candidateIsInRecurrence(Calendar candidate,
0705: boolean debug) {
0706: if ((until != null)
0707: && (candidate.getTime().getTime() > until.getTime()
0708: .getTime())) {
0709:
0710: // After "until"
0711:
0712: if (debug) {
0713: System.err.println("after until");
0714: }
0715:
0716: return false;
0717: }
0718:
0719: if (getRecurrenceCount(candidate) % interval != 0) {
0720:
0721: // Not a repetition of the interval
0722:
0723: if (debug) {
0724: System.err.println("not an interval rep");
0725: }
0726:
0727: return false;
0728: } else if ((occurrence > 0)
0729: && (getRecurrenceCount(candidate) >= occurrence)) {
0730:
0731: return false;
0732: }
0733:
0734: if (!matchesByDay(candidate) || !matchesByMonthDay(candidate)
0735: || !matchesByYearDay(candidate)
0736: || !matchesByWeekNo(candidate)
0737: || !matchesByMonth(candidate)) {
0738:
0739: // Doesn't match a by* rule
0740:
0741: if (debug) {
0742: System.err.println("doesn't match a by*");
0743: }
0744:
0745: return false;
0746: }
0747:
0748: if (debug) {
0749: System.err.println("All checks succeeded");
0750: }
0751:
0752: return true;
0753: }
0754:
0755: /**
0756: * Method getMinimumInterval
0757: *
0758: *
0759: * @return int
0760: *
0761: */
0762: protected int getMinimumInterval() {
0763: if ((frequency == DAILY) || (byDay != null)
0764: || (byMonthDay != null) || (byYearDay != null)) {
0765: return DAILY;
0766: } else if ((frequency == WEEKLY) || (byWeekNo != null)) {
0767: return WEEKLY;
0768: } else if ((frequency == MONTHLY) || (byMonth != null)) {
0769: return MONTHLY;
0770: } else if (frequency == YEARLY) {
0771: return YEARLY;
0772: } else if (frequency == NO_RECURRENCE) {
0773: return NO_RECURRENCE;
0774: } else {
0775:
0776: // Shouldn't happen
0777:
0778: throw new IllegalStateException(
0779: "Internal error: Unknown frequency value");
0780: }
0781: }
0782:
0783: /**
0784: * Method getCandidateStartTime
0785: *
0786: *
0787: * @param current
0788: *
0789: * @return Calendar
0790: *
0791: */
0792: public Calendar getCandidateStartTime(Calendar current) {
0793: if (dtStart.getTime().getTime() > current.getTime().getTime()) {
0794: throw new IllegalArgumentException(
0795: "Current time before DtStart");
0796: }
0797:
0798: int minInterval = getMinimumInterval();
0799: Calendar candidate = (Calendar) current.clone();
0800:
0801: if (true) {
0802:
0803: // This block is only needed while this function is public...
0804:
0805: candidate.clear(Calendar.ZONE_OFFSET);
0806: candidate.clear(Calendar.DST_OFFSET);
0807: candidate.setTimeZone(TimeZone.getTimeZone("GMT"));
0808: candidate.setMinimalDaysInFirstWeek(4);
0809: candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
0810: }
0811:
0812: if (frequency == NO_RECURRENCE) {
0813: candidate.setTime(dtStart.getTime());
0814:
0815: return candidate;
0816: }
0817:
0818: reduce_constant_length_field(Calendar.SECOND, dtStart,
0819: candidate);
0820: reduce_constant_length_field(Calendar.MINUTE, dtStart,
0821: candidate);
0822: reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart,
0823: candidate);
0824:
0825: switch (minInterval) {
0826:
0827: case DAILY:
0828:
0829: /* No more adjustments needed */
0830:
0831: break;
0832:
0833: case WEEKLY:
0834: reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
0835: candidate);
0836: break;
0837:
0838: case MONTHLY:
0839: reduce_day_of_month(dtStart, candidate);
0840: break;
0841:
0842: case YEARLY:
0843: reduce_day_of_year(dtStart, candidate);
0844: break;
0845: }
0846:
0847: return candidate;
0848: }
0849:
0850: /**
0851: * Method reduce_constant_length_field
0852: *
0853: *
0854: * @param field
0855: * @param start
0856: * @param candidate
0857: *
0858: */
0859: protected static void reduce_constant_length_field(int field,
0860: Calendar start, Calendar candidate) {
0861: if ((start.getMaximum(field) != start.getLeastMaximum(field))
0862: || (start.getMinimum(field) != start
0863: .getGreatestMinimum(field))) {
0864: throw new IllegalArgumentException(
0865: "Not a constant length field");
0866: }
0867:
0868: int fieldLength = (start.getMaximum(field)
0869: - start.getMinimum(field) + 1);
0870: int delta = start.get(field) - candidate.get(field);
0871:
0872: if (delta > 0) {
0873: delta -= fieldLength;
0874: }
0875:
0876: candidate.add(field, delta);
0877: }
0878:
0879: /**
0880: * Method reduce_day_of_month
0881: *
0882: *
0883: * @param start
0884: * @param candidate
0885: *
0886: */
0887: protected static void reduce_day_of_month(Calendar start,
0888: Calendar candidate) {
0889: Calendar tempCal = (Calendar) candidate.clone();
0890:
0891: tempCal.add(Calendar.MONTH, -1);
0892:
0893: int delta = start.get(Calendar.DATE)
0894: - candidate.get(Calendar.DATE);
0895:
0896: if (delta > 0) {
0897: delta -= tempCal.getActualMaximum(Calendar.DATE);
0898: }
0899:
0900: candidate.add(Calendar.DATE, delta);
0901:
0902: while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
0903: tempCal.add(Calendar.MONTH, -1);
0904: candidate.add(Calendar.DATE, -tempCal
0905: .getActualMaximum(Calendar.DATE));
0906: }
0907: }
0908:
0909: /**
0910: * Method reduce_day_of_year
0911: *
0912: *
0913: * @param start
0914: * @param candidate
0915: *
0916: */
0917: protected static void reduce_day_of_year(Calendar start,
0918: Calendar candidate) {
0919: if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
0920: || ((start.get(Calendar.MONTH) == candidate
0921: .get(Calendar.MONTH)) && (start
0922: .get(Calendar.DATE) > candidate
0923: .get(Calendar.DATE)))) {
0924: candidate.add(Calendar.YEAR, -1);
0925: }
0926:
0927: /* Set the candidate date to the start date. */
0928:
0929: candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
0930: candidate.set(Calendar.DATE, start.get(Calendar.DATE));
0931:
0932: while ((start.get(Calendar.MONTH) != candidate
0933: .get(Calendar.MONTH))
0934: || (start.get(Calendar.DATE) != candidate
0935: .get(Calendar.DATE))) {
0936: candidate.add(Calendar.YEAR, -1);
0937: candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
0938: candidate.set(Calendar.DATE, start.get(Calendar.DATE));
0939: }
0940: }
0941:
0942: /**
0943: * Method getRecurrenceCount
0944: *
0945: *
0946: * @param candidate
0947: *
0948: * @return int
0949: *
0950: */
0951: protected int getRecurrenceCount(Calendar candidate) {
0952: switch (frequency) {
0953:
0954: case NO_RECURRENCE:
0955: return 0;
0956:
0957: case DAILY:
0958: return (int) (getDayNumber(candidate) - getDayNumber(dtStart));
0959:
0960: case WEEKLY:
0961: Calendar tempCand = (Calendar) candidate.clone();
0962:
0963: tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
0964:
0965: return (int) (getWeekNumber(tempCand) - getWeekNumber(dtStart));
0966:
0967: case MONTHLY:
0968: return (int) (getMonthNumber(candidate) - getMonthNumber(dtStart));
0969:
0970: case YEARLY:
0971: return candidate.get(Calendar.YEAR)
0972: - dtStart.get(Calendar.YEAR);
0973:
0974: default:
0975: throw new IllegalStateException(
0976: "bad frequency internally...");
0977: }
0978: }
0979:
0980: /**
0981: * Method getDayNumber
0982: *
0983: *
0984: * @param cal
0985: *
0986: * @return long
0987: *
0988: */
0989: protected static long getDayNumber(Calendar cal) {
0990: Calendar tempCal = (Calendar) cal.clone();
0991:
0992: // Set to midnight, GMT
0993:
0994: tempCal.set(Calendar.MILLISECOND, 0);
0995: tempCal.set(Calendar.SECOND, 0);
0996: tempCal.set(Calendar.MINUTE, 0);
0997: tempCal.set(Calendar.HOUR_OF_DAY, 0);
0998:
0999: return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
1000: }
1001:
1002: /**
1003: * Method getWeekNumber
1004: *
1005: *
1006: * @param cal
1007: *
1008: * @return long
1009: *
1010: */
1011: protected static long getWeekNumber(Calendar cal) {
1012: Calendar tempCal = (Calendar) cal.clone();
1013:
1014: // Set to midnight, GMT
1015:
1016: tempCal.set(Calendar.MILLISECOND, 0);
1017: tempCal.set(Calendar.SECOND, 0);
1018: tempCal.set(Calendar.MINUTE, 0);
1019: tempCal.set(Calendar.HOUR_OF_DAY, 0);
1020:
1021: // Roll back to the first day of the week
1022:
1023: int delta = tempCal.getFirstDayOfWeek()
1024: - tempCal.get(Calendar.DAY_OF_WEEK);
1025:
1026: if (delta > 0) {
1027: delta -= 7;
1028: }
1029:
1030: // tempCal now points to the first instant of this week.
1031:
1032: // Calculate the "week epoch" -- the weekstart day closest to January 1,
1033: // 1970 (which was a Thursday)
1034:
1035: long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY)
1036: * 24 * 60 * 60 * 1000L;
1037:
1038: return (tempCal.getTime().getTime() - weekEpoch)
1039: / (7 * 24 * 60 * 60 * 1000);
1040: }
1041:
1042: /**
1043: * Method getMonthNumber
1044: *
1045: *
1046: * @param cal
1047: *
1048: * @return long
1049: *
1050: */
1051: protected static long getMonthNumber(Calendar cal) {
1052: return (cal.get(Calendar.YEAR) - 1970) * 12
1053: + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
1054: }
1055:
1056: /**
1057: * Method matchesByDay
1058: *
1059: *
1060: * @param candidate
1061: *
1062: * @return boolean
1063: *
1064: */
1065: protected boolean matchesByDay(Calendar candidate) {
1066: if ((byDay == null) || (byDay.length == 0)) {
1067:
1068: /* No byDay rules, so it matches trivially */
1069:
1070: return true;
1071: }
1072:
1073: int i;
1074:
1075: for (i = 0; i < byDay.length; i++) {
1076: if (matchesIndividualByDay(candidate, byDay[i])) {
1077: return true;
1078: }
1079: }
1080:
1081: return false;
1082: }
1083:
1084: /**
1085: * Method matchesIndividualByDay
1086: *
1087: *
1088: * @param candidate
1089: * @param pos
1090: *
1091: * @return boolean
1092: *
1093: */
1094: protected boolean matchesIndividualByDay(Calendar candidate,
1095: DayAndPosition pos) {
1096: if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1097: return false;
1098: }
1099:
1100: int position = pos.getDayPosition();
1101:
1102: if (position == 0) {
1103: return true;
1104: }
1105:
1106: int field;
1107:
1108: switch (frequency) {
1109:
1110: case MONTHLY:
1111: field = Calendar.DAY_OF_MONTH;
1112: break;
1113:
1114: case YEARLY:
1115: field = Calendar.DAY_OF_YEAR;
1116: break;
1117:
1118: default:
1119: throw new IllegalStateException("byday has a day position "
1120: + "in non-MONTHLY or YEARLY recurrence");
1121: }
1122:
1123: if (position > 0) {
1124: int day_of_week_in_field = ((candidate.get(field) - 1) / 7) + 1;
1125:
1126: return (position == day_of_week_in_field);
1127: } else {
1128:
1129: /* position < 0 */
1130:
1131: int negative_day_of_week_in_field = ((candidate
1132: .getActualMaximum(field) - candidate.get(field)) / 7) + 1;
1133:
1134: return (-position == negative_day_of_week_in_field);
1135: }
1136: }
1137:
1138: /**
1139: * Method matchesByField
1140: *
1141: *
1142: * @param array
1143: * @param field
1144: * @param candidate
1145: * @param allowNegative
1146: *
1147: * @return boolean
1148: *
1149: */
1150: protected static boolean matchesByField(int[] array, int field,
1151: Calendar candidate, boolean allowNegative) {
1152: if ((array == null) || (array.length == 0)) {
1153:
1154: /* No rules, so it matches trivially */
1155:
1156: return true;
1157: }
1158:
1159: int i;
1160:
1161: for (i = 0; i < array.length; i++) {
1162: int val;
1163:
1164: if (allowNegative && (array[i] < 0)) {
1165:
1166: // byMonthDay = -1, in a 31-day month, means 31
1167:
1168: int max = candidate.getActualMaximum(field);
1169:
1170: val = (max + 1) + array[i];
1171: } else {
1172: val = array[i];
1173: }
1174:
1175: if (val == candidate.get(field)) {
1176: return true;
1177: }
1178: }
1179:
1180: return false;
1181: }
1182:
1183: /**
1184: * Method matchesByMonthDay
1185: *
1186: *
1187: * @param candidate
1188: *
1189: * @return boolean
1190: *
1191: */
1192: protected boolean matchesByMonthDay(Calendar candidate) {
1193: return matchesByField(byMonthDay, Calendar.DATE, candidate,
1194: true);
1195: }
1196:
1197: /**
1198: * Method matchesByYearDay
1199: *
1200: *
1201: * @param candidate
1202: *
1203: * @return boolean
1204: *
1205: */
1206: protected boolean matchesByYearDay(Calendar candidate) {
1207: return matchesByField(byYearDay, Calendar.DAY_OF_YEAR,
1208: candidate, true);
1209: }
1210:
1211: /**
1212: * Method matchesByWeekNo
1213: *
1214: *
1215: * @param candidate
1216: *
1217: * @return boolean
1218: *
1219: */
1220: protected boolean matchesByWeekNo(Calendar candidate) {
1221: return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR,
1222: candidate, true);
1223: }
1224:
1225: /**
1226: * Method matchesByMonth
1227: *
1228: *
1229: * @param candidate
1230: *
1231: * @return boolean
1232: *
1233: */
1234: protected boolean matchesByMonth(Calendar candidate) {
1235: return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1236: }
1237:
1238: /**
1239: * Method toString
1240: *
1241: *
1242: * @return String
1243: *
1244: */
1245: public String toString() {
1246: StringMaker sm = new StringMaker();
1247:
1248: sm.append(getClass().getName());
1249: sm.append("[dtStart=");
1250: sm.append((dtStart != null) ? dtStart.toString() : "null");
1251: sm.append(",duration=");
1252: sm.append((duration != null) ? duration.toString() : "null");
1253: sm.append(",frequency=");
1254: sm.append(frequency);
1255: sm.append(",interval=");
1256: sm.append(interval);
1257: sm.append(",until=");
1258: sm.append((until != null) ? until.toString() : "null");
1259: sm.append(",byDay=");
1260:
1261: if (byDay == null) {
1262: sm.append("null");
1263: } else {
1264: sm.append("[");
1265:
1266: for (int i = 0; i < byDay.length; i++) {
1267: if (i != 0) {
1268: sm.append(",");
1269: }
1270:
1271: if (byDay[i] != null) {
1272: sm.append(byDay[i].toString());
1273: } else {
1274: sm.append("null");
1275: }
1276: }
1277:
1278: sm.append("]");
1279: }
1280:
1281: sm.append(",byMonthDay=");
1282: sm.append(stringizeIntArray(byMonthDay));
1283: sm.append(",byYearDay=");
1284: sm.append(stringizeIntArray(byYearDay));
1285: sm.append(",byWeekNo=");
1286: sm.append(stringizeIntArray(byWeekNo));
1287: sm.append(",byMonth=");
1288: sm.append(stringizeIntArray(byMonth));
1289: sm.append(']');
1290:
1291: return sm.toString();
1292: }
1293:
1294: /**
1295: * Method stringizeIntArray
1296: *
1297: *
1298: * @param a
1299: *
1300: * @return String
1301: *
1302: */
1303: private String stringizeIntArray(int[] a) {
1304: if (a == null) {
1305: return "null";
1306: }
1307:
1308: StringMaker sm = new StringMaker();
1309:
1310: sm.append("[");
1311:
1312: for (int i = 0; i < a.length; i++) {
1313: if (i != 0) {
1314: sm.append(",");
1315: }
1316:
1317: sm.append(a[i]);
1318: }
1319:
1320: sm.append("]");
1321:
1322: return sm.toString();
1323: }
1324:
1325: }
|