| 1 10 11 package mondrian.util; 12 13 import java.sql.Time ; 14 import java.util.Calendar ; 15 import java.util.Date ; 16 import java.util.TimeZone ; 17 18 50 public class Schedule { 51 52 54 private DateSchedule dateSchedule; 55 private TimeSchedule timeSchedule; 56 private TimeZone tz; 57 private Date begin; 58 private Date end; 59 60 62 66 public static final int LAST_DAY_OF_MONTH = 0; 67 71 public static final int LAST_WEEK_OF_MONTH = 0; 72 73 static final TimeZone 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 | (1 << LAST_DAY_OF_MONTH); 85 static final int allWeeksOfMonthBitmap = 0x0000003e | (1 << LAST_WEEK_OF_MONTH); 87 88 90 94 private Schedule( 95 DateSchedule dateSchedule, 96 TimeSchedule timeSchedule, 97 TimeZone tz, 98 Date begin, 99 Date end) { 100 this.dateSchedule = dateSchedule; 101 this.timeSchedule = timeSchedule; 102 this.tz = tz; 103 this.begin = begin; 104 this.end = end; 105 } 106 107 117 public static Schedule createOnce(Date date, TimeZone tz) { 118 Calendar calendar = ScheduleUtil.createCalendar(date); 119 Time 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 datePlusDelta = calendar.getTime(); 125 return createDaily(date, datePlusDelta, tz, timeOfDay, 1); 126 } 127 128 144 public static Schedule createDaily( 145 Date begin, Date end, TimeZone tz, Time 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 177 public static Schedule createWeekly( 178 Date begin, Date end, TimeZone tz, 179 Time 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 218 public static Schedule createMonthlyByDay( 219 Date begin, Date end, TimeZone tz, Time 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 261 public static Schedule createMonthlyByWeek( 262 Date begin, Date end, TimeZone tz, 263 Time 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 290 public Date nextOccurrence(Date 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 (0); 298 } 299 Date next = nextOccurrence0(after, strict); 300 if (next != null && 303 end != null && 304 !next.before(end)) { 305 next = null; 306 } 307 return next; 308 } 309 310 private Date nextOccurrence0(Date after, boolean strict) { 311 Calendar 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 next.add(Calendar.MILLISECOND, offset); 328 Date result = nextOccurrence1(next, strict); 329 if (result == null) { 330 return null; 331 } 332 Calendar resultCalendar = ScheduleUtil.createCalendar(result); 333 int offset2 = ScheduleUtil.timezoneOffset(tz, resultCalendar); 334 resultCalendar.add(Calendar.MILLISECOND, -offset2); 336 return resultCalendar.getTime(); 337 } 338 } 339 340 private Date nextOccurrence1(Calendar earliest, boolean strict) { 341 Calendar earliestDay = ScheduleUtil.floor(earliest); 342 Calendar earliestTime = ScheduleUtil.getTime(earliest); 343 Calendar nextDay = dateSchedule.nextOccurrence(earliestDay, false); 345 Calendar nextTime = timeSchedule.nextOccurrence(earliestTime, strict); 346 if (nextTime == null) { 347 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 365 interface TimeSchedule { 366 374 Calendar nextOccurrence(Calendar earliest, boolean strict); 375 } 376 377 380 class OnceTimeSchedule implements TimeSchedule { 381 Calendar time; 382 OnceTimeSchedule(Calendar time) { 383 ScheduleUtil.assertTrue(time != null); 384 ScheduleUtil.assertTrue(ScheduleUtil.isTime(time)); 385 this.time = time; 386 } 387 public Calendar nextOccurrence(Calendar 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 404 interface DateSchedule { 405 410 Calendar nextOccurrence(Calendar earliest, boolean strict); 411 }; 412 413 416 class DailyDateSchedule implements DateSchedule { 417 int period; 418 int beginOrdinal; 419 DailyDateSchedule(Calendar 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 nextOccurrence(Calendar day, boolean strict) { 427 day = (Calendar ) 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 445 class WeeklyDateSchedule implements DateSchedule { 446 int period; 447 int beginOrdinal; 448 int daysOfWeekBitmap; 449 450 WeeklyDateSchedule(Calendar 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 nextOccurrence(Calendar earliest, boolean strict) { 465 earliest = (Calendar ) earliest.clone(); 466 if (strict) { 467 earliest.add(Calendar.DATE, 1); 468 } 469 int i = 7 + period; 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 490 class MonthlyByDayDateSchedule implements DateSchedule { 491 int period; 492 int beginMonth; 493 int daysOfMonthBitmap; 494 495 MonthlyByDayDateSchedule( 496 Calendar 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 nextOccurrence(Calendar earliest, boolean strict) { 511 earliest = (Calendar ) earliest.clone(); 512 if (strict) { 513 earliest.add(Calendar.DATE, 1); 514 } 515 int i = 31 + period; while (i-- > 0) { 517 int month = monthOrdinal(earliest); 518 if ((month - beginMonth) % period != 0) { 519 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 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 earliest) { 544 return earliest.get(Calendar.YEAR) * 12 + 545 earliest.get(Calendar.MONTH); 546 } 547 } 548 549 553 class MonthlyByWeekDateSchedule implements DateSchedule { 554 int period; 555 int beginMonth; 556 int daysOfWeekBitmap; 557 int weeksOfMonthBitmap; 558 559 MonthlyByWeekDateSchedule( 560 Calendar 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 nextOccurrence(Calendar earliest, boolean strict) { 585 earliest = (Calendar ) earliest.clone(); 586 if (strict) { 587 earliest.add(Calendar.DATE, 1); 588 } 589 int i = 365 + period; 591 while (i-- > 0) { 592 int month = monthOrdinal(earliest); 593 if ((month - beginMonth) % period != 0) { 594 earliest.set(Calendar.DAY_OF_MONTH, 1); 596 earliest.add(Calendar.MONTH, 1); 597 continue; 598 } 599 int dayOfWeek = earliest.get(Calendar.DAY_OF_WEEK); 601 if ((daysOfWeekBitmap & (1 << dayOfWeek)) != 0) { 602 int dayOfMonth = earliest.get(Calendar.DAY_OF_MONTH); 604 int weekOfMonth = (dayOfMonth + 6) / 7; if ((weeksOfMonthBitmap & (1 << weekOfMonth)) != 0) { 606 return earliest; 607 } 608 if ((weeksOfMonthBitmap & (1 << Schedule.LAST_WEEK_OF_MONTH)) 610 != 0) { 611 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 earliest) { 628 return earliest.get(Calendar.YEAR) * 12 + 629 earliest.get(Calendar.MONTH); 630 } 631 } 632 633 636 class ScheduleUtil { 637 static final Calendar epochDay = ScheduleUtil.createCalendar(new Date (0)); 638 static final Calendar midnightTime = ScheduleUtil.createTimeCalendar(0,0,0); 639 640 public static void assertTrue(boolean b) { 641 if (!b) { 642 throw new Error ("assertion failed"); 643 } 644 } 645 public static void assertTrue(boolean b, String s) { 646 if (!b) { 647 throw new Error ("assertion failed: " + s); 648 } 649 } 650 public static Error newInternal() { 651 return new Error ("internal error"); 652 } 653 public static Error newInternal(Throwable e, String s) { 654 return new Error ("internal error '" + e + "': " + s); 655 } 656 public static Error newInternal(String s) { 657 return new Error ("internal error: " + s); 658 } 659 public static boolean lessThan(Time t1, Time 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 d1, Date 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 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 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 688 public static Calendar floor(Calendar calendar) { 689 if (calendar == null) { 690 return null; 691 } 692 calendar = (Calendar ) 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 703 public static Calendar ceiling(Calendar calendar) { 704 if (calendar == null) { 705 return null; 706 } 707 if (is0000(calendar)) { 708 return calendar; 709 } 710 calendar = (Calendar ) calendar.clone(); 711 calendar.add(Calendar.DATE, 1); 712 return calendar; 713 } 714 715 718 public static Calendar getTime(Calendar 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 733 public static Calendar createCalendar(Date date) { 734 Calendar calendar = Calendar.getInstance(); 735 calendar.setTimeZone(Schedule.utcTimeZone); 736 calendar.setTime(date); 737 return calendar; 738 } 739 743 public static Calendar createCalendar( 744 int year, int month, int day, int hour, int minute, int second) { 745 Calendar calendar = Calendar.getInstance(); 746 calendar.setTimeZone(Schedule.utcTimeZone); 747 calendar.toString(); calendar.set(Calendar.YEAR, year); 749 calendar.set(Calendar.MONTH, month - 1); 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 763 public static Calendar createTimeCalendar(Time time) { 764 Calendar calendar = (Calendar ) 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 775 public static Calendar createTimeCalendar( 776 int hours, int minutes, int seconds) { 777 Calendar calendar = (Calendar ) 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 787 public static Calendar createDateCalendar( 788 int year, int month, int dayOfMonth) { 789 Calendar 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 799 public static Time createTime(int hour, int minutes, int second) { 800 return new Time ( 801 createTimeCalendar(hour, minutes, second).getTime().getTime()); 802 } 803 807 public static int julianDay(Calendar 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 817 public static int timezoneOffset(TimeZone tz, Calendar 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 | Popular Tags |