KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > util > Schedule


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/util/Schedule.java#8 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2002-2006 Julian Hyde
7 // All Rights Reserved.
8 // You must accept the terms of that agreement to use this software.
9 */

10
11 package mondrian.util;
12
13 import java.sql.Time JavaDoc;
14 import java.util.Calendar JavaDoc;
15 import java.util.Date JavaDoc;
16 import java.util.TimeZone JavaDoc;
17
18 /**
19  * A <code>Schedule</code> generates a series of time events.
20  *
21  * <p> Create a schedule using one of the factory methods:<ul>
22  * <li>{@link #createOnce},</li>
23  * <li>{@link #createDaily},</li>
24  * <li>{@link #createWeekly},</li>
25  * <li>{@link #createMonthlyByDay},</li>
26  * <li>{@link #createMonthlyByWeek}.</li></ul>
27  *
28  * <p> Then use the {@link #nextOccurrence} method to find the next occurrence
29  * after a particular point in time.
30  *
31  * <p> The <code>begin</code> and <code>end</code> parameters represent the
32  * points in time between which the schedule is active. Both are optional.
33  * However, if a schedule type supports a <code>period</code> parameter, and
34  * you supply a value greater than 1, <code>begin</code> is used to determine
35  * the start of the cycle. If <code>begin</code> is not specified, the cycle
36  * starts at the epoch (January 1st, 1970).
37  *
38  * <p> The {@link Date} parameters in this API -- <code>begin</code> and
39  * <code>end</code>, the <code>time</code> parameter to {@link #createOnce},
40  * and the <code>earliestDate</code> parameter and value returned from {@link
41  * #nextOccurrence} -- always represent a point in time (GMT), not a local
42  * time. If a schedule is to start at 12 noon Tokyo time, April 1st, 2002, it
43  * is the application's reponsibility to convert this into a UTC {@link Date}
44  * value.
45  *
46  * @author jhyde
47  * @since May 7, 2002
48  * @version $Id: //open/mondrian/src/main/mondrian/util/Schedule.java#8 $
49  */

50 public class Schedule {
51
52     // members
53

54     private DateSchedule dateSchedule;
55     private TimeSchedule timeSchedule;
56     private TimeZone JavaDoc tz;
57     private Date JavaDoc begin;
58     private Date JavaDoc end;
59
60     // constants
61

62     /**
63      * Indicates that a schedule should fire on the last day of the month.
64      * @see #createMonthlyByDay
65      */

66     public static final int LAST_DAY_OF_MONTH = 0;
67     /**
68      * Indicates that a schedule should fire on the last week of the month.
69      * @see #createMonthlyByWeek
70      */

71     public static final int LAST_WEEK_OF_MONTH = 0;
72
73     static final TimeZone JavaDoc utcTimeZone = TimeZone.getTimeZone("UTC");
74
75     static final int allDaysOfWeekBitmap =
76             (1 << Calendar.MONDAY) |
77             (1 << Calendar.TUESDAY) |
78             (1 << Calendar.WEDNESDAY) |
79             (1 << Calendar.THURSDAY) |
80             (1 << Calendar.FRIDAY) |
81             (1 << Calendar.SATURDAY) |
82             (1 << Calendar.SUNDAY);
83     static final int allDaysOfMonthBitmap = 0xefffFffe | // bits 1..31
84
(1 << LAST_DAY_OF_MONTH);
85     static final int allWeeksOfMonthBitmap = 0x0000003e | // bits 1..5
86
(1 << LAST_WEEK_OF_MONTH);
87
88     // constructor(s) and factory methods
89

90     /**
91      * Please use the factory methods {@link #createDaily} etc. to create a
92      * Schedule.
93      */

94     private Schedule(
95             DateSchedule dateSchedule,
96             TimeSchedule timeSchedule,
97             TimeZone JavaDoc tz,
98             Date JavaDoc begin,
99             Date JavaDoc end) {
100         this.dateSchedule = dateSchedule;
101         this.timeSchedule = timeSchedule;
102         this.tz = tz;
103         this.begin = begin;
104         this.end = end;
105     }
106
107     /**
108      * Creates a calendar which fires only once.
109      *
110      * @param date date and time to fire, must be UTC
111      * @param tz timezone
112      *
113      * @pre tz != null
114      * @pre date != null
115      * @post return != null
116      */

117     public static Schedule createOnce(Date JavaDoc date, TimeZone JavaDoc tz) {
118         Calendar JavaDoc calendar = ScheduleUtil.createCalendar(date);
119         Time JavaDoc timeOfDay = ScheduleUtil.createTime(
120                 calendar.get(Calendar.HOUR_OF_DAY),
121                 calendar.get(Calendar.MINUTE),
122                 calendar.get(Calendar.SECOND));
123         calendar.add(Calendar.SECOND, 1);
124         Date JavaDoc datePlusDelta = calendar.getTime();
125         return createDaily(date, datePlusDelta, tz, timeOfDay, 1);
126     }
127
128     /**
129      * Creates a calendar which fires every day.
130      *
131      * @param begin open lower bound, may be null, must be UTC
132      * @param end closed upper bound, may be null, must be UTC
133      * @param tz timezone
134      * @param timeOfDay time at which to fire
135      * @param period causes the schedule to fire every <code>period</code>
136      * days. If <code>period</code> is greater than 1, the cycle starts
137      * at the begin point of the schedule, or at the epoch (1 January,
138      * 1970) if <code>begin</code> is not specified.
139      *
140      * @pre tz != null
141      * @pre period > 0
142      * @post return != null
143      */

144     public static Schedule createDaily(
145             Date JavaDoc begin, Date JavaDoc end, TimeZone JavaDoc tz, Time JavaDoc timeOfDay, int period) {
146         DateSchedule dateSchedule = new DailyDateSchedule(
147                 begin == null ? null : ScheduleUtil.createCalendar(begin),
148                 period);
149         return new Schedule(
150                 dateSchedule,
151                 new OnceTimeSchedule(ScheduleUtil.createTimeCalendar(timeOfDay)),
152                 tz,
153                 begin,
154                 end);
155     }
156
157     /**
158      * Creates a calendar which fires on particular days each week.
159      *
160      * @param tz timezone
161      * @param daysOfWeekBitmap a bitmap of day values, for example
162      * <code>(1 << {@link Calendar#TUESDAY}) |
163      * (1 << {@link Calendar#THURSDAY})</code> to fire on Tuesdays
164      * and Thursdays
165      * @param timeOfDay time at which to fire
166      * @param begin open lower bound, may be null
167      * @param end closed upper bound, may be null
168      * @param period causes the schedule to be active every <code>period</code>
169      * weeks. If <code>period</code> is greater than 1, the cycle starts
170      * at the begin point of the schedule, or at the epoch (1 January,
171      * 1970) if <code>begin</code> is not specified.
172      *
173      * @pre tz != null
174      * @pre period > 0
175      * @post return != null
176      */

177     public static Schedule createWeekly(
178             Date JavaDoc begin, Date JavaDoc end, TimeZone JavaDoc tz,
179             Time JavaDoc timeOfDay, int period, int daysOfWeekBitmap) {
180         DateSchedule dateSchedule = new WeeklyDateSchedule(
181                 begin == null ? null : ScheduleUtil.createCalendar(begin),
182                 period,
183                 daysOfWeekBitmap);
184         return new Schedule(
185                 dateSchedule,
186                 new OnceTimeSchedule(ScheduleUtil.createTimeCalendar(timeOfDay)),
187                 tz,
188                 begin,
189                 end);
190     }
191
192     /**
193      * Creates a calendar which fires on particular days of each month.
194      * For example,<blockquote>
195      *
196      * <pre>createMonthlyByDay(
197      * null, null, TimeZone.getTimeZone("PST"), 1,
198      * (1 << 12) | (1 << 14) | (1 << {@link #LAST_DAY_OF_MONTH}))</pre>
199      *
200      * </blockquote> creates a schedule which fires on the 12th, 14th and last
201      * day of the month.
202      *
203      * @param begin open lower bound, may be null
204      * @param end closed upper bound, may be null
205      * @param tz timezone
206      * @param daysOfMonthBitmap a bitmap of day values, may include
207      * {@link #LAST_DAY_OF_MONTH}
208      * @param timeOfDay time at which to fire
209      * @param period causes the schedule to be active every <code>period</code>
210      * months. If <code>period</code> is greater than 1, the cycle starts
211      * at the begin point of the schedule, or at the epoch (1 January,
212      * 1970) if <code>begin</code> is not specified.
213      *
214      * @pre tz != null
215      * @pre period > 0
216      * @post return != null
217      */

218     public static Schedule createMonthlyByDay(
219             Date JavaDoc begin, Date JavaDoc end, TimeZone JavaDoc tz, Time JavaDoc timeOfDay, int period,
220             int daysOfMonthBitmap) {
221         DateSchedule dateSchedule = new MonthlyByDayDateSchedule(
222                 begin == null ? null : ScheduleUtil.createCalendar(begin),
223                 period, daysOfMonthBitmap);
224         return new Schedule(
225                 dateSchedule,
226                 new OnceTimeSchedule(ScheduleUtil.createTimeCalendar(timeOfDay)),
227                 tz,
228                 begin,
229                 end);
230     }
231
232     /**
233      * Creates a calendar which fires on particular days of particular weeks of
234      * a month. For example,<blockquote>
235      *
236      * <pre>createMonthlyByWeek(
237      * null, null, TimeZone.getTimeZone("PST"),
238      * (1 << Calendar.TUESDAY) | (1 << Calendar.THURSDAY),
239      * (1 << 2) | (1 << {@link #LAST_WEEK_OF_MONTH})</pre>
240      *
241      * </blockquote> creates a schedule which fires on the 2nd and last Tuesday
242      * and Thursday of the month.
243      *
244      * @param begin open lower bound, may be null
245      * @param end closed upper bound, may be null
246      * @param tz timezone
247      * @param daysOfWeekBitmap a bitmap of day values, for example
248      * <code>(1 << Calendar.TUESDAY) | (1 << Calendar.THURSDAY)</code>
249      * @param weeksOfMonthBitmap a bitmap of week values (may include
250      * {@link #LAST_WEEK_OF_MONTH}
251      * @param timeOfDay time at which to fire
252      * @param period causes the schedule be active every <code>period</code>
253      * months. If <code>period</code> is greater than 1, the cycle starts
254      * at the begin point of the schedule, or at the epoch (1 January,
255      * 1970) if <code>begin</code> is not specified.
256      *
257      * @pre tz != null
258      * @pre period > 0
259      * @post return != null
260      */

261     public static Schedule createMonthlyByWeek(
262             Date JavaDoc begin, Date JavaDoc end, TimeZone JavaDoc tz,
263             Time JavaDoc timeOfDay, int period, int daysOfWeekBitmap,
264             int weeksOfMonthBitmap) {
265         DateSchedule dateSchedule = new MonthlyByWeekDateSchedule(
266                 begin == null ? null : ScheduleUtil.createCalendar(begin),
267                 period,
268                 daysOfWeekBitmap,
269                 weeksOfMonthBitmap);
270         return new Schedule(
271                 dateSchedule,
272                 new OnceTimeSchedule(ScheduleUtil.createTimeCalendar(timeOfDay)),
273                 tz,
274                 begin,
275                 end);
276     }
277
278     /**
279      * Returns the next occurrence of this schedule after a given date. If
280      * <code>after</code> is null, returns the first occurrence. If there are
281      * no further occurrences, returns null.
282      *
283      * @param after if not null, returns the first occurrence after this
284      * point in time; if null, returns the first occurrence ever.
285      * @param strict If <code>after</code> is an occurrence,
286      * <code>strict</code> determines whether this method returns it, or
287      * the next occurrence. If <code>strict</code> is true, the value
288      * returned is strictly greater than <code>after</code>.
289      */

290     public Date JavaDoc nextOccurrence(Date JavaDoc after, boolean strict) {
291         if (after == null ||
292                 begin != null && begin.after(after)) {
293             after = begin;
294             strict = false;
295         }
296         if (after == null) {
297             after = new Date JavaDoc(0);
298         }
299         Date JavaDoc next = nextOccurrence0(after, strict);
300         // if there is an upper bound, and this is not STRICTLY before it,
301
// there's no next occurrence
302
if (next != null &&
303                 end != null &&
304                 !next.before(end)) {
305             next = null;
306         }
307         return next;
308     }
309
310     private Date JavaDoc nextOccurrence0(Date JavaDoc after, boolean strict) {
311         Calendar JavaDoc next = ScheduleUtil.createCalendar(after);
312         if (tz == null || tz.getID().equals("GMT")) {
313             return nextOccurrence1(next, strict);
314         } else {
315             int offset;
316             if (next == null) {
317                 offset = tz.getRawOffset();
318             } else {
319                 offset = ScheduleUtil.timezoneOffset(tz, next);
320             }
321             // Add the offset to the calendar, so that the calendar looks like
322
// the local time (even though it is still in GMT). Suppose an
323
// event runs at 12:00 JST each day. At 02:00 GMT they ask for the
324
// next event. We convert this to local time, 11:00 JST, by adding
325
// the 9 hour offset. We will convert the result back to GMT by
326
// subtracting the offset.
327
next.add(Calendar.MILLISECOND, offset);
328             Date JavaDoc result = nextOccurrence1(next, strict);
329             if (result == null) {
330                 return null;
331             }
332             Calendar JavaDoc resultCalendar = ScheduleUtil.createCalendar(result);
333             int offset2 = ScheduleUtil.timezoneOffset(tz, resultCalendar);
334             // Shift the result back again.
335
resultCalendar.add(Calendar.MILLISECOND, -offset2);
336             return resultCalendar.getTime();
337         }
338     }
339
340     private Date JavaDoc nextOccurrence1(Calendar JavaDoc earliest, boolean strict) {
341         Calendar JavaDoc earliestDay = ScheduleUtil.floor(earliest);
342         Calendar JavaDoc earliestTime = ScheduleUtil.getTime(earliest);
343         // first, try a later time on the same day
344
Calendar JavaDoc nextDay = dateSchedule.nextOccurrence(earliestDay, false);
345         Calendar JavaDoc nextTime = timeSchedule.nextOccurrence(earliestTime, strict);
346         if (nextTime == null) {
347             // next, try the first time on a later day
348
nextDay = dateSchedule.nextOccurrence(earliestDay, true);
349             nextTime = timeSchedule.nextOccurrence(ScheduleUtil.midnightTime, false);
350         }
351         if (nextDay == null || nextTime == null) {
352             return null;
353         }
354         nextDay.set(Calendar.HOUR_OF_DAY, nextTime.get(Calendar.HOUR_OF_DAY));
355         nextDay.set(Calendar.MINUTE, nextTime.get(Calendar.MINUTE));
356         nextDay.set(Calendar.SECOND, nextTime.get(Calendar.SECOND));
357         nextDay.set(Calendar.MILLISECOND, nextTime.get(Calendar.MILLISECOND));
358         return nextDay.getTime();
359     }
360 }
361
362 /**
363  * A <code>TimeSchedule</code> generates a series of times within a day.
364  */

365 interface TimeSchedule {
366     /**
367      * Returns the next occurrence at or after <code>after</code>. If
368      * <code>after</code> is null, returns the first occurrence. If there are
369      * no further occurrences, returns null.
370      *
371      * @param strict if true, return time must be after <code>after</code>, not
372      * equal to it
373      */

374     Calendar JavaDoc nextOccurrence(Calendar JavaDoc earliest, boolean strict);
375 }
376
377 /**
378  * A <code>OnceTimeSchedule</code> fires at one and only one time.
379  */

380 class OnceTimeSchedule implements TimeSchedule {
381     Calendar JavaDoc time;
382     OnceTimeSchedule(Calendar JavaDoc time) {
383         ScheduleUtil.assertTrue(time != null);
384         ScheduleUtil.assertTrue(ScheduleUtil.isTime(time));
385         this.time = time;
386     }
387     public Calendar JavaDoc nextOccurrence(Calendar JavaDoc after, boolean strict) {
388         if (after == null) {
389             return time;
390         }
391         if (time.after(after)) {
392             return time;
393         }
394         if (!strict && time.equals(after)) {
395             return time;
396         }
397         return null;
398     }
399 }
400
401 /**
402  * A <code>DateSchedule</code> returns a series of dates.
403  */

404 interface DateSchedule {
405     /**
406      * Returns the next date when this schedule fires.
407      *
408      * @pre earliest != null
409      */

410     Calendar JavaDoc nextOccurrence(Calendar JavaDoc earliest, boolean strict);
411 };
412
413 /**
414  * A <code>DailyDateSchedule</code> fires every day.
415  */

416 class DailyDateSchedule implements DateSchedule {
417     int period;
418     int beginOrdinal;
419     DailyDateSchedule(Calendar JavaDoc begin, int period) {
420         this.period = period;
421         ScheduleUtil.assertTrue(period > 0, "period must be positive");
422         this.beginOrdinal = ScheduleUtil.julianDay(
423                 begin == null ? ScheduleUtil.epochDay : begin);
424     }
425
426     public Calendar JavaDoc nextOccurrence(Calendar JavaDoc day, boolean strict) {
427         day = (Calendar JavaDoc) day.clone();
428         if (strict) {
429             day.add(Calendar.DATE, 1);
430         }
431         while (true) {
432             int ordinal = ScheduleUtil.julianDay(day);
433             if ((ordinal - beginOrdinal) % period == 0) {
434                 return day;
435             }
436             day.add(Calendar.DATE, 1);
437         }
438     }
439 }
440
441 /**
442  * A <code>WeeklyDateSchedule</code> fires every week. A bitmap indicates
443  * which days of the week it fires.
444  */

445 class WeeklyDateSchedule implements DateSchedule {
446     int period;
447     int beginOrdinal;
448     int daysOfWeekBitmap;
449
450     WeeklyDateSchedule(Calendar JavaDoc begin, int period, int daysOfWeekBitmap) {
451         this.period = period;
452         ScheduleUtil.assertTrue(period > 0, "period must be positive");
453         this.beginOrdinal = ScheduleUtil.julianDay(
454                 begin == null ? ScheduleUtil.epochDay : begin) / 7;
455         this.daysOfWeekBitmap = daysOfWeekBitmap;
456         ScheduleUtil.assertTrue(
457                 (daysOfWeekBitmap & Schedule.allDaysOfWeekBitmap) != 0,
458                 "weekly schedule must have at least one day set");
459         ScheduleUtil.assertTrue(
460                 (daysOfWeekBitmap & Schedule.allDaysOfWeekBitmap) == daysOfWeekBitmap,
461                 "weekly schedule has bad bits set: " + daysOfWeekBitmap);
462     }
463
464     public Calendar JavaDoc nextOccurrence(Calendar JavaDoc earliest, boolean strict) {
465         earliest = (Calendar JavaDoc) earliest.clone();
466         if (strict) {
467             earliest.add(Calendar.DATE, 1);
468         }
469         int i = 7 + period; // should be enough
470
while (i-- > 0) {
471             int dayOfWeek = earliest.get(Calendar.DAY_OF_WEEK);
472             if ((daysOfWeekBitmap & (1 << dayOfWeek)) != 0) {
473                 int ordinal = ScheduleUtil.julianDay(earliest) / 7;
474                 if ((ordinal - beginOrdinal) % period == 0) {
475                     return earliest;
476                 }
477             }
478             earliest.add(Calendar.DATE, 1);
479         }
480         throw ScheduleUtil.newInternal(
481                 "weekly date schedule is looping -- maybe the " +
482                 "bitmap is empty: " + daysOfWeekBitmap);
483     }
484 }
485
486 /**
487  * A <code>MonthlyByDayDateSchedule</code> fires on a particular set of days
488  * every month.
489  */

490 class MonthlyByDayDateSchedule implements DateSchedule {
491     int period;
492     int beginMonth;
493     int daysOfMonthBitmap;
494
495     MonthlyByDayDateSchedule(
496             Calendar JavaDoc begin, int period, int daysOfMonthBitmap) {
497         this.period = period;
498         ScheduleUtil.assertTrue(period > 0, "period must be positive");
499         this.beginMonth = begin == null ? 0 : monthOrdinal(begin);
500         this.daysOfMonthBitmap = daysOfMonthBitmap;
501         ScheduleUtil.assertTrue(
502                 (daysOfMonthBitmap & Schedule.allDaysOfMonthBitmap) != 0,
503                 "monthly day schedule must have at least one day set");
504         ScheduleUtil.assertTrue(
505                 (daysOfMonthBitmap & Schedule.allDaysOfMonthBitmap) ==
506                 daysOfMonthBitmap,
507                 "monthly schedule has bad bits set: " + daysOfMonthBitmap);
508     }
509
510     public Calendar JavaDoc nextOccurrence(Calendar JavaDoc earliest, boolean strict) {
511         earliest = (Calendar JavaDoc) earliest.clone();
512         if (strict) {
513             earliest.add(Calendar.DATE, 1);
514         }
515         int i = 31 + period; // should be enough
516
while (i-- > 0) {
517             int month = monthOrdinal(earliest);
518             if ((month - beginMonth) % period != 0) {
519                 // not this month! move to first of next month
520
earliest.set(Calendar.DAY_OF_MONTH, 1);
521                 earliest.add(Calendar.MONTH, 1);
522                 continue;
523             }
524             int dayOfMonth = earliest.get(Calendar.DAY_OF_MONTH);
525             if ((daysOfMonthBitmap & (1 << dayOfMonth)) != 0) {
526                 return earliest;
527             }
528             earliest.add(Calendar.DATE, 1);
529             if ((daysOfMonthBitmap & (1 << Schedule.LAST_DAY_OF_MONTH)) != 0 &&
530                     earliest.get(Calendar.DAY_OF_MONTH) == 1) {
531                 // They want us to fire on the last day of the month, and
532
// now we're at the first day of the month, so we must have
533
// been at the last. Backtrack and return it.
534
earliest.add(Calendar.DATE, -1);
535                 return earliest;
536             }
537         }
538         throw ScheduleUtil.newInternal(
539                 "monthly-by-day date schedule is looping -- maybe " +
540                 "the bitmap is empty: " + daysOfMonthBitmap);
541     }
542
543     private static int monthOrdinal(Calendar JavaDoc earliest) {
544         return earliest.get(Calendar.YEAR) * 12 +
545             earliest.get(Calendar.MONTH);
546     }
547 }
548
549 /**
550  * A <code>MonthlyByWeekDateSchedule</code> fires on particular days of
551  * particular weeks of a month.
552  */

553 class MonthlyByWeekDateSchedule implements DateSchedule {
554     int period;
555     int beginMonth;
556     int daysOfWeekBitmap;
557     int weeksOfMonthBitmap;
558
559     MonthlyByWeekDateSchedule(
560             Calendar JavaDoc begin, int period, int daysOfWeekBitmap,
561             int weeksOfMonthBitmap) {
562         this.period = period;
563         ScheduleUtil.assertTrue(period > 0, "period must be positive");
564         this.beginMonth = begin == null ? 0 : monthOrdinal(begin);
565         this.daysOfWeekBitmap = daysOfWeekBitmap;
566         ScheduleUtil.assertTrue(
567                 (daysOfWeekBitmap & Schedule.allDaysOfWeekBitmap) != 0,
568                 "weekly schedule must have at least one day set");
569         ScheduleUtil.assertTrue(
570                 (daysOfWeekBitmap & Schedule.allDaysOfWeekBitmap) ==
571                 daysOfWeekBitmap,
572                 "weekly schedule has bad bits set: " + daysOfWeekBitmap);
573         this.weeksOfMonthBitmap = weeksOfMonthBitmap;
574         ScheduleUtil.assertTrue(
575                 (weeksOfMonthBitmap & Schedule.allWeeksOfMonthBitmap) != 0,
576                 "weeks of month schedule must have at least one week set");
577         ScheduleUtil.assertTrue(
578                 (weeksOfMonthBitmap & Schedule.allWeeksOfMonthBitmap) ==
579                 weeksOfMonthBitmap,
580                 "week of month schedule has bad bits set: " +
581                 weeksOfMonthBitmap);
582     }
583
584     public Calendar JavaDoc nextOccurrence(Calendar JavaDoc earliest, boolean strict) {
585         earliest = (Calendar JavaDoc) earliest.clone();
586         if (strict) {
587             earliest.add(Calendar.DATE, 1);
588         }
589          // should be enough... worst case is '5th Monday of every 3rd month'
590
int i = 365 + period;
591         while (i-- > 0) {
592             int month = monthOrdinal(earliest);
593             if ((month - beginMonth) % period != 0) {
594                 // not this month! move to first of next month
595
earliest.set(Calendar.DAY_OF_MONTH, 1);
596                 earliest.add(Calendar.MONTH, 1);
597                 continue;
598             }
599             // is it one of the days we're interested in?
600
int dayOfWeek = earliest.get(Calendar.DAY_OF_WEEK);
601             if ((daysOfWeekBitmap & (1 << dayOfWeek)) != 0) {
602                 // is it the Yth occurrence of day X?
603
int dayOfMonth = earliest.get(Calendar.DAY_OF_MONTH);
604                 int weekOfMonth = (dayOfMonth + 6) / 7; // 1-based
605
if ((weeksOfMonthBitmap & (1 << weekOfMonth)) != 0) {
606                     return earliest;
607                 }
608                 // is it the last occurrence of day X?
609
if ((weeksOfMonthBitmap & (1 << Schedule.LAST_WEEK_OF_MONTH))
610                         != 0) {
611                     // we're in the last week of the month iff a week later is
612
// in the first week of the next month
613
earliest.add(Calendar.WEEK_OF_MONTH, 1);
614                     boolean isLast = earliest.get(Calendar.DAY_OF_MONTH) <= 7;
615                     earliest.add(Calendar.WEEK_OF_MONTH, -1);
616                     if (isLast) {
617                         return earliest;
618                     }
619                 }
620             }
621             earliest.add(Calendar.DATE, 1);
622         }
623         throw ScheduleUtil.newInternal(
624                 "monthy-by-week date schedule is cyclic");
625     }
626
627     private static int monthOrdinal(Calendar JavaDoc earliest) {
628         return earliest.get(Calendar.YEAR) * 12 +
629             earliest.get(Calendar.MONTH);
630     }
631 }
632
633 /**
634  * Utility functions for {@link Schedule} and supporting classes.
635  */

636 class ScheduleUtil {
637     static final Calendar JavaDoc epochDay = ScheduleUtil.createCalendar(new Date JavaDoc(0));
638     static final Calendar JavaDoc midnightTime = ScheduleUtil.createTimeCalendar(0,0,0);
639
640     public static void assertTrue(boolean b) {
641         if (!b) {
642             throw new Error JavaDoc("assertion failed");
643         }
644     }
645     public static void assertTrue(boolean b, String JavaDoc s) {
646         if (!b) {
647             throw new Error JavaDoc("assertion failed: " + s);
648         }
649     }
650     public static Error JavaDoc newInternal() {
651         return new Error JavaDoc("internal error");
652     }
653     public static Error JavaDoc newInternal(Throwable JavaDoc e, String JavaDoc s) {
654         return new Error JavaDoc("internal error '" + e + "': " + s);
655     }
656     public static Error JavaDoc newInternal(String JavaDoc s) {
657         return new Error JavaDoc("internal error: " + s);
658     }
659     public static boolean lessThan(Time JavaDoc t1, Time JavaDoc t2, boolean strict) {
660         if (strict) {
661             return t1.getTime() < t2.getTime();
662         } else {
663             return t1.getTime() <= t2.getTime();
664         }
665     }
666     public static boolean lessThan(Date JavaDoc d1, Date JavaDoc d2, boolean strict) {
667         if (strict) {
668             return d1.getTime() < d2.getTime();
669         } else {
670             return d1.getTime() <= d2.getTime();
671         }
672     }
673     public static boolean is0000(Calendar JavaDoc calendar) {
674         return calendar.get(Calendar.HOUR_OF_DAY) == 0 &&
675             calendar.get(Calendar.MINUTE) == 0 &&
676             calendar.get(Calendar.SECOND) == 0 &&
677             calendar.get(Calendar.MILLISECOND) == 0;
678     }
679     public static boolean isTime(Calendar JavaDoc calendar) {
680         return calendar.get(Calendar.YEAR) ==
681                 ScheduleUtil.epochDay.get(Calendar.YEAR) &&
682             calendar.get(Calendar.DAY_OF_YEAR) ==
683                 ScheduleUtil.epochDay.get(Calendar.DAY_OF_YEAR);
684     }
685     /**
686      * Returns a calendar rounded down to the previous midnight.
687      */

688     public static Calendar JavaDoc floor(Calendar JavaDoc calendar) {
689         if (calendar == null) {
690             return null;
691         }
692         calendar = (Calendar JavaDoc) calendar.clone();
693         calendar.set(Calendar.HOUR_OF_DAY, 0);
694         calendar.set(Calendar.MINUTE, 0);
695         calendar.set(Calendar.SECOND, 0);
696         calendar.set(Calendar.MILLISECOND, 0);
697         return calendar;
698     }
699     /**
700      * Returns a calendar rounded up to the next midnight, unless it is already
701      * midnight.
702      */

703     public static Calendar JavaDoc ceiling(Calendar JavaDoc calendar) {
704         if (calendar == null) {
705             return null;
706         }
707         if (is0000(calendar)) {
708             return calendar;
709         }
710         calendar = (Calendar JavaDoc) calendar.clone();
711         calendar.add(Calendar.DATE, 1);
712         return calendar;
713     }
714
715     /**
716      * Extracts the time part of a date. Given a null date, returns null.
717      */

718     public static Calendar JavaDoc getTime(Calendar JavaDoc calendar) {
719         if (calendar == null) {
720             return null;
721         }
722         return createTimeCalendar(
723                 calendar.get(Calendar.HOUR_OF_DAY),
724                 calendar.get(Calendar.MINUTE),
725                 calendar.get(Calendar.SECOND));
726     }
727     /**
728      * Creates a calendar in UTC, and initializes it to <code>date</code>.
729      *
730      * @pre date != null
731      * @post return != null
732      */

733     public static Calendar JavaDoc createCalendar(Date JavaDoc date) {
734         Calendar JavaDoc calendar = Calendar.getInstance();
735         calendar.setTimeZone(Schedule.utcTimeZone);
736         calendar.setTime(date);
737         return calendar;
738     }
739     /**
740      * Creates a calendar in UTC, and initializes it to a given year, month,
741      * day, hour, minute, second. <b>NOTE: month is 1-based</b>
742      */

743     public static Calendar JavaDoc createCalendar(
744             int year, int month, int day, int hour, int minute, int second) {
745         Calendar JavaDoc calendar = Calendar.getInstance();
746         calendar.setTimeZone(Schedule.utcTimeZone);
747         calendar.toString(); // calls complete()
748
calendar.set(Calendar.YEAR, year);
749         calendar.set(Calendar.MONTH, month - 1); // CONVERT TO 0-BASED!!
750
calendar.set(Calendar.DAY_OF_MONTH, day);
751         calendar.set(Calendar.HOUR_OF_DAY, hour);
752         calendar.set(Calendar.MINUTE, minute);
753         calendar.set(Calendar.SECOND, second);
754         calendar.set(Calendar.MILLISECOND, 0);
755         return calendar;
756     }
757     /**
758      * Creates a calendar from a time. Milliseconds are ignored.
759      *
760      * @pre time != null
761      * @post return != null
762      */

763     public static Calendar JavaDoc createTimeCalendar(Time JavaDoc time) {
764         Calendar JavaDoc calendar = (Calendar JavaDoc) ScheduleUtil.epochDay.clone();
765         calendar.setTimeZone(Schedule.utcTimeZone);
766         calendar.setTime(time);
767         return createTimeCalendar(
768                 calendar.get(Calendar.HOUR_OF_DAY),
769                 calendar.get(Calendar.MINUTE),
770                 calendar.get(Calendar.SECOND));
771     }
772     /**
773      * Creates a calendar and sets it to a given hours, minutes, seconds.
774      */

775     public static Calendar JavaDoc createTimeCalendar(
776             int hours, int minutes, int seconds) {
777         Calendar JavaDoc calendar = (Calendar JavaDoc) ScheduleUtil.epochDay.clone();
778         calendar.set(Calendar.HOUR_OF_DAY, hours);
779         calendar.set(Calendar.MINUTE, minutes);
780         calendar.set(Calendar.SECOND, seconds);
781         calendar.set(Calendar.MILLISECOND, 0);
782         return calendar;
783     }
784     /**
785      * Creates a calendar and sets it to a given year, month, date.
786      */

787     public static Calendar JavaDoc createDateCalendar(
788             int year, int month, int dayOfMonth) {
789         Calendar JavaDoc calendar = Calendar.getInstance();
790         calendar.setTimeZone(Schedule.utcTimeZone);
791         calendar.set(Calendar.YEAR, year);
792         calendar.set(Calendar.MONTH, month);
793         calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
794         return calendar;
795     }
796     /**
797      * Creates a {@link java.sql.Time}
798      */

799     public static Time JavaDoc createTime(int hour, int minutes, int second) {
800         return new Time JavaDoc(
801                 createTimeCalendar(hour, minutes, second).getTime().getTime());
802     }
803     /**
804      * Returns the julian day number of a given date. (Is there a better way
805      * to do this?)
806      */

807     public static int julianDay(Calendar JavaDoc calendar) {
808         int year = calendar.get(Calendar.YEAR),
809             day = calendar.get(Calendar.DAY_OF_YEAR),
810             leapDays = (year / 4) - (year / 100) + (year / 400);
811         return year * 365 + leapDays + day;
812     }
813     /**
814      * Returns the offset from UTC in milliseconds in this timezone on a given
815      * date.
816      */

817     public static int timezoneOffset(TimeZone JavaDoc tz, Calendar JavaDoc calendar) {
818         return tz.getOffset(
819                 calendar.get(Calendar.ERA),
820                 calendar.get(Calendar.YEAR),
821                 calendar.get(Calendar.MONTH),
822                 calendar.get(Calendar.DAY_OF_MONTH),
823                 calendar.get(Calendar.DAY_OF_WEEK),
824                 (1000 *
825                     (60 *
826                         (60 * calendar.get(Calendar.HOUR_OF_DAY) +
827                             calendar.get(Calendar.MINUTE)) +
828                     calendar.get(Calendar.SECOND)) +
829                 calendar.get(Calendar.MILLISECOND)));
830     }
831 }
832
833 // End Schedule.java
834
Popular Tags