0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package java.util;
0019:
0020: import java.io.IOException;
0021: import java.io.ObjectInputStream;
0022: import java.io.ObjectOutputStream;
0023:
0024: /**
0025: * GregorianCalendar provides the conversion between Dates and integer calendar
0026: * fields, such as the month, year or minute, for the Gregorian calendar. See
0027: * Calendar for the defined fields.
0028: *
0029: * @see Calendar
0030: * @see TimeZone
0031: * @see SimpleTimeZone
0032: */
0033: public class GregorianCalendar extends Calendar {
0034:
0035: private static final long serialVersionUID = -8125100834729963327L;
0036:
0037: /**
0038: * Value for the BC era.
0039: */
0040: public static final int BC = 0;
0041:
0042: /**
0043: * Value for the AD era.
0044: */
0045: public static final int AD = 1;
0046:
0047: private static final long defaultGregorianCutover = -12219292800000l;
0048:
0049: private long gregorianCutover = defaultGregorianCutover;
0050:
0051: private transient int changeYear = 1582;
0052:
0053: private transient int julianSkew = ((changeYear - 2000) / 400)
0054: + julianError() - ((changeYear - 2000) / 100);
0055:
0056: static byte[] DaysInMonth = new byte[] { 31, 28, 31, 30, 31, 30,
0057: 31, 31, 30, 31, 30, 31 };
0058:
0059: private static int[] DaysInYear = new int[] { 0, 31, 59, 90, 120,
0060: 151, 181, 212, 243, 273, 304, 334 };
0061:
0062: private static int[] maximums = new int[] { 1, 292278994, 11, 53,
0063: 6, 31, 366, 7, 6, 1, 11, 23, 59, 59, 999, 14 * 3600 * 1000,
0064: 7200000 };
0065:
0066: private static int[] minimums = new int[] { 0, 1, 0, 1, 0, 1, 1, 1,
0067: 1, 0, 0, 0, 0, 0, 0, -13 * 3600 * 1000, 0 };
0068:
0069: private static int[] leastMaximums = new int[] { 1, 292269054, 11,
0070: 50, 3, 28, 355, 7, 3, 1, 11, 23, 59, 59, 999, 50400000,
0071: 1200000 };
0072:
0073: private boolean isCached;
0074:
0075: private int cachedFields[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
0076:
0077: private long nextMidnightMillis = 0L;
0078:
0079: private long lastMidnightMillis = 0L;
0080:
0081: private int currentYearSkew = 10;
0082:
0083: private int lastYearSkew = 0;
0084:
0085: /**
0086: * Constructs a new GregorianCalendar initialized to the current date and
0087: * time.
0088: */
0089: public GregorianCalendar() {
0090: this (TimeZone.getDefault(), Locale.getDefault());
0091: }
0092:
0093: /**
0094: * Constructs a new GregorianCalendar initialized to midnight in the default
0095: * time zone on the specified date.
0096: *
0097: * @param year
0098: * the year
0099: * @param month
0100: * the month
0101: * @param day
0102: * the day of the month
0103: */
0104: public GregorianCalendar(int year, int month, int day) {
0105: super (TimeZone.getDefault(), Locale.getDefault());
0106: set(year, month, day);
0107: }
0108:
0109: /**
0110: * Constructs a new GregorianCalendar initialized to the specified date and
0111: * time.
0112: *
0113: * @param year
0114: * the year
0115: * @param month
0116: * the month
0117: * @param day
0118: * the day of the month
0119: * @param hour
0120: * the hour
0121: * @param minute
0122: * the minute
0123: */
0124: public GregorianCalendar(int year, int month, int day, int hour,
0125: int minute) {
0126: super (TimeZone.getDefault(), Locale.getDefault());
0127: set(year, month, day, hour, minute);
0128: }
0129:
0130: /**
0131: * Constructs a new GregorianCalendar initialized to the specified date and
0132: * time.
0133: *
0134: * @param year
0135: * the year
0136: * @param month
0137: * the month
0138: * @param day
0139: * the day of the month
0140: * @param hour
0141: * the hour
0142: * @param minute
0143: * the minute
0144: * @param second
0145: * the second
0146: */
0147: public GregorianCalendar(int year, int month, int day, int hour,
0148: int minute, int second) {
0149: super (TimeZone.getDefault(), Locale.getDefault());
0150: set(year, month, day, hour, minute, second);
0151: }
0152:
0153: GregorianCalendar(long milliseconds) {
0154: this (false);
0155: setTimeInMillis(milliseconds);
0156: }
0157:
0158: /**
0159: * Constructs a new GregorianCalendar initialized to the current date and
0160: * time and using the specified Locale.
0161: *
0162: * @param locale
0163: * the Locale
0164: */
0165: public GregorianCalendar(Locale locale) {
0166: this (TimeZone.getDefault(), locale);
0167: }
0168:
0169: /**
0170: * Constructs a new GregorianCalendar initialized to the current date and
0171: * time and using the specified TimeZone.
0172: *
0173: * @param timezone
0174: * the TimeZone
0175: */
0176: public GregorianCalendar(TimeZone timezone) {
0177: this (timezone, Locale.getDefault());
0178: }
0179:
0180: /**
0181: * Constructs a new GregorianCalendar initialized to the current date and
0182: * time and using the specified TimeZone and Locale.
0183: *
0184: * @param timezone
0185: * the TimeZone
0186: * @param locale
0187: * the Locale
0188: */
0189: public GregorianCalendar(TimeZone timezone, Locale locale) {
0190: super (timezone, locale);
0191: setTimeInMillis(System.currentTimeMillis());
0192: }
0193:
0194: GregorianCalendar(boolean ignored) {
0195: super (TimeZone.getDefault());
0196: setFirstDayOfWeek(SUNDAY);
0197: setMinimalDaysInFirstWeek(1);
0198: }
0199:
0200: /**
0201: * Adds the specified amount to a Calendar field.
0202: *
0203: * @param field
0204: * the Calendar field to modify
0205: * @param value
0206: * the amount to add to the field
0207: *
0208: * @exception IllegalArgumentException
0209: * when the specified field is DST_OFFSET or ZONE_OFFSET.
0210: */
0211: @Override
0212: public void add(int field, int value) {
0213: if (value == 0) {
0214: return;
0215: }
0216: if (field < 0 || field >= ZONE_OFFSET) {
0217: throw new IllegalArgumentException();
0218: }
0219:
0220: isCached = false;
0221:
0222: if (field == ERA) {
0223: complete();
0224: if (fields[ERA] == AD) {
0225: if (value >= 0) {
0226: return;
0227: }
0228: set(ERA, BC);
0229: } else {
0230: if (value <= 0) {
0231: return;
0232: }
0233: set(ERA, AD);
0234: }
0235: complete();
0236: return;
0237: }
0238:
0239: if (field == YEAR || field == MONTH) {
0240: complete();
0241: if (field == MONTH) {
0242: int month = fields[MONTH] + value;
0243: if (month < 0) {
0244: value = (month - 11) / 12;
0245: month = 12 + (month % 12);
0246: } else {
0247: value = month / 12;
0248: }
0249: set(MONTH, month % 12);
0250: }
0251: set(YEAR, fields[YEAR] + value);
0252: int days = daysInMonth(isLeapYear(fields[YEAR]),
0253: fields[MONTH]);
0254: if (fields[DATE] > days) {
0255: set(DATE, days);
0256: }
0257: complete();
0258: return;
0259: }
0260:
0261: long multiplier = 0;
0262: getTimeInMillis(); // Update the time
0263: switch (field) {
0264: case MILLISECOND:
0265: time += value;
0266: break;
0267: case SECOND:
0268: time += value * 1000L;
0269: break;
0270: case MINUTE:
0271: time += value * 60000L;
0272: break;
0273: case HOUR:
0274: case HOUR_OF_DAY:
0275: time += value * 3600000L;
0276: break;
0277: case AM_PM:
0278: multiplier = 43200000L;
0279: break;
0280: case DATE:
0281: case DAY_OF_YEAR:
0282: case DAY_OF_WEEK:
0283: multiplier = 86400000L;
0284: break;
0285: case WEEK_OF_YEAR:
0286: case WEEK_OF_MONTH:
0287: case DAY_OF_WEEK_IN_MONTH:
0288: multiplier = 604800000L;
0289: break;
0290: }
0291: if (multiplier > 0) {
0292: int zoneOffset = getTimeZone().getRawOffset();
0293: int offset = getOffset(time + zoneOffset);
0294: time += value * multiplier;
0295: int newOffset = getOffset(time + zoneOffset);
0296: // Adjust for moving over a DST boundary
0297: if (newOffset != offset) {
0298: time += offset - newOffset;
0299: }
0300: }
0301: areFieldsSet = false;
0302: complete();
0303: }
0304:
0305: /**
0306: * Creates new instance of GregorianCalendar with the same properties.
0307: *
0308: * @return a shallow copy of this GregorianCalendar
0309: */
0310: @Override
0311: public Object clone() {
0312: GregorianCalendar this Clone = (GregorianCalendar) super .clone();
0313: this Clone.cachedFields = cachedFields.clone();
0314: return this Clone;
0315: }
0316:
0317: private final void fullFieldsCalc(long timeVal, int millis,
0318: int zoneOffset) {
0319: long days = timeVal / 86400000;
0320:
0321: if (millis < 0) {
0322: millis += 86400000;
0323: days--;
0324: }
0325: // Cannot add ZONE_OFFSET to time as it might overflow
0326: millis += zoneOffset;
0327: while (millis < 0) {
0328: millis += 86400000;
0329: days--;
0330: }
0331: while (millis >= 86400000) {
0332: millis -= 86400000;
0333: days++;
0334: }
0335:
0336: int dayOfYear = computeYearAndDay(days, timeVal + zoneOffset);
0337: fields[DAY_OF_YEAR] = dayOfYear;
0338: if (fields[YEAR] == changeYear
0339: && gregorianCutover <= timeVal + zoneOffset) {
0340: dayOfYear += currentYearSkew;
0341: }
0342: int month = dayOfYear / 32;
0343: boolean leapYear = isLeapYear(fields[YEAR]);
0344: int date = dayOfYear - daysInYear(leapYear, month);
0345: if (date > daysInMonth(leapYear, month)) {
0346: date -= daysInMonth(leapYear, month);
0347: month++;
0348: }
0349: fields[DAY_OF_WEEK] = mod7(days - 3) + 1;
0350: int dstOffset = fields[YEAR] <= 0 ? 0 : getTimeZone()
0351: .getOffset(AD, fields[YEAR], month, date,
0352: fields[DAY_OF_WEEK], millis);
0353: if (fields[YEAR] > 0) {
0354: dstOffset -= zoneOffset;
0355: }
0356: fields[DST_OFFSET] = dstOffset;
0357: if (dstOffset != 0) {
0358: long oldDays = days;
0359: millis += dstOffset;
0360: if (millis < 0) {
0361: millis += 86400000;
0362: days--;
0363: } else if (millis >= 86400000) {
0364: millis -= 86400000;
0365: days++;
0366: }
0367: if (oldDays != days) {
0368: dayOfYear = computeYearAndDay(days, timeVal
0369: - zoneOffset + dstOffset);
0370: fields[DAY_OF_YEAR] = dayOfYear;
0371: if (fields[YEAR] == changeYear
0372: && gregorianCutover <= timeVal - zoneOffset
0373: + dstOffset) {
0374: dayOfYear += currentYearSkew;
0375: }
0376: month = dayOfYear / 32;
0377: leapYear = isLeapYear(fields[YEAR]);
0378: date = dayOfYear - daysInYear(leapYear, month);
0379: if (date > daysInMonth(leapYear, month)) {
0380: date -= daysInMonth(leapYear, month);
0381: month++;
0382: }
0383: fields[DAY_OF_WEEK] = mod7(days - 3) + 1;
0384: }
0385: }
0386:
0387: fields[MILLISECOND] = (millis % 1000);
0388: millis /= 1000;
0389: fields[SECOND] = (millis % 60);
0390: millis /= 60;
0391: fields[MINUTE] = (millis % 60);
0392: millis /= 60;
0393: fields[HOUR_OF_DAY] = (millis % 24);
0394: millis /= 24;
0395: fields[AM_PM] = fields[HOUR_OF_DAY] > 11 ? 1 : 0;
0396: fields[HOUR] = fields[HOUR_OF_DAY] % 12;
0397:
0398: if (fields[YEAR] <= 0) {
0399: fields[ERA] = BC;
0400: fields[YEAR] = -fields[YEAR] + 1;
0401: } else {
0402: fields[ERA] = AD;
0403: }
0404: fields[MONTH] = month;
0405: fields[DATE] = date;
0406: fields[DAY_OF_WEEK_IN_MONTH] = (date - 1) / 7 + 1;
0407: fields[WEEK_OF_MONTH] = (date - 1 + mod7(days - date - 2
0408: - (getFirstDayOfWeek() - 1))) / 7 + 1;
0409: int daysFromStart = mod7(days - 3 - (fields[DAY_OF_YEAR] - 1)
0410: - (getFirstDayOfWeek() - 1));
0411: int week = (fields[DAY_OF_YEAR] - 1 + daysFromStart)
0412: / 7
0413: + (7 - daysFromStart >= getMinimalDaysInFirstWeek() ? 1
0414: : 0);
0415: if (week == 0) {
0416: fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart
0417: - (isLeapYear(fields[YEAR] - 1) ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 53
0418: : 52;
0419: } else if (fields[DAY_OF_YEAR] >= (leapYear ? 367 : 366)
0420: - mod7(daysFromStart + (leapYear ? 2 : 1))) {
0421: fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart
0422: + (leapYear ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 1
0423: : week;
0424: } else {
0425: fields[WEEK_OF_YEAR] = week;
0426: }
0427: }
0428:
0429: private final void cachedFieldsCheckAndGet(long timeVal,
0430: long newTimeMillis, long newTimeMillisAdjusted, int millis,
0431: int zoneOffset) {
0432: int dstOffset = fields[DST_OFFSET];
0433: if (!isCached
0434: || newTimeMillis >= nextMidnightMillis
0435: || newTimeMillis <= lastMidnightMillis
0436: || cachedFields[4] != zoneOffset
0437: || (dstOffset == 0 && (newTimeMillisAdjusted >= nextMidnightMillis))
0438: || (dstOffset != 0 && (newTimeMillisAdjusted <= lastMidnightMillis))) {
0439: fullFieldsCalc(timeVal, millis, zoneOffset);
0440: isCached = false;
0441: } else {
0442: fields[YEAR] = cachedFields[0];
0443: fields[MONTH] = cachedFields[1];
0444: fields[DATE] = cachedFields[2];
0445: fields[DAY_OF_WEEK] = cachedFields[3];
0446: fields[ERA] = cachedFields[5];
0447: fields[WEEK_OF_YEAR] = cachedFields[6];
0448: fields[WEEK_OF_MONTH] = cachedFields[7];
0449: fields[DAY_OF_YEAR] = cachedFields[8];
0450: fields[DAY_OF_WEEK_IN_MONTH] = cachedFields[9];
0451: }
0452: }
0453:
0454: /**
0455: * Computes the Calendar fields from the time.
0456: */
0457: @Override
0458: protected void computeFields() {
0459: int zoneOffset = getTimeZone().getRawOffset();
0460:
0461: fields[ZONE_OFFSET] = zoneOffset;
0462:
0463: int millis = (int) (time % 86400000);
0464: int savedMillis = millis;
0465: int dstOffset = fields[DST_OFFSET];
0466: // compute without a change in daylight saving time
0467: int offset = zoneOffset + dstOffset;
0468: long newTime = time + offset;
0469:
0470: if (time > 0L && newTime < 0L && offset > 0) {
0471: newTime = 0x7fffffffffffffffL;
0472: } else if (time < 0L && newTime > 0L && offset < 0) {
0473: newTime = 0x8000000000000000L;
0474: }
0475:
0476: if (isCached) {
0477: if (millis < 0) {
0478: millis += 86400000;
0479: }
0480:
0481: // Cannot add ZONE_OFFSET to time as it might overflow
0482: millis += zoneOffset;
0483: millis += dstOffset;
0484:
0485: if (millis < 0) {
0486: millis += 86400000;
0487: } else if (millis >= 86400000) {
0488: millis -= 86400000;
0489: }
0490:
0491: fields[MILLISECOND] = (millis % 1000);
0492: millis /= 1000;
0493: fields[SECOND] = (millis % 60);
0494: millis /= 60;
0495: fields[MINUTE] = (millis % 60);
0496: millis /= 60;
0497: fields[HOUR_OF_DAY] = (millis % 24);
0498: millis /= 24;
0499: fields[AM_PM] = fields[HOUR_OF_DAY] > 11 ? 1 : 0;
0500: fields[HOUR] = fields[HOUR_OF_DAY] % 12;
0501:
0502: long newTimeAdjusted = newTime;
0503: if (getTimeZone().useDaylightTime()) {
0504: int dstSavings = ((SimpleTimeZone) getTimeZone())
0505: .getDSTSavings();
0506: newTimeAdjusted += (dstOffset == 0) ? dstSavings
0507: : -dstSavings;
0508: }
0509:
0510: if (newTime > 0L && newTimeAdjusted < 0L && dstOffset == 0) {
0511: newTimeAdjusted = 0x7fffffffffffffffL;
0512: } else if (newTime < 0L && newTimeAdjusted > 0L
0513: && dstOffset != 0) {
0514: newTimeAdjusted = 0x8000000000000000L;
0515: }
0516:
0517: cachedFieldsCheckAndGet(time, newTime, newTimeAdjusted,
0518: savedMillis, zoneOffset);
0519: } else {
0520: fullFieldsCalc(time, savedMillis, zoneOffset);
0521: }
0522:
0523: for (int i = 0; i < FIELD_COUNT; i++) {
0524: isSet[i] = true;
0525: }
0526:
0527: // Caching
0528: if (!isCached
0529: && newTime != 0x7fffffffffffffffL
0530: && newTime != 0x8000000000000000L
0531: && (!getTimeZone().useDaylightTime() || getTimeZone() instanceof SimpleTimeZone)) {
0532: int cacheMillis = 0;
0533:
0534: cachedFields[0] = fields[YEAR];
0535: cachedFields[1] = fields[MONTH];
0536: cachedFields[2] = fields[DATE];
0537: cachedFields[3] = fields[DAY_OF_WEEK];
0538: cachedFields[4] = zoneOffset;
0539: cachedFields[5] = fields[ERA];
0540: cachedFields[6] = fields[WEEK_OF_YEAR];
0541: cachedFields[7] = fields[WEEK_OF_MONTH];
0542: cachedFields[8] = fields[DAY_OF_YEAR];
0543: cachedFields[9] = fields[DAY_OF_WEEK_IN_MONTH];
0544:
0545: cacheMillis += (23 - fields[HOUR_OF_DAY]) * 60 * 60 * 1000;
0546: cacheMillis += (59 - fields[MINUTE]) * 60 * 1000;
0547: cacheMillis += (59 - fields[SECOND]) * 1000;
0548: nextMidnightMillis = newTime + cacheMillis;
0549:
0550: cacheMillis = fields[HOUR_OF_DAY] * 60 * 60 * 1000;
0551: cacheMillis += fields[MINUTE] * 60 * 1000;
0552: cacheMillis += fields[SECOND] * 1000;
0553: lastMidnightMillis = newTime - cacheMillis;
0554:
0555: isCached = true;
0556: }
0557: }
0558:
0559: /**
0560: * Computes the time from the Calendar fields.
0561: *
0562: * @exception IllegalArgumentException
0563: * when the time cannot be computed from the current field
0564: * values
0565: */
0566: @Override
0567: protected void computeTime() {
0568: if (!isLenient()) {
0569: if (isSet[HOUR_OF_DAY]) {
0570: if (fields[HOUR_OF_DAY] < 0 || fields[HOUR_OF_DAY] > 23) {
0571: throw new IllegalArgumentException();
0572: }
0573: } else if (isSet[HOUR]
0574: && (fields[HOUR] < 0 || fields[HOUR] > 11)) {
0575: throw new IllegalArgumentException();
0576: }
0577: if (isSet[MINUTE]
0578: && (fields[MINUTE] < 0 || fields[MINUTE] > 59)) {
0579: throw new IllegalArgumentException();
0580: }
0581: if (isSet[SECOND]
0582: && (fields[SECOND] < 0 || fields[SECOND] > 59)) {
0583: throw new IllegalArgumentException();
0584: }
0585: if (isSet[MILLISECOND]
0586: && (fields[MILLISECOND] < 0 || fields[MILLISECOND] > 999)) {
0587: throw new IllegalArgumentException();
0588: }
0589: if (isSet[WEEK_OF_YEAR]
0590: && (fields[WEEK_OF_YEAR] < 1 || fields[WEEK_OF_YEAR] > 53)) {
0591: throw new IllegalArgumentException();
0592: }
0593: if (isSet[DAY_OF_WEEK]
0594: && (fields[DAY_OF_WEEK] < 1 || fields[DAY_OF_WEEK] > 7)) {
0595: throw new IllegalArgumentException();
0596: }
0597: if (isSet[DAY_OF_WEEK_IN_MONTH]
0598: && (fields[DAY_OF_WEEK_IN_MONTH] < 1 || fields[DAY_OF_WEEK_IN_MONTH] > 6)) {
0599: throw new IllegalArgumentException();
0600: }
0601: if (isSet[WEEK_OF_MONTH]
0602: && (fields[WEEK_OF_MONTH] < 1 || fields[WEEK_OF_MONTH] > 6)) {
0603: throw new IllegalArgumentException();
0604: }
0605: if (isSet[AM_PM] && fields[AM_PM] != AM
0606: && fields[AM_PM] != PM) {
0607: throw new IllegalArgumentException();
0608: }
0609: if (isSet[HOUR] && (fields[HOUR] < 0 || fields[HOUR] > 11)) {
0610: throw new IllegalArgumentException();
0611: }
0612: if (isSet[YEAR]) {
0613: if (isSet[ERA]
0614: && fields[ERA] == BC
0615: && (fields[YEAR] < 1 || fields[YEAR] > 292269054)) {
0616: throw new IllegalArgumentException();
0617: } else if (fields[YEAR] < 1 || fields[YEAR] > 292278994) {
0618: throw new IllegalArgumentException();
0619: }
0620: }
0621: if (isSet[MONTH]
0622: && (fields[MONTH] < 0 || fields[MONTH] > 11)) {
0623: throw new IllegalArgumentException();
0624: }
0625: }
0626:
0627: long timeVal;
0628: long hour = 0;
0629: if (isSet[HOUR_OF_DAY] && lastTimeFieldSet != HOUR) {
0630: hour = fields[HOUR_OF_DAY];
0631: } else if (isSet[HOUR]) {
0632: hour = (fields[AM_PM] * 12) + fields[HOUR];
0633: }
0634: timeVal = hour * 3600000;
0635:
0636: if (isSet[MINUTE]) {
0637: timeVal += ((long) fields[MINUTE]) * 60000;
0638: }
0639: if (isSet[SECOND]) {
0640: timeVal += ((long) fields[SECOND]) * 1000;
0641: }
0642: if (isSet[MILLISECOND]) {
0643: timeVal += fields[MILLISECOND];
0644: }
0645:
0646: long days;
0647: int year = isSet[YEAR] ? fields[YEAR] : 1970;
0648: if (isSet[ERA]) {
0649: // Always test for valid ERA, even if the Calendar is lenient
0650: if (fields[ERA] != BC && fields[ERA] != AD) {
0651: throw new IllegalArgumentException();
0652: }
0653: if (fields[ERA] == BC) {
0654: year = 1 - year;
0655: }
0656: }
0657:
0658: boolean weekMonthSet = isSet[WEEK_OF_MONTH]
0659: || isSet[DAY_OF_WEEK_IN_MONTH];
0660: boolean useMonth = (isSet[DATE] || isSet[MONTH] || weekMonthSet)
0661: && lastDateFieldSet != DAY_OF_YEAR;
0662: if (useMonth
0663: && (lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_YEAR)) {
0664: if (isSet[WEEK_OF_YEAR] && isSet[DAY_OF_WEEK]) {
0665: useMonth = lastDateFieldSet != WEEK_OF_YEAR
0666: && weekMonthSet && isSet[DAY_OF_WEEK];
0667: } else if (isSet[DAY_OF_YEAR]) {
0668: useMonth = isSet[DATE] && isSet[MONTH];
0669: }
0670: }
0671:
0672: if (useMonth) {
0673: int month = fields[MONTH];
0674: year += month / 12;
0675: month %= 12;
0676: if (month < 0) {
0677: year--;
0678: month += 12;
0679: }
0680: boolean leapYear = isLeapYear(year);
0681: days = daysFromBaseYear(year) + daysInYear(leapYear, month);
0682: boolean useDate = isSet[DATE];
0683: if (useDate
0684: && (lastDateFieldSet == DAY_OF_WEEK
0685: || lastDateFieldSet == WEEK_OF_MONTH || lastDateFieldSet == DAY_OF_WEEK_IN_MONTH)) {
0686: useDate = !(isSet[DAY_OF_WEEK] && weekMonthSet);
0687: }
0688: if (useDate) {
0689: if (!isLenient()
0690: && (fields[DATE] < 1 || fields[DATE] > daysInMonth(
0691: leapYear, month))) {
0692: throw new IllegalArgumentException();
0693: }
0694: days += fields[DATE] - 1;
0695: } else {
0696: int dayOfWeek;
0697: if (isSet[DAY_OF_WEEK]) {
0698: dayOfWeek = fields[DAY_OF_WEEK] - 1;
0699: } else {
0700: dayOfWeek = getFirstDayOfWeek() - 1;
0701: }
0702: if (isSet[WEEK_OF_MONTH]
0703: && lastDateFieldSet != DAY_OF_WEEK_IN_MONTH) {
0704: int skew = mod7(days - 3
0705: - (getFirstDayOfWeek() - 1));
0706: days += (fields[WEEK_OF_MONTH] - 1) * 7
0707: + mod7(skew + dayOfWeek - (days - 3))
0708: - skew;
0709: } else if (isSet[DAY_OF_WEEK_IN_MONTH]) {
0710: if (fields[DAY_OF_WEEK_IN_MONTH] >= 0) {
0711: days += mod7(dayOfWeek - (days - 3))
0712: + (fields[DAY_OF_WEEK_IN_MONTH] - 1)
0713: * 7;
0714: } else {
0715: days += daysInMonth(leapYear, month)
0716: + mod7(dayOfWeek
0717: - (days
0718: + daysInMonth(leapYear,
0719: month) - 3))
0720: + fields[DAY_OF_WEEK_IN_MONTH] * 7;
0721: }
0722: } else if (isSet[DAY_OF_WEEK]) {
0723: int skew = mod7(days - 3
0724: - (getFirstDayOfWeek() - 1));
0725: days += mod7(mod7(skew + dayOfWeek - (days - 3))
0726: - skew);
0727: }
0728: }
0729: } else {
0730: boolean useWeekYear = isSet[WEEK_OF_YEAR]
0731: && lastDateFieldSet != DAY_OF_YEAR;
0732: if (useWeekYear && isSet[DAY_OF_YEAR]) {
0733: useWeekYear = isSet[DAY_OF_WEEK];
0734: }
0735: days = daysFromBaseYear(year);
0736: if (useWeekYear) {
0737: int dayOfWeek;
0738: if (isSet[DAY_OF_WEEK]) {
0739: dayOfWeek = fields[DAY_OF_WEEK] - 1;
0740: } else {
0741: dayOfWeek = getFirstDayOfWeek() - 1;
0742: }
0743: int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1));
0744: days += (fields[WEEK_OF_YEAR] - 1) * 7
0745: + mod7(skew + dayOfWeek - (days - 3)) - skew;
0746: if (7 - skew < getMinimalDaysInFirstWeek()) {
0747: days += 7;
0748: }
0749: } else if (isSet[DAY_OF_YEAR]) {
0750: if (!isLenient()
0751: && (fields[DAY_OF_YEAR] < 1 || fields[DAY_OF_YEAR] > (365 + (isLeapYear(year) ? 1
0752: : 0)))) {
0753: throw new IllegalArgumentException();
0754: }
0755: days += fields[DAY_OF_YEAR] - 1;
0756: } else if (isSet[DAY_OF_WEEK]) {
0757: days += mod7(fields[DAY_OF_WEEK] - 1 - (days - 3));
0758: }
0759: }
0760: lastDateFieldSet = 0;
0761:
0762: timeVal += days * 86400000;
0763: // Use local time to compare with the gregorian change
0764: if (year == changeYear
0765: && timeVal >= gregorianCutover + julianError()
0766: * 86400000L) {
0767: timeVal -= julianError() * 86400000L;
0768: }
0769:
0770: // It is not possible to simply subtract getOffset(timeVal) from timeVal
0771: // to get UTC.
0772: // The trick is needed for the moment when DST transition occurs,
0773: // say 1:00 is a transition time when DST offset becomes +1 hour,
0774: // then wall time in the interval 1:00 - 2:00 is invalid and is
0775: // treated as UTC time.
0776: long timeValWithoutDST = timeVal - getOffset(timeVal)
0777: + getTimeZone().getRawOffset();
0778: timeVal -= getOffset(timeValWithoutDST);
0779: // Need to update wall time in fields, since it was invalid due to DST
0780: // transition
0781: this .time = timeVal;
0782: if (timeValWithoutDST != timeVal) {
0783: computeFields();
0784: areFieldsSet = true;
0785: }
0786: }
0787:
0788: private int computeYearAndDay(long dayCount, long localTime) {
0789: int year = 1970;
0790: long days = dayCount;
0791: if (localTime < gregorianCutover) {
0792: days -= julianSkew;
0793: }
0794: int approxYears;
0795:
0796: while ((approxYears = (int) (days / 365)) != 0) {
0797: year = year + approxYears;
0798: days = dayCount - daysFromBaseYear(year);
0799: }
0800: if (days < 0) {
0801: year = year - 1;
0802: days = days + daysInYear(year);
0803: }
0804: fields[YEAR] = year;
0805: return (int) days + 1;
0806: }
0807:
0808: private long daysFromBaseYear(int iyear) {
0809: long year = iyear;
0810:
0811: if (year >= 1970) {
0812: long days = (year - 1970) * 365 + ((year - 1969) / 4);
0813: if (year > changeYear) {
0814: days -= ((year - 1901) / 100) - ((year - 1601) / 400);
0815: } else {
0816: if (year == changeYear) {
0817: days += currentYearSkew;
0818: } else if (year == changeYear - 1) {
0819: days += lastYearSkew;
0820: } else {
0821: days += julianSkew;
0822: }
0823: }
0824: return days;
0825: } else if (year <= changeYear) {
0826: return (year - 1970) * 365 + ((year - 1972) / 4)
0827: + julianSkew;
0828: }
0829: return (year - 1970) * 365 + ((year - 1972) / 4)
0830: - ((year - 2000) / 100) + ((year - 2000) / 400);
0831: }
0832:
0833: private int daysInMonth() {
0834: return daysInMonth(isLeapYear(fields[YEAR]), fields[MONTH]);
0835: }
0836:
0837: private int daysInMonth(boolean leapYear, int month) {
0838: if (leapYear && month == FEBRUARY) {
0839: return DaysInMonth[month] + 1;
0840: }
0841:
0842: return DaysInMonth[month];
0843: }
0844:
0845: private int daysInYear(int year) {
0846: int daysInYear = isLeapYear(year) ? 366 : 365;
0847: if (year == changeYear) {
0848: daysInYear -= currentYearSkew;
0849: }
0850: if (year == changeYear - 1) {
0851: daysInYear -= lastYearSkew;
0852: }
0853: return daysInYear;
0854: }
0855:
0856: private int daysInYear(boolean leapYear, int month) {
0857: if (leapYear && month > FEBRUARY) {
0858: return DaysInYear[month] + 1;
0859: }
0860:
0861: return DaysInYear[month];
0862: }
0863:
0864: /**
0865: * Compares the specified object to this GregorianCalendar and answer if
0866: * they are equal. The object must be an instance of GregorianCalendar and
0867: * have the same properties.
0868: *
0869: * @param object
0870: * the object to compare with this object
0871: * @return true if the specified object is equal to this GregorianCalendar,
0872: * false otherwise
0873: *
0874: * @exception IllegalArgumentException
0875: * when the time is not set and the time cannot be computed
0876: * from the current field values
0877: *
0878: * @see #hashCode
0879: */
0880: @Override
0881: public boolean equals(Object object) {
0882: return super .equals(object)
0883: && gregorianCutover == ((GregorianCalendar) object).gregorianCutover;
0884: }
0885:
0886: /**
0887: * Gets the maximum value of the specified field for the current date. For
0888: * example, the maximum number of days in the current month.
0889: *
0890: * @param field
0891: * the field
0892: * @return the maximum value of the specified field
0893: */
0894: @Override
0895: public int getActualMaximum(int field) {
0896: int value;
0897: if ((value = maximums[field]) == leastMaximums[field]) {
0898: return value;
0899: }
0900:
0901: switch (field) {
0902: case WEEK_OF_YEAR:
0903: case WEEK_OF_MONTH:
0904: isCached = false;
0905: break;
0906: }
0907:
0908: complete();
0909: long orgTime = time;
0910: int result = 0;
0911: switch (field) {
0912: case WEEK_OF_YEAR:
0913: set(DATE, 31);
0914: set(MONTH, DECEMBER);
0915: result = get(WEEK_OF_YEAR);
0916: if (result == 1) {
0917: set(DATE, 31 - 7);
0918: result = get(WEEK_OF_YEAR);
0919: }
0920: areFieldsSet = false;
0921: break;
0922: case WEEK_OF_MONTH:
0923: set(DATE, daysInMonth());
0924: result = get(WEEK_OF_MONTH);
0925: areFieldsSet = false;
0926: break;
0927: case DATE:
0928: return daysInMonth();
0929: case DAY_OF_YEAR:
0930: return daysInYear(fields[YEAR]);
0931: case DAY_OF_WEEK_IN_MONTH:
0932: result = get(DAY_OF_WEEK_IN_MONTH)
0933: + ((daysInMonth() - get(DATE)) / 7);
0934: break;
0935: case YEAR:
0936: GregorianCalendar clone = (GregorianCalendar) clone();
0937: if (get(ERA) == AD) {
0938: clone.setTimeInMillis(Long.MAX_VALUE);
0939: } else {
0940: clone.setTimeInMillis(Long.MIN_VALUE);
0941: }
0942: result = clone.get(YEAR);
0943: clone.set(YEAR, get(YEAR));
0944: if (clone.before(this )) {
0945: result--;
0946: }
0947: break;
0948: case DST_OFFSET:
0949: result = getMaximum(DST_OFFSET);
0950: break;
0951: }
0952: time = orgTime;
0953: return result;
0954: }
0955:
0956: /**
0957: * Gets the minimum value of the specified field for the current date. For
0958: * the gregorian calendar, this value is the same as
0959: * <code>getMinimum()</code>.
0960: *
0961: * @param field
0962: * the field
0963: * @return the minimum value of the specified field
0964: */
0965: @Override
0966: public int getActualMinimum(int field) {
0967: return getMinimum(field);
0968: }
0969:
0970: /**
0971: * Gets the greatest minimum value of the specified field. For the gregorian
0972: * calendar, this value is the same as <code>getMinimum()</code>.
0973: *
0974: * @param field
0975: * the field
0976: * @return the greatest minimum value of the specified field
0977: */
0978: @Override
0979: public int getGreatestMinimum(int field) {
0980: return minimums[field];
0981: }
0982:
0983: /**
0984: * Answers the gregorian change date of this calendar. This is the date on
0985: * which the gregorian calendar came into effect.
0986: *
0987: * @return a Date which represents the gregorian change date
0988: */
0989: public final Date getGregorianChange() {
0990: return new Date(gregorianCutover);
0991: }
0992:
0993: /**
0994: * Gets the smallest maximum value of the specified field. For example, 28
0995: * for the day of month field.
0996: *
0997: * @param field
0998: * the field
0999: * @return the smallest maximum value of the specified field
1000: */
1001: @Override
1002: public int getLeastMaximum(int field) {
1003: // return value for WEEK_OF_YEAR should make corresponding changes when
1004: // the gregorian change date have been reset.
1005: if (gregorianCutover != defaultGregorianCutover
1006: && field == WEEK_OF_YEAR) {
1007: long currentTimeInMillis = time;
1008: setTimeInMillis(gregorianCutover);
1009: int actual = getActualMaximum(field);
1010: setTimeInMillis(currentTimeInMillis);
1011: return actual;
1012: }
1013: return leastMaximums[field];
1014: }
1015:
1016: /**
1017: * Gets the greatest maximum value of the specified field. For example, 31
1018: * for the day of month field.
1019: *
1020: * @param field
1021: * the field
1022: * @return the greatest maximum value of the specified field
1023: */
1024: @Override
1025: public int getMaximum(int field) {
1026: return maximums[field];
1027: }
1028:
1029: /**
1030: * Gets the smallest minimum value of the specified field.
1031: *
1032: * @param field
1033: * the field
1034: * @return the smallest minimum value of the specified field
1035: */
1036: @Override
1037: public int getMinimum(int field) {
1038: return minimums[field];
1039: }
1040:
1041: int getOffset(long localTime) {
1042: TimeZone timeZone = getTimeZone();
1043: if (!timeZone.useDaylightTime()) {
1044: return timeZone.getRawOffset();
1045: }
1046:
1047: long dayCount = localTime / 86400000;
1048: int millis = (int) (localTime % 86400000);
1049: if (millis < 0) {
1050: millis += 86400000;
1051: dayCount--;
1052: }
1053:
1054: int year = 1970;
1055: long days = dayCount;
1056: if (localTime < gregorianCutover) {
1057: days -= julianSkew;
1058: }
1059: int approxYears;
1060:
1061: while ((approxYears = (int) (days / 365)) != 0) {
1062: year = year + approxYears;
1063: days = dayCount - daysFromBaseYear(year);
1064: }
1065: if (days < 0) {
1066: year = year - 1;
1067: days = days + 365 + (isLeapYear(year) ? 1 : 0);
1068: if (year == changeYear && localTime < gregorianCutover) {
1069: days -= julianError();
1070: }
1071: }
1072: if (year <= 0) {
1073: return timeZone.getRawOffset();
1074: }
1075: int dayOfYear = (int) days + 1;
1076:
1077: int month = dayOfYear / 32;
1078: boolean leapYear = isLeapYear(year);
1079: int date = dayOfYear - daysInYear(leapYear, month);
1080: if (date > daysInMonth(leapYear, month)) {
1081: date -= daysInMonth(leapYear, month);
1082: month++;
1083: }
1084: int dayOfWeek = mod7(dayCount - 3) + 1;
1085: int offset = timeZone.getOffset(AD, year, month, date,
1086: dayOfWeek, millis);
1087: return offset;
1088: }
1089:
1090: /**
1091: * Answers an integer hash code for the receiver. Objects which are equal
1092: * answer the same value for this method.
1093: *
1094: * @return the receiver's hash
1095: *
1096: * @see #equals
1097: */
1098: @Override
1099: public int hashCode() {
1100: return super .hashCode()
1101: + ((int) (gregorianCutover >>> 32) ^ (int) gregorianCutover);
1102: }
1103:
1104: /**
1105: * Answers if the specified year is a leap year.
1106: *
1107: * @param year
1108: * the year
1109: * @return true if the specified year is a leap year, false otherwise
1110: */
1111: public boolean isLeapYear(int year) {
1112: if (year > changeYear) {
1113: return year % 4 == 0
1114: && (year % 100 != 0 || year % 400 == 0);
1115: }
1116:
1117: return year % 4 == 0;
1118: }
1119:
1120: private int julianError() {
1121: return changeYear / 100 - changeYear / 400 - 2;
1122: }
1123:
1124: private int mod(int value, int mod) {
1125: int rem = value % mod;
1126: if (value < 0 && rem < 0) {
1127: return rem + mod;
1128: }
1129: return rem;
1130: }
1131:
1132: private int mod7(long num1) {
1133: int rem = (int) (num1 % 7);
1134: if (num1 < 0 && rem < 0) {
1135: return rem + 7;
1136: }
1137: return rem;
1138: }
1139:
1140: /**
1141: * Adds the specified amount the specified field and wrap the value of the
1142: * field when it goes beyond the maximum or minimum value for the current
1143: * date. Other fields will be adjusted as required to maintain a consistent
1144: * date.
1145: *
1146: * @param field
1147: * the field to roll
1148: * @param value
1149: * the amount to add
1150: *
1151: * @exception IllegalArgumentException
1152: * when an invalid field is specified
1153: */
1154: @Override
1155: public void roll(int field, int value) {
1156: if (value == 0) {
1157: return;
1158: }
1159: if (field < 0 || field >= ZONE_OFFSET) {
1160: throw new IllegalArgumentException();
1161: }
1162:
1163: isCached = false;
1164:
1165: complete();
1166: int days, day, mod, maxWeeks, newWeek;
1167: int max = -1;
1168: switch (field) {
1169: case YEAR:
1170: max = maximums[field];
1171: break;
1172: case WEEK_OF_YEAR:
1173: days = daysInYear(fields[YEAR]);
1174: day = DAY_OF_YEAR;
1175: mod = mod7(fields[DAY_OF_WEEK] - fields[day]
1176: - (getFirstDayOfWeek() - 1));
1177: maxWeeks = (days - 1 + mod) / 7 + 1;
1178: newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1;
1179: if (newWeek == maxWeeks) {
1180: int addDays = (newWeek - fields[field]) * 7;
1181: if (fields[day] > addDays
1182: && fields[day] + addDays > days) {
1183: set(field, 1);
1184: } else {
1185: set(field, newWeek - 1);
1186: }
1187: } else if (newWeek == 1) {
1188: int week = (fields[day] - ((fields[day] - 1) / 7 * 7)
1189: - 1 + mod) / 7 + 1;
1190: if (week > 1) {
1191: set(field, 1);
1192: } else {
1193: set(field, newWeek);
1194: }
1195: } else {
1196: set(field, newWeek);
1197: }
1198: break;
1199: case WEEK_OF_MONTH:
1200: days = daysInMonth();
1201: day = DATE;
1202: mod = mod7(fields[DAY_OF_WEEK] - fields[day]
1203: - (getFirstDayOfWeek() - 1));
1204: maxWeeks = (days - 1 + mod) / 7 + 1;
1205: newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1;
1206: if (newWeek == maxWeeks) {
1207: if (fields[day] + (newWeek - fields[field]) * 7 > days) {
1208: set(day, days);
1209: } else {
1210: set(field, newWeek);
1211: }
1212: } else if (newWeek == 1) {
1213: int week = (fields[day] - ((fields[day] - 1) / 7 * 7)
1214: - 1 + mod) / 7 + 1;
1215: if (week > 1) {
1216: set(day, 1);
1217: } else {
1218: set(field, newWeek);
1219: }
1220: } else {
1221: set(field, newWeek);
1222: }
1223: break;
1224: case DATE:
1225: max = daysInMonth();
1226: break;
1227: case DAY_OF_YEAR:
1228: max = daysInYear(fields[YEAR]);
1229: break;
1230: case DAY_OF_WEEK:
1231: max = maximums[field];
1232: lastDateFieldSet = WEEK_OF_MONTH;
1233: break;
1234: case DAY_OF_WEEK_IN_MONTH:
1235: max = (fields[DATE]
1236: + ((daysInMonth() - fields[DATE]) / 7 * 7) - 1) / 7 + 1;
1237: break;
1238:
1239: case ERA:
1240: case MONTH:
1241: case AM_PM:
1242: case HOUR:
1243: case HOUR_OF_DAY:
1244: case MINUTE:
1245: case SECOND:
1246: case MILLISECOND:
1247: set(field, mod(fields[field] + value, maximums[field] + 1));
1248: if (field == MONTH && fields[DATE] > daysInMonth()) {
1249: set(DATE, daysInMonth());
1250: } else if (field == AM_PM) {
1251: lastTimeFieldSet = HOUR;
1252: }
1253: break;
1254: }
1255: if (max != -1) {
1256: set(field, mod(fields[field] - 1 + value, max) + 1);
1257: }
1258: complete();
1259: }
1260:
1261: /**
1262: * Increment or decrement the specified field and wrap the value of the
1263: * field when it goes beyond the maximum or minimum value for the current
1264: * date. Other fields will be adjusted as required to maintain a consistent
1265: * date. For example, March 31 will roll to April 30 when rolling the month
1266: * field.
1267: *
1268: * @param field
1269: * the field to roll
1270: * @param increment
1271: * true to increment the field, false to decrement
1272: *
1273: * @exception IllegalArgumentException
1274: * when an invalid field is specified
1275: */
1276: @Override
1277: public void roll(int field, boolean increment) {
1278: roll(field, increment ? 1 : -1);
1279: }
1280:
1281: /**
1282: * Sets the gregorian change date of this calendar.
1283: *
1284: * @param date
1285: * a Date which represents the gregorian change date
1286: */
1287: public void setGregorianChange(Date date) {
1288: gregorianCutover = date.getTime();
1289: GregorianCalendar cal = new GregorianCalendar(TimeZone.GMT);
1290: cal.setTime(date);
1291: changeYear = cal.get(YEAR);
1292: if (cal.get(ERA) == BC) {
1293: changeYear = 1 - changeYear;
1294: }
1295: julianSkew = ((changeYear - 2000) / 400) + julianError()
1296: - ((changeYear - 2000) / 100);
1297: isCached = false;
1298: int dayOfYear = cal.get(DAY_OF_YEAR);
1299: if (dayOfYear < julianSkew) {
1300: currentYearSkew = dayOfYear - 1;
1301: lastYearSkew = julianSkew - dayOfYear + 1;
1302: } else {
1303: lastYearSkew = 0;
1304: currentYearSkew = julianSkew;
1305: }
1306: isCached = false;
1307: }
1308:
1309: private void writeObject(ObjectOutputStream stream)
1310: throws IOException {
1311: stream.defaultWriteObject();
1312: }
1313:
1314: private void readObject(ObjectInputStream stream)
1315: throws IOException, ClassNotFoundException {
1316:
1317: stream.defaultReadObject();
1318: setGregorianChange(new Date(gregorianCutover));
1319: isCached = false;
1320: }
1321:
1322: @Override
1323: public void setFirstDayOfWeek(int value) {
1324: super .setFirstDayOfWeek(value);
1325: isCached = false;
1326: }
1327:
1328: @Override
1329: public void setMinimalDaysInFirstWeek(int value) {
1330: super .setMinimalDaysInFirstWeek(value);
1331: isCached = false;
1332: }
1333: }
|