1 6 package com.ibm.icu.util; 7 import com.ibm.icu.text.*; 8 import com.ibm.icu.util.TimeZone; 9 import com.ibm.icu.impl.CalendarAstronomer; 10 import com.ibm.icu.impl.CalendarCache; 11 12 import java.io.IOException ; 13 import java.io.ObjectInputStream ; 14 import java.util.Locale ; 15 16 85 public class ChineseCalendar extends Calendar { 86 private static final long serialVersionUID = 7312110751940929420L; 88 89 106 110 private transient CalendarAstronomer astro = new CalendarAstronomer(); 111 112 116 private transient CalendarCache winterSolsticeCache = new CalendarCache(); 117 118 122 private transient CalendarCache newYearCache = new CalendarCache(); 123 124 129 private transient boolean isLeapYear; 130 131 135 139 public ChineseCalendar() { 140 super(); 141 setTimeInMillis(System.currentTimeMillis()); 142 } 143 144 150 public ChineseCalendar(TimeZone zone, Locale locale) { 151 super(zone, locale); 152 setTimeInMillis(System.currentTimeMillis()); 153 } 154 155 162 public ChineseCalendar(TimeZone zone, ULocale locale) { 163 super(zone, locale); 164 setTimeInMillis(System.currentTimeMillis()); 165 } 166 167 171 176 public static int IS_LEAP_MONTH = BASE_FIELD_COUNT; 177 178 181 private static final int FIELD_COUNT = IS_LEAP_MONTH + 1; 182 183 187 191 protected int[] handleCreateFields() { 192 return new int[FIELD_COUNT]; 193 } 194 195 231 private static final int LIMITS[][] = { 232 { 1, 1, 83333, 83333 }, { 1, 1, 70, 70 }, { 0, 0, 11, 11 }, { 1, 1, 50, 55 }, { 1, 1, 5, 6 }, { 1, 1, 29, 30 }, { 1, 1, 353, 385 }, {}, { -1, -1, 5, 5 }, {}, {}, {}, {}, {}, {}, {}, {}, { -5000001, -5000001, 5000001, 5000001 }, {}, { -5000000, -5000000, 5000000, 5000000 }, {}, {}, { 0, 0, 1, 1 }, }; 258 259 263 protected int handleGetLimit(int field, int limitType) { 264 return LIMITS[field][limitType]; 265 } 266 267 274 protected int handleGetExtendedYear() { 275 int year; 276 if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) { 277 year = internalGet(EXTENDED_YEAR, 1); } else { 279 int cycle = internalGet(ERA, 1) - 1; year = cycle * 60 + internalGet(YEAR, 1); 281 } 282 return year; 283 } 284 285 293 protected int handleGetMonthLength(int extendedYear, int month) { 294 int thisStart = handleComputeMonthStart(extendedYear, month, true) - 295 EPOCH_JULIAN_DAY + 1; int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true); 297 return nextStart - thisStart; 298 } 299 300 307 protected DateFormat handleGetDateFormat(String pattern, ULocale locale) { 308 return new ChineseDateFormat(pattern, locale); 309 } 310 311 314 static final int[][][] CHINESE_DATE_PRECEDENCE = { 315 { 316 { DAY_OF_MONTH }, 317 { WEEK_OF_YEAR, DAY_OF_WEEK }, 318 { WEEK_OF_MONTH, DAY_OF_WEEK }, 319 { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK }, 320 { WEEK_OF_YEAR, DOW_LOCAL }, 321 { WEEK_OF_MONTH, DOW_LOCAL }, 322 { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL }, 323 { DAY_OF_YEAR }, 324 { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH }, 325 }, 326 { 327 { WEEK_OF_YEAR }, 328 { WEEK_OF_MONTH }, 329 { DAY_OF_WEEK_IN_MONTH }, 330 { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK }, 331 { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL }, 332 }, 333 }; 334 335 340 protected int[][][] getFieldResolutionTable() { 341 return CHINESE_DATE_PRECEDENCE; 342 } 343 344 355 private void offsetMonth(int newMoon, int dom, int delta) { 356 newMoon += (int) (CalendarAstronomer.SYNODIC_MONTH * (delta - 0.5)); 358 359 newMoon = newMoonNear(newMoon, true); 361 362 int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom; 364 365 if (dom > 29) { 368 set(JULIAN_DAY, jd-1); 369 complete(); 374 if (getActualMaximum(DAY_OF_MONTH) >= dom) { 375 set(JULIAN_DAY, jd); 376 } 377 } else { 378 set(JULIAN_DAY, jd); 379 } 380 } 381 382 386 public void add(int field, int amount) { 387 switch (field) { 388 case MONTH: 389 if (amount != 0) { 390 int dom = get(DAY_OF_MONTH); 391 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; int moon = day - dom + 1; offsetMonth(moon, dom, amount); 394 } 395 break; 396 default: 397 super.add(field, amount); 398 break; 399 } 400 } 401 402 406 public void roll(int field, int amount) { 407 switch (field) { 408 case MONTH: 409 if (amount != 0) { 410 int dom = get(DAY_OF_MONTH); 411 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; int moon = day - dom + 1; 414 417 int m = get(MONTH); if (isLeapYear) { if (get(IS_LEAP_MONTH) == 1) { 423 ++m; 424 } else { 425 int moon1 = moon - 433 (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5)); 434 moon1 = newMoonNear(moon1, true); 435 if (isLeapMonthBetween(moon1, moon)) { 436 ++m; 437 } 438 } 439 } 440 441 int n = isLeapYear ? 13 : 12; int newM = (m + amount) % n; 445 if (newM < 0) { 446 newM += n; 447 } 448 449 if (newM != m) { 450 offsetMonth(moon, dom, newM - m); 451 } 452 } 453 break; 454 default: 455 super.roll(field, amount); 456 break; 457 } 458 } 459 460 464 470 private static final int CHINESE_EPOCH_YEAR = -2636; 472 477 private static final long CHINA_OFFSET = 8*ONE_HOUR; 478 479 484 private static final int SYNODIC_GAP = 25; 485 486 491 private static final long daysToMillis(int days) { 492 return (days * ONE_DAY) - CHINA_OFFSET; 493 } 494 495 500 private static final int millisToDays(long millis) { 501 return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY); 502 } 503 504 508 516 private int winterSolstice(int gyear) { 517 518 long cacheValue = winterSolsticeCache.get(gyear); 519 520 if (cacheValue == CalendarCache.EMPTY) { 521 long ms = daysToMillis(computeGregorianMonthStart(gyear, DECEMBER) + 526 1 - EPOCH_JULIAN_DAY); 527 astro.setTime(ms); 528 529 long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE, 531 true); 532 cacheValue = millisToDays(solarLong); 533 winterSolsticeCache.put(gyear, cacheValue); 534 } 535 return (int) cacheValue; 536 } 537 538 547 private int newMoonNear(int days, boolean after) { 548 549 astro.setTime(daysToMillis(days)); 550 long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after); 551 552 return millisToDays(newMoon); 553 } 554 555 562 private int synodicMonthsBetween(int day1, int day2) { 563 return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH); 564 } 565 566 572 private int majorSolarTerm(int days) { 573 574 astro.setTime(daysToMillis(days)); 575 576 int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12; 578 if (term < 1) { 579 term += 12; 580 } 581 return term; 582 } 583 584 589 private boolean hasNoMajorSolarTerm(int newMoon) { 590 591 int mst = majorSolarTerm(newMoon); 592 int nmn = newMoonNear(newMoon + SYNODIC_GAP, true); 593 int mstt = majorSolarTerm(nmn); 594 return mst == mstt; 595 599 } 600 601 605 613 private boolean isLeapMonthBetween(int newMoon1, int newMoon2) { 614 615 if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) { 619 throw new IllegalArgumentException ("isLeapMonthBetween(" + newMoon1 + 620 ", " + newMoon2 + 621 "): Invalid parameters"); 622 } 623 624 return (newMoon2 >= newMoon1) && 625 (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) || 626 hasNoMajorSolarTerm(newMoon2)); 627 } 628 629 647 protected void handleComputeFields(int julianDay) { 648 649 computeChineseFields(julianDay - EPOCH_JULIAN_DAY, getGregorianYear(), getGregorianMonth(), 651 true); } 653 654 670 private void computeChineseFields(int days, int gyear, int gmonth, 671 boolean setAllFields) { 672 673 int solsticeBefore; 678 int solsticeAfter = winterSolstice(gyear); 679 if (days < solsticeAfter) { 680 solsticeBefore = winterSolstice(gyear - 1); 681 } else { 682 solsticeBefore = solsticeAfter; 683 solsticeAfter = winterSolstice(gyear + 1); 684 } 685 686 int firstMoon = newMoonNear(solsticeBefore + 1, true); 690 int lastMoon = newMoonNear(solsticeAfter + 1, false); 691 int thisMoon = newMoonNear(days + 1, false); isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12; 694 695 int month = synodicMonthsBetween(firstMoon, thisMoon); 696 if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) { 697 month--; 698 } 699 if (month < 1) { 700 month += 12; 701 } 702 703 boolean isLeapMonth = isLeapYear && 704 hasNoMajorSolarTerm(thisMoon) && 705 !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false)); 706 707 internalSet(MONTH, month-1); internalSet(IS_LEAP_MONTH, isLeapMonth?1:0); 709 710 if (setAllFields) { 711 712 int year = gyear - CHINESE_EPOCH_YEAR; 713 if (month < 11 || 714 gmonth >= JULY) { 715 year++; 716 } 717 int dayOfMonth = days - thisMoon + 1; 718 719 internalSet(EXTENDED_YEAR, year); 720 721 int[] yearOfCycle = new int[1]; 723 int cycle = floorDivide(year-1, 60, yearOfCycle); 724 internalSet(ERA, cycle+1); 725 internalSet(YEAR, yearOfCycle[0]+1); 726 727 internalSet(DAY_OF_MONTH, dayOfMonth); 728 729 int newYear = newYear(gyear); 734 if (days < newYear) { 735 newYear = newYear(gyear-1); 736 } 737 internalSet(DAY_OF_YEAR, days - newYear + 1); 738 } 739 } 740 741 745 751 private int newYear(int gyear) { 752 753 long cacheValue = newYearCache.get(gyear); 754 755 if (cacheValue == CalendarCache.EMPTY) { 756 757 int solsticeBefore= winterSolstice(gyear - 1); 758 int solsticeAfter = winterSolstice(gyear); 759 int newMoon1 = newMoonNear(solsticeBefore + 1, true); 760 int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true); 761 int newMoon11 = newMoonNear(solsticeAfter + 1, false); 762 763 if (synodicMonthsBetween(newMoon1, newMoon11) == 12 && 764 (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) { 765 cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true); 766 } else { 767 cacheValue = newMoon2; 768 } 769 770 newYearCache.put(gyear, cacheValue); 771 } 772 return (int) cacheValue; 773 } 774 775 788 protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) { 789 790 if (month < 0 || month > 11) { 793 int[] rem = new int[1]; 794 eyear += floorDivide(month, 12, rem); 795 month = rem[0]; 796 } 797 798 int gyear = eyear + CHINESE_EPOCH_YEAR - 1; int newYear = newYear(gyear); 800 int newMoon = newMoonNear(newYear + month * 29, true); 801 802 int julianDay = newMoon + EPOCH_JULIAN_DAY; 803 804 int saveMonth = internalGet(MONTH); 806 int saveIsLeapMonth = internalGet(IS_LEAP_MONTH); 807 808 int isLeapMonth = useMonth ? saveIsLeapMonth : 0; 810 811 computeGregorianFields(julianDay); 812 813 computeChineseFields(newMoon, getGregorianYear(), 815 getGregorianMonth(), false); 816 817 if (month != internalGet(MONTH) || 818 isLeapMonth != internalGet(IS_LEAP_MONTH)) { 819 newMoon = newMoonNear(newMoon + SYNODIC_GAP, true); 820 julianDay = newMoon + EPOCH_JULIAN_DAY; 821 } 822 823 internalSet(MONTH, saveMonth); 824 internalSet(IS_LEAP_MONTH, saveIsLeapMonth); 825 826 return julianDay - 1; 827 } 828 829 835 public String getType() { 836 return "chinese"; 837 } 838 839 842 private void readObject(ObjectInputStream stream) 843 throws IOException , ClassNotFoundException 844 { 845 stream.defaultReadObject(); 846 847 848 astro = new CalendarAstronomer(); 849 winterSolsticeCache = new CalendarCache(); 850 newYearCache = new CalendarCache(); 851 } 852 853 870 } 871 | Popular Tags |