0001: /* ========================================================================
0002: * JCommon : a free general purpose class library for the Java(tm) platform
0003: * ========================================================================
0004: *
0005: * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jcommon/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * ---------------
0028: * SerialDate.java
0029: * ---------------
0030: * (C) Copyright 2001-2006, by Object Refinery Limited.
0031: *
0032: * Original Author: David Gilbert (for Object Refinery Limited);
0033: * Contributor(s): -;
0034: *
0035: * $Id: SerialDate.java,v 1.8 2006/08/29 13:44:16 mungady Exp $
0036: *
0037: * Changes (from 11-Oct-2001)
0038: * --------------------------
0039: * 11-Oct-2001 : Re-organised the class and moved it to new package
0040: * com.jrefinery.date (DG);
0041: * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate
0042: * class (DG);
0043: * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate
0044: * class is gone (DG); Changed getPreviousDayOfWeek(),
0045: * getFollowingDayOfWeek() and getNearestDayOfWeek() to correct
0046: * bugs (DG);
0047: * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);
0048: * 29-May-2002 : Moved the month constants into a separate interface
0049: * (MonthConstants) (DG);
0050: * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);
0051: * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
0052: * 13-Mar-2003 : Implemented Serializable (DG);
0053: * 29-May-2003 : Fixed bug in addMonths method (DG);
0054: * 04-Sep-2003 : Implemented Comparable. Updated the isInRange javadocs (DG);
0055: * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);
0056: *
0057: */
0058:
0059: package org.jfree.date;
0060:
0061: import java.io.Serializable;
0062: import java.text.DateFormatSymbols;
0063: import java.text.SimpleDateFormat;
0064: import java.util.Calendar;
0065: import java.util.GregorianCalendar;
0066:
0067: /**
0068: * An abstract class that defines our requirements for manipulating dates,
0069: * without tying down a particular implementation.
0070: * <P>
0071: * Requirement 1 : match at least what Excel does for dates;
0072: * Requirement 2 : the date represented by the class is immutable;
0073: * <P>
0074: * Why not just use java.util.Date? We will, when it makes sense. At times,
0075: * java.util.Date can be *too* precise - it represents an instant in time,
0076: * accurate to 1/1000th of a second (with the date itself depending on the
0077: * time-zone). Sometimes we just want to represent a particular day (e.g. 21
0078: * January 2015) without concerning ourselves about the time of day, or the
0079: * time-zone, or anything else. That's what we've defined SerialDate for.
0080: * <P>
0081: * You can call getInstance() to get a concrete subclass of SerialDate,
0082: * without worrying about the exact implementation.
0083: *
0084: * @author David Gilbert
0085: */
0086: public abstract class SerialDate implements Comparable, Serializable,
0087: MonthConstants {
0088:
0089: /** For serialization. */
0090: private static final long serialVersionUID = -293716040467423637L;
0091:
0092: /** Date format symbols. */
0093: public static final DateFormatSymbols DATE_FORMAT_SYMBOLS = new SimpleDateFormat()
0094: .getDateFormatSymbols();
0095:
0096: /** The serial number for 1 January 1900. */
0097: public static final int SERIAL_LOWER_BOUND = 2;
0098:
0099: /** The serial number for 31 December 9999. */
0100: public static final int SERIAL_UPPER_BOUND = 2958465;
0101:
0102: /** The lowest year value supported by this date format. */
0103: public static final int MINIMUM_YEAR_SUPPORTED = 1900;
0104:
0105: /** The highest year value supported by this date format. */
0106: public static final int MAXIMUM_YEAR_SUPPORTED = 9999;
0107:
0108: /** Useful constant for Monday. Equivalent to java.util.Calendar.MONDAY. */
0109: public static final int MONDAY = Calendar.MONDAY;
0110:
0111: /**
0112: * Useful constant for Tuesday. Equivalent to java.util.Calendar.TUESDAY.
0113: */
0114: public static final int TUESDAY = Calendar.TUESDAY;
0115:
0116: /**
0117: * Useful constant for Wednesday. Equivalent to
0118: * java.util.Calendar.WEDNESDAY.
0119: */
0120: public static final int WEDNESDAY = Calendar.WEDNESDAY;
0121:
0122: /**
0123: * Useful constant for Thrusday. Equivalent to java.util.Calendar.THURSDAY.
0124: */
0125: public static final int THURSDAY = Calendar.THURSDAY;
0126:
0127: /** Useful constant for Friday. Equivalent to java.util.Calendar.FRIDAY. */
0128: public static final int FRIDAY = Calendar.FRIDAY;
0129:
0130: /**
0131: * Useful constant for Saturday. Equivalent to java.util.Calendar.SATURDAY.
0132: */
0133: public static final int SATURDAY = Calendar.SATURDAY;
0134:
0135: /** Useful constant for Sunday. Equivalent to java.util.Calendar.SUNDAY. */
0136: public static final int SUNDAY = Calendar.SUNDAY;
0137:
0138: /** The number of days in each month in non leap years. */
0139: static final int[] LAST_DAY_OF_MONTH = { 0, 31, 28, 31, 30, 31, 30,
0140: 31, 31, 30, 31, 30, 31 };
0141:
0142: /** The number of days in a (non-leap) year up to the end of each month. */
0143: static final int[] AGGREGATE_DAYS_TO_END_OF_MONTH = { 0, 31, 59,
0144: 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
0145:
0146: /** The number of days in a year up to the end of the preceding month. */
0147: static final int[] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = { 0,
0148: 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
0149:
0150: /** The number of days in a leap year up to the end of each month. */
0151: static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH = { 0,
0152: 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
0153:
0154: /**
0155: * The number of days in a leap year up to the end of the preceding month.
0156: */
0157: static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = {
0158: 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335,
0159: 366 };
0160:
0161: /** A useful constant for referring to the first week in a month. */
0162: public static final int FIRST_WEEK_IN_MONTH = 1;
0163:
0164: /** A useful constant for referring to the second week in a month. */
0165: public static final int SECOND_WEEK_IN_MONTH = 2;
0166:
0167: /** A useful constant for referring to the third week in a month. */
0168: public static final int THIRD_WEEK_IN_MONTH = 3;
0169:
0170: /** A useful constant for referring to the fourth week in a month. */
0171: public static final int FOURTH_WEEK_IN_MONTH = 4;
0172:
0173: /** A useful constant for referring to the last week in a month. */
0174: public static final int LAST_WEEK_IN_MONTH = 0;
0175:
0176: /** Useful range constant. */
0177: public static final int INCLUDE_NONE = 0;
0178:
0179: /** Useful range constant. */
0180: public static final int INCLUDE_FIRST = 1;
0181:
0182: /** Useful range constant. */
0183: public static final int INCLUDE_SECOND = 2;
0184:
0185: /** Useful range constant. */
0186: public static final int INCLUDE_BOTH = 3;
0187:
0188: /**
0189: * Useful constant for specifying a day of the week relative to a fixed
0190: * date.
0191: */
0192: public static final int PRECEDING = -1;
0193:
0194: /**
0195: * Useful constant for specifying a day of the week relative to a fixed
0196: * date.
0197: */
0198: public static final int NEAREST = 0;
0199:
0200: /**
0201: * Useful constant for specifying a day of the week relative to a fixed
0202: * date.
0203: */
0204: public static final int FOLLOWING = 1;
0205:
0206: /** A description for the date. */
0207: private String description;
0208:
0209: /**
0210: * Default constructor.
0211: */
0212: protected SerialDate() {
0213: }
0214:
0215: /**
0216: * Returns <code>true</code> if the supplied integer code represents a
0217: * valid day-of-the-week, and <code>false</code> otherwise.
0218: *
0219: * @param code the code being checked for validity.
0220: *
0221: * @return <code>true</code> if the supplied integer code represents a
0222: * valid day-of-the-week, and <code>false</code> otherwise.
0223: */
0224: public static boolean isValidWeekdayCode(final int code) {
0225:
0226: switch (code) {
0227: case SUNDAY:
0228: case MONDAY:
0229: case TUESDAY:
0230: case WEDNESDAY:
0231: case THURSDAY:
0232: case FRIDAY:
0233: case SATURDAY:
0234: return true;
0235: default:
0236: return false;
0237: }
0238:
0239: }
0240:
0241: /**
0242: * Converts the supplied string to a day of the week.
0243: *
0244: * @param s a string representing the day of the week.
0245: *
0246: * @return <code>-1</code> if the string is not convertable, the day of
0247: * the week otherwise.
0248: */
0249: public static int stringToWeekdayCode(String s) {
0250:
0251: final String[] shortWeekdayNames = DATE_FORMAT_SYMBOLS
0252: .getShortWeekdays();
0253: final String[] weekDayNames = DATE_FORMAT_SYMBOLS.getWeekdays();
0254:
0255: int result = -1;
0256: s = s.trim();
0257: for (int i = 0; i < weekDayNames.length; i++) {
0258: if (s.equals(shortWeekdayNames[i])) {
0259: result = i;
0260: break;
0261: }
0262: if (s.equals(weekDayNames[i])) {
0263: result = i;
0264: break;
0265: }
0266: }
0267: return result;
0268:
0269: }
0270:
0271: /**
0272: * Returns a string representing the supplied day-of-the-week.
0273: * <P>
0274: * Need to find a better approach.
0275: *
0276: * @param weekday the day of the week.
0277: *
0278: * @return a string representing the supplied day-of-the-week.
0279: */
0280: public static String weekdayCodeToString(final int weekday) {
0281:
0282: final String[] weekdays = DATE_FORMAT_SYMBOLS.getWeekdays();
0283: return weekdays[weekday];
0284:
0285: }
0286:
0287: /**
0288: * Returns an array of month names.
0289: *
0290: * @return an array of month names.
0291: */
0292: public static String[] getMonths() {
0293:
0294: return getMonths(false);
0295:
0296: }
0297:
0298: /**
0299: * Returns an array of month names.
0300: *
0301: * @param shortened a flag indicating that shortened month names should
0302: * be returned.
0303: *
0304: * @return an array of month names.
0305: */
0306: public static String[] getMonths(final boolean shortened) {
0307:
0308: if (shortened) {
0309: return DATE_FORMAT_SYMBOLS.getShortMonths();
0310: } else {
0311: return DATE_FORMAT_SYMBOLS.getMonths();
0312: }
0313:
0314: }
0315:
0316: /**
0317: * Returns true if the supplied integer code represents a valid month.
0318: *
0319: * @param code the code being checked for validity.
0320: *
0321: * @return <code>true</code> if the supplied integer code represents a
0322: * valid month.
0323: */
0324: public static boolean isValidMonthCode(final int code) {
0325:
0326: switch (code) {
0327: case JANUARY:
0328: case FEBRUARY:
0329: case MARCH:
0330: case APRIL:
0331: case MAY:
0332: case JUNE:
0333: case JULY:
0334: case AUGUST:
0335: case SEPTEMBER:
0336: case OCTOBER:
0337: case NOVEMBER:
0338: case DECEMBER:
0339: return true;
0340: default:
0341: return false;
0342: }
0343:
0344: }
0345:
0346: /**
0347: * Returns the quarter for the specified month.
0348: *
0349: * @param code the month code (1-12).
0350: *
0351: * @return the quarter that the month belongs to.
0352: */
0353: public static int monthCodeToQuarter(final int code) {
0354:
0355: switch (code) {
0356: case JANUARY:
0357: case FEBRUARY:
0358: case MARCH:
0359: return 1;
0360: case APRIL:
0361: case MAY:
0362: case JUNE:
0363: return 2;
0364: case JULY:
0365: case AUGUST:
0366: case SEPTEMBER:
0367: return 3;
0368: case OCTOBER:
0369: case NOVEMBER:
0370: case DECEMBER:
0371: return 4;
0372: default:
0373: throw new IllegalArgumentException(
0374: "SerialDate.monthCodeToQuarter: invalid month code.");
0375: }
0376:
0377: }
0378:
0379: /**
0380: * Returns a string representing the supplied month.
0381: * <P>
0382: * The string returned is the long form of the month name taken from the
0383: * default locale.
0384: *
0385: * @param month the month.
0386: *
0387: * @return a string representing the supplied month.
0388: */
0389: public static String monthCodeToString(final int month) {
0390:
0391: return monthCodeToString(month, false);
0392:
0393: }
0394:
0395: /**
0396: * Returns a string representing the supplied month.
0397: * <P>
0398: * The string returned is the long or short form of the month name taken
0399: * from the default locale.
0400: *
0401: * @param month the month.
0402: * @param shortened if <code>true</code> return the abbreviation of the
0403: * month.
0404: *
0405: * @return a string representing the supplied month.
0406: */
0407: public static String monthCodeToString(final int month,
0408: final boolean shortened) {
0409:
0410: // check arguments...
0411: if (!isValidMonthCode(month)) {
0412: throw new IllegalArgumentException(
0413: "SerialDate.monthCodeToString: month outside valid range.");
0414: }
0415:
0416: final String[] months;
0417:
0418: if (shortened) {
0419: months = DATE_FORMAT_SYMBOLS.getShortMonths();
0420: } else {
0421: months = DATE_FORMAT_SYMBOLS.getMonths();
0422: }
0423:
0424: return months[month - 1];
0425:
0426: }
0427:
0428: /**
0429: * Converts a string to a month code.
0430: * <P>
0431: * This method will return one of the constants JANUARY, FEBRUARY, ...,
0432: * DECEMBER that corresponds to the string. If the string is not
0433: * recognised, this method returns -1.
0434: *
0435: * @param s the string to parse.
0436: *
0437: * @return <code>-1</code> if the string is not parseable, the month of the
0438: * year otherwise.
0439: */
0440: public static int stringToMonthCode(String s) {
0441:
0442: final String[] shortMonthNames = DATE_FORMAT_SYMBOLS
0443: .getShortMonths();
0444: final String[] monthNames = DATE_FORMAT_SYMBOLS.getMonths();
0445:
0446: int result = -1;
0447: s = s.trim();
0448:
0449: // first try parsing the string as an integer (1-12)...
0450: try {
0451: result = Integer.parseInt(s);
0452: } catch (NumberFormatException e) {
0453: // suppress
0454: }
0455:
0456: // now search through the month names...
0457: if ((result < 1) || (result > 12)) {
0458: for (int i = 0; i < monthNames.length; i++) {
0459: if (s.equals(shortMonthNames[i])) {
0460: result = i + 1;
0461: break;
0462: }
0463: if (s.equals(monthNames[i])) {
0464: result = i + 1;
0465: break;
0466: }
0467: }
0468: }
0469:
0470: return result;
0471:
0472: }
0473:
0474: /**
0475: * Returns true if the supplied integer code represents a valid
0476: * week-in-the-month, and false otherwise.
0477: *
0478: * @param code the code being checked for validity.
0479: * @return <code>true</code> if the supplied integer code represents a
0480: * valid week-in-the-month.
0481: */
0482: public static boolean isValidWeekInMonthCode(final int code) {
0483:
0484: switch (code) {
0485: case FIRST_WEEK_IN_MONTH:
0486: case SECOND_WEEK_IN_MONTH:
0487: case THIRD_WEEK_IN_MONTH:
0488: case FOURTH_WEEK_IN_MONTH:
0489: case LAST_WEEK_IN_MONTH:
0490: return true;
0491: default:
0492: return false;
0493: }
0494:
0495: }
0496:
0497: /**
0498: * Determines whether or not the specified year is a leap year.
0499: *
0500: * @param yyyy the year (in the range 1900 to 9999).
0501: *
0502: * @return <code>true</code> if the specified year is a leap year.
0503: */
0504: public static boolean isLeapYear(final int yyyy) {
0505:
0506: if ((yyyy % 4) != 0) {
0507: return false;
0508: } else if ((yyyy % 400) == 0) {
0509: return true;
0510: } else if ((yyyy % 100) == 0) {
0511: return false;
0512: } else {
0513: return true;
0514: }
0515:
0516: }
0517:
0518: /**
0519: * Returns the number of leap years from 1900 to the specified year
0520: * INCLUSIVE.
0521: * <P>
0522: * Note that 1900 is not a leap year.
0523: *
0524: * @param yyyy the year (in the range 1900 to 9999).
0525: *
0526: * @return the number of leap years from 1900 to the specified year.
0527: */
0528: public static int leapYearCount(final int yyyy) {
0529:
0530: final int leap4 = (yyyy - 1896) / 4;
0531: final int leap100 = (yyyy - 1800) / 100;
0532: final int leap400 = (yyyy - 1600) / 400;
0533: return leap4 - leap100 + leap400;
0534:
0535: }
0536:
0537: /**
0538: * Returns the number of the last day of the month, taking into account
0539: * leap years.
0540: *
0541: * @param month the month.
0542: * @param yyyy the year (in the range 1900 to 9999).
0543: *
0544: * @return the number of the last day of the month.
0545: */
0546: public static int lastDayOfMonth(final int month, final int yyyy) {
0547:
0548: final int result = LAST_DAY_OF_MONTH[month];
0549: if (month != FEBRUARY) {
0550: return result;
0551: } else if (isLeapYear(yyyy)) {
0552: return result + 1;
0553: } else {
0554: return result;
0555: }
0556:
0557: }
0558:
0559: /**
0560: * Creates a new date by adding the specified number of days to the base
0561: * date.
0562: *
0563: * @param days the number of days to add (can be negative).
0564: * @param base the base date.
0565: *
0566: * @return a new date.
0567: */
0568: public static SerialDate addDays(final int days,
0569: final SerialDate base) {
0570:
0571: final int serialDayNumber = base.toSerial() + days;
0572: return SerialDate.createInstance(serialDayNumber);
0573:
0574: }
0575:
0576: /**
0577: * Creates a new date by adding the specified number of months to the base
0578: * date.
0579: * <P>
0580: * If the base date is close to the end of the month, the day on the result
0581: * may be adjusted slightly: 31 May + 1 month = 30 June.
0582: *
0583: * @param months the number of months to add (can be negative).
0584: * @param base the base date.
0585: *
0586: * @return a new date.
0587: */
0588: public static SerialDate addMonths(final int months,
0589: final SerialDate base) {
0590:
0591: final int yy = (12 * base.getYYYY() + base.getMonth() + months - 1) / 12;
0592: final int mm = (12 * base.getYYYY() + base.getMonth() + months - 1) % 12 + 1;
0593: final int dd = Math.min(base.getDayOfMonth(), SerialDate
0594: .lastDayOfMonth(mm, yy));
0595: return SerialDate.createInstance(dd, mm, yy);
0596:
0597: }
0598:
0599: /**
0600: * Creates a new date by adding the specified number of years to the base
0601: * date.
0602: *
0603: * @param years the number of years to add (can be negative).
0604: * @param base the base date.
0605: *
0606: * @return A new date.
0607: */
0608: public static SerialDate addYears(final int years,
0609: final SerialDate base) {
0610:
0611: final int baseY = base.getYYYY();
0612: final int baseM = base.getMonth();
0613: final int baseD = base.getDayOfMonth();
0614:
0615: final int targetY = baseY + years;
0616: final int targetD = Math.min(baseD, SerialDate.lastDayOfMonth(
0617: baseM, targetY));
0618:
0619: return SerialDate.createInstance(targetD, baseM, targetY);
0620:
0621: }
0622:
0623: /**
0624: * Returns the latest date that falls on the specified day-of-the-week and
0625: * is BEFORE the base date.
0626: *
0627: * @param targetWeekday a code for the target day-of-the-week.
0628: * @param base the base date.
0629: *
0630: * @return the latest date that falls on the specified day-of-the-week and
0631: * is BEFORE the base date.
0632: */
0633: public static SerialDate getPreviousDayOfWeek(
0634: final int targetWeekday, final SerialDate base) {
0635:
0636: // check arguments...
0637: if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
0638: throw new IllegalArgumentException(
0639: "Invalid day-of-the-week code.");
0640: }
0641:
0642: // find the date...
0643: final int adjust;
0644: final int baseDOW = base.getDayOfWeek();
0645: if (baseDOW > targetWeekday) {
0646: adjust = Math.min(0, targetWeekday - baseDOW);
0647: } else {
0648: adjust = -7 + Math.max(0, targetWeekday - baseDOW);
0649: }
0650:
0651: return SerialDate.addDays(adjust, base);
0652:
0653: }
0654:
0655: /**
0656: * Returns the earliest date that falls on the specified day-of-the-week
0657: * and is AFTER the base date.
0658: *
0659: * @param targetWeekday a code for the target day-of-the-week.
0660: * @param base the base date.
0661: *
0662: * @return the earliest date that falls on the specified day-of-the-week
0663: * and is AFTER the base date.
0664: */
0665: public static SerialDate getFollowingDayOfWeek(
0666: final int targetWeekday, final SerialDate base) {
0667:
0668: // check arguments...
0669: if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
0670: throw new IllegalArgumentException(
0671: "Invalid day-of-the-week code.");
0672: }
0673:
0674: // find the date...
0675: final int adjust;
0676: final int baseDOW = base.getDayOfWeek();
0677: if (baseDOW > targetWeekday) {
0678: adjust = 7 + Math.min(0, targetWeekday - baseDOW);
0679: } else {
0680: adjust = Math.max(0, targetWeekday - baseDOW);
0681: }
0682:
0683: return SerialDate.addDays(adjust, base);
0684: }
0685:
0686: /**
0687: * Returns the date that falls on the specified day-of-the-week and is
0688: * CLOSEST to the base date.
0689: *
0690: * @param targetDOW a code for the target day-of-the-week.
0691: * @param base the base date.
0692: *
0693: * @return the date that falls on the specified day-of-the-week and is
0694: * CLOSEST to the base date.
0695: */
0696: public static SerialDate getNearestDayOfWeek(final int targetDOW,
0697: final SerialDate base) {
0698:
0699: // check arguments...
0700: if (!SerialDate.isValidWeekdayCode(targetDOW)) {
0701: throw new IllegalArgumentException(
0702: "Invalid day-of-the-week code.");
0703: }
0704:
0705: // find the date...
0706: final int baseDOW = base.getDayOfWeek();
0707: int adjust = -Math.abs(targetDOW - baseDOW);
0708: if (adjust >= 4) {
0709: adjust = 7 - adjust;
0710: }
0711: if (adjust <= -4) {
0712: adjust = 7 + adjust;
0713: }
0714: return SerialDate.addDays(adjust, base);
0715:
0716: }
0717:
0718: /**
0719: * Rolls the date forward to the last day of the month.
0720: *
0721: * @param base the base date.
0722: *
0723: * @return a new serial date.
0724: */
0725: public SerialDate getEndOfCurrentMonth(final SerialDate base) {
0726: final int last = SerialDate.lastDayOfMonth(base.getMonth(),
0727: base.getYYYY());
0728: return SerialDate.createInstance(last, base.getMonth(), base
0729: .getYYYY());
0730: }
0731:
0732: /**
0733: * Returns a string corresponding to the week-in-the-month code.
0734: * <P>
0735: * Need to find a better approach.
0736: *
0737: * @param count an integer code representing the week-in-the-month.
0738: *
0739: * @return a string corresponding to the week-in-the-month code.
0740: */
0741: public static String weekInMonthToString(final int count) {
0742:
0743: switch (count) {
0744: case SerialDate.FIRST_WEEK_IN_MONTH:
0745: return "First";
0746: case SerialDate.SECOND_WEEK_IN_MONTH:
0747: return "Second";
0748: case SerialDate.THIRD_WEEK_IN_MONTH:
0749: return "Third";
0750: case SerialDate.FOURTH_WEEK_IN_MONTH:
0751: return "Fourth";
0752: case SerialDate.LAST_WEEK_IN_MONTH:
0753: return "Last";
0754: default:
0755: return "SerialDate.weekInMonthToString(): invalid code.";
0756: }
0757:
0758: }
0759:
0760: /**
0761: * Returns a string representing the supplied 'relative'.
0762: * <P>
0763: * Need to find a better approach.
0764: *
0765: * @param relative a constant representing the 'relative'.
0766: *
0767: * @return a string representing the supplied 'relative'.
0768: */
0769: public static String relativeToString(final int relative) {
0770:
0771: switch (relative) {
0772: case SerialDate.PRECEDING:
0773: return "Preceding";
0774: case SerialDate.NEAREST:
0775: return "Nearest";
0776: case SerialDate.FOLLOWING:
0777: return "Following";
0778: default:
0779: return "ERROR : Relative To String";
0780: }
0781:
0782: }
0783:
0784: /**
0785: * Factory method that returns an instance of some concrete subclass of
0786: * {@link SerialDate}.
0787: *
0788: * @param day the day (1-31).
0789: * @param month the month (1-12).
0790: * @param yyyy the year (in the range 1900 to 9999).
0791: *
0792: * @return An instance of {@link SerialDate}.
0793: */
0794: public static SerialDate createInstance(final int day,
0795: final int month, final int yyyy) {
0796: return new SpreadsheetDate(day, month, yyyy);
0797: }
0798:
0799: /**
0800: * Factory method that returns an instance of some concrete subclass of
0801: * {@link SerialDate}.
0802: *
0803: * @param serial the serial number for the day (1 January 1900 = 2).
0804: *
0805: * @return a instance of SerialDate.
0806: */
0807: public static SerialDate createInstance(final int serial) {
0808: return new SpreadsheetDate(serial);
0809: }
0810:
0811: /**
0812: * Factory method that returns an instance of a subclass of SerialDate.
0813: *
0814: * @param date A Java date object.
0815: *
0816: * @return a instance of SerialDate.
0817: */
0818: public static SerialDate createInstance(final java.util.Date date) {
0819:
0820: final GregorianCalendar calendar = new GregorianCalendar();
0821: calendar.setTime(date);
0822: return new SpreadsheetDate(calendar.get(Calendar.DATE),
0823: calendar.get(Calendar.MONTH) + 1, calendar
0824: .get(Calendar.YEAR));
0825:
0826: }
0827:
0828: /**
0829: * Returns the serial number for the date, where 1 January 1900 = 2 (this
0830: * corresponds, almost, to the numbering system used in Microsoft Excel for
0831: * Windows and Lotus 1-2-3).
0832: *
0833: * @return the serial number for the date.
0834: */
0835: public abstract int toSerial();
0836:
0837: /**
0838: * Returns a java.util.Date. Since java.util.Date has more precision than
0839: * SerialDate, we need to define a convention for the 'time of day'.
0840: *
0841: * @return this as <code>java.util.Date</code>.
0842: */
0843: public abstract java.util.Date toDate();
0844:
0845: /**
0846: * Returns the description that is attached to the date. It is not
0847: * required that a date have a description, but for some applications it
0848: * is useful.
0849: *
0850: * @return The description (possibly <code>null</code>).
0851: */
0852: public String getDescription() {
0853: return this .description;
0854: }
0855:
0856: /**
0857: * Sets the description for the date.
0858: *
0859: * @param description the description for this date (<code>null</code>
0860: * permitted).
0861: */
0862: public void setDescription(final String description) {
0863: this .description = description;
0864: }
0865:
0866: /**
0867: * Converts the date to a string.
0868: *
0869: * @return a string representation of the date.
0870: */
0871: public String toString() {
0872: return getDayOfMonth() + "-"
0873: + SerialDate.monthCodeToString(getMonth()) + "-"
0874: + getYYYY();
0875: }
0876:
0877: /**
0878: * Returns the year (assume a valid range of 1900 to 9999).
0879: *
0880: * @return the year.
0881: */
0882: public abstract int getYYYY();
0883:
0884: /**
0885: * Returns the month (January = 1, February = 2, March = 3).
0886: *
0887: * @return the month of the year.
0888: */
0889: public abstract int getMonth();
0890:
0891: /**
0892: * Returns the day of the month.
0893: *
0894: * @return the day of the month.
0895: */
0896: public abstract int getDayOfMonth();
0897:
0898: /**
0899: * Returns the day of the week.
0900: *
0901: * @return the day of the week.
0902: */
0903: public abstract int getDayOfWeek();
0904:
0905: /**
0906: * Returns the difference (in days) between this date and the specified
0907: * 'other' date.
0908: * <P>
0909: * The result is positive if this date is after the 'other' date and
0910: * negative if it is before the 'other' date.
0911: *
0912: * @param other the date being compared to.
0913: *
0914: * @return the difference between this and the other date.
0915: */
0916: public abstract int compare(SerialDate other);
0917:
0918: /**
0919: * Returns true if this SerialDate represents the same date as the
0920: * specified SerialDate.
0921: *
0922: * @param other the date being compared to.
0923: *
0924: * @return <code>true</code> if this SerialDate represents the same date as
0925: * the specified SerialDate.
0926: */
0927: public abstract boolean isOn(SerialDate other);
0928:
0929: /**
0930: * Returns true if this SerialDate represents an earlier date compared to
0931: * the specified SerialDate.
0932: *
0933: * @param other The date being compared to.
0934: *
0935: * @return <code>true</code> if this SerialDate represents an earlier date
0936: * compared to the specified SerialDate.
0937: */
0938: public abstract boolean isBefore(SerialDate other);
0939:
0940: /**
0941: * Returns true if this SerialDate represents the same date as the
0942: * specified SerialDate.
0943: *
0944: * @param other the date being compared to.
0945: *
0946: * @return <code>true<code> if this SerialDate represents the same date
0947: * as the specified SerialDate.
0948: */
0949: public abstract boolean isOnOrBefore(SerialDate other);
0950:
0951: /**
0952: * Returns true if this SerialDate represents the same date as the
0953: * specified SerialDate.
0954: *
0955: * @param other the date being compared to.
0956: *
0957: * @return <code>true</code> if this SerialDate represents the same date
0958: * as the specified SerialDate.
0959: */
0960: public abstract boolean isAfter(SerialDate other);
0961:
0962: /**
0963: * Returns true if this SerialDate represents the same date as the
0964: * specified SerialDate.
0965: *
0966: * @param other the date being compared to.
0967: *
0968: * @return <code>true</code> if this SerialDate represents the same date
0969: * as the specified SerialDate.
0970: */
0971: public abstract boolean isOnOrAfter(SerialDate other);
0972:
0973: /**
0974: * Returns <code>true</code> if this {@link SerialDate} is within the
0975: * specified range (INCLUSIVE). The date order of d1 and d2 is not
0976: * important.
0977: *
0978: * @param d1 a boundary date for the range.
0979: * @param d2 the other boundary date for the range.
0980: *
0981: * @return A boolean.
0982: */
0983: public abstract boolean isInRange(SerialDate d1, SerialDate d2);
0984:
0985: /**
0986: * Returns <code>true</code> if this {@link SerialDate} is within the
0987: * specified range (caller specifies whether or not the end-points are
0988: * included). The date order of d1 and d2 is not important.
0989: *
0990: * @param d1 a boundary date for the range.
0991: * @param d2 the other boundary date for the range.
0992: * @param include a code that controls whether or not the start and end
0993: * dates are included in the range.
0994: *
0995: * @return A boolean.
0996: */
0997: public abstract boolean isInRange(SerialDate d1, SerialDate d2,
0998: int include);
0999:
1000: /**
1001: * Returns the latest date that falls on the specified day-of-the-week and
1002: * is BEFORE this date.
1003: *
1004: * @param targetDOW a code for the target day-of-the-week.
1005: *
1006: * @return the latest date that falls on the specified day-of-the-week and
1007: * is BEFORE this date.
1008: */
1009: public SerialDate getPreviousDayOfWeek(final int targetDOW) {
1010: return getPreviousDayOfWeek(targetDOW, this );
1011: }
1012:
1013: /**
1014: * Returns the earliest date that falls on the specified day-of-the-week
1015: * and is AFTER this date.
1016: *
1017: * @param targetDOW a code for the target day-of-the-week.
1018: *
1019: * @return the earliest date that falls on the specified day-of-the-week
1020: * and is AFTER this date.
1021: */
1022: public SerialDate getFollowingDayOfWeek(final int targetDOW) {
1023: return getFollowingDayOfWeek(targetDOW, this );
1024: }
1025:
1026: /**
1027: * Returns the nearest date that falls on the specified day-of-the-week.
1028: *
1029: * @param targetDOW a code for the target day-of-the-week.
1030: *
1031: * @return the nearest date that falls on the specified day-of-the-week.
1032: */
1033: public SerialDate getNearestDayOfWeek(final int targetDOW) {
1034: return getNearestDayOfWeek(targetDOW, this);
1035: }
1036:
1037: }
|