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