001: /* ========================================================================
002: * JCommon : a free general purpose class library for the Java(tm) platform
003: * ========================================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jcommon/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * ------------------------
028: * SerialDateUtilities.java
029: * ------------------------
030: * (C) Copyright 2001-2003, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: SerialDateUtilities.java,v 1.6 2005/11/16 15:58:40 taqua Exp $
036: *
037: * Changes (from 26-Oct-2001)
038: * --------------------------
039: * 26-Oct-2001 : Changed package to com.jrefinery.date.*;
040: * 08-Dec-2001 : Dropped isLeapYear() method (DG);
041: * 04-Mar-2002 : Renamed SerialDates.java --> SerialDateUtilities.java (DG);
042: * 25-Jun-2002 : Fixed a bug in the dayCountActual() method (DG);
043: * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
044: *
045: */
046:
047: package org.jfree.date;
048:
049: import java.text.DateFormatSymbols;
050: import java.util.Calendar;
051:
052: /**
053: * A utility class that provides a number of useful methods (some static).
054: * Many of these are used in the implementation of the day-count convention
055: * classes. I recognise some limitations in this implementation:
056: * <p>
057: * [1] some of the methods assume that the default Calendar is a
058: * GregorianCalendar (used mostly to determine leap years) - so the code
059: * won’t work if some other Calendar is the default. I'm not sure how
060: * to handle this properly?
061: * <p>
062: * [2] a whole bunch of static methods isn't very object-oriented - but I couldn't think of a good
063: * way to extend the Date and Calendar classes to add the functions I required,
064: * so static methods are doing the job for now.
065: *
066: * @author David Gilbert
067: */
068: public class SerialDateUtilities {
069:
070: /** The default date format symbols. */
071: private DateFormatSymbols dateFormatSymbols;
072:
073: /** Strings representing the weekdays. */
074: private String[] weekdays;
075:
076: /** Strings representing the months. */
077: private String[] months;
078:
079: /**
080: * Creates a new utility class for the default locale.
081: */
082: public SerialDateUtilities() {
083: this .dateFormatSymbols = new DateFormatSymbols();
084: this .weekdays = this .dateFormatSymbols.getWeekdays();
085: this .months = this .dateFormatSymbols.getMonths();
086: }
087:
088: /**
089: * Returns an array of strings representing the days-of-the-week.
090: *
091: * @return an array of strings representing the days-of-the-week.
092: */
093: public String[] getWeekdays() {
094: return this .weekdays;
095: }
096:
097: /**
098: * Returns an array of strings representing the months.
099: *
100: * @return an array of strings representing the months.
101: */
102: public String[] getMonths() {
103: return this .months;
104: }
105:
106: /**
107: * Converts the specified string to a weekday, using the default locale.
108: *
109: * @param s a string representing the day-of-the-week.
110: *
111: * @return an integer representing the day-of-the-week.
112: */
113: public int stringToWeekday(final String s) {
114:
115: if (s.equals(this .weekdays[Calendar.SATURDAY])) {
116: return SerialDate.SATURDAY;
117: } else if (s.equals(this .weekdays[Calendar.SUNDAY])) {
118: return SerialDate.SUNDAY;
119: } else if (s.equals(this .weekdays[Calendar.MONDAY])) {
120: return SerialDate.MONDAY;
121: } else if (s.equals(this .weekdays[Calendar.TUESDAY])) {
122: return SerialDate.TUESDAY;
123: } else if (s.equals(this .weekdays[Calendar.WEDNESDAY])) {
124: return SerialDate.WEDNESDAY;
125: } else if (s.equals(this .weekdays[Calendar.THURSDAY])) {
126: return SerialDate.THURSDAY;
127: } else {
128: return SerialDate.FRIDAY;
129: }
130:
131: }
132:
133: /**
134: * Returns the actual number of days between two dates.
135: *
136: * @param start the start date.
137: * @param end the end date.
138: *
139: * @return the number of days between the start date and the end date.
140: */
141: public static int dayCountActual(final SerialDate start,
142: final SerialDate end) {
143: return end.compare(start);
144: }
145:
146: /**
147: * Returns the number of days between the specified start and end dates,
148: * assuming that there are thirty days in every month (that is,
149: * corresponding to the 30/360 day-count convention).
150: * <P>
151: * The method handles cases where the start date is before the end date (by
152: * switching the dates and returning a negative result).
153: *
154: * @param start the start date.
155: * @param end the end date.
156: *
157: * @return the number of days between the two dates, assuming the 30/360 day-count convention.
158: */
159: public static int dayCount30(final SerialDate start,
160: final SerialDate end) {
161: final int d1;
162: final int m1;
163: final int y1;
164: final int d2;
165: final int m2;
166: final int y2;
167: if (start.isBefore(end)) { // check the order of the dates
168: d1 = start.getDayOfMonth();
169: m1 = start.getMonth();
170: y1 = start.getYYYY();
171: d2 = end.getDayOfMonth();
172: m2 = end.getMonth();
173: y2 = end.getYYYY();
174: return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
175: } else {
176: return -dayCount30(end, start);
177: }
178: }
179:
180: /**
181: * Returns the number of days between the specified start and end dates,
182: * assuming that there are thirty days in every month, and applying the
183: * ISDA adjustments (that is, corresponding to the 30/360 (ISDA) day-count
184: * convention).
185: * <P>
186: * The method handles cases where the start date is before the end date (by
187: * switching the dates around and returning a negative result).
188: *
189: * @param start the start date.
190: * @param end the end date.
191: *
192: * @return The number of days between the two dates, assuming the 30/360
193: * (ISDA) day-count convention.
194: */
195: public static int dayCount30ISDA(final SerialDate start,
196: final SerialDate end) {
197: int d1;
198: final int m1;
199: final int y1;
200: int d2;
201: final int m2;
202: final int y2;
203: if (start.isBefore(end)) {
204: d1 = start.getDayOfMonth();
205: m1 = start.getMonth();
206: y1 = start.getYYYY();
207: if (d1 == 31) { // first ISDA adjustment
208: d1 = 30;
209: }
210: d2 = end.getDayOfMonth();
211: m2 = end.getMonth();
212: y2 = end.getYYYY();
213: if ((d2 == 31) && (d1 == 30)) { // second ISDA adjustment
214: d2 = 30;
215: }
216: return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
217: } else if (start.isAfter(end)) {
218: return -dayCount30ISDA(end, start);
219: } else {
220: return 0;
221: }
222: }
223:
224: /**
225: * Returns the number of days between the specified start and end dates,
226: * assuming that there are thirty days in every month, and applying the PSA
227: * adjustments (that is, corresponding to the 30/360 (PSA) day-count convention).
228: * The method handles cases where the start date is before the end date (by
229: * switching the dates around and returning a negative result).
230: *
231: * @param start the start date.
232: * @param end the end date.
233: *
234: * @return The number of days between the two dates, assuming the 30/360
235: * (PSA) day-count convention.
236: */
237: public static int dayCount30PSA(final SerialDate start,
238: final SerialDate end) {
239: int d1;
240: final int m1;
241: final int y1;
242: int d2;
243: final int m2;
244: final int y2;
245:
246: if (start.isOnOrBefore(end)) { // check the order of the dates
247: d1 = start.getDayOfMonth();
248: m1 = start.getMonth();
249: y1 = start.getYYYY();
250:
251: if (SerialDateUtilities.isLastDayOfFebruary(start)) {
252: d1 = 30;
253: }
254: if ((d1 == 31)
255: || SerialDateUtilities.isLastDayOfFebruary(start)) {
256: // first PSA adjustment
257: d1 = 30;
258: }
259: d2 = end.getDayOfMonth();
260: m2 = end.getMonth();
261: y2 = end.getYYYY();
262: if ((d2 == 31) && (d1 == 30)) { // second PSA adjustment
263: d2 = 30;
264: }
265: return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
266: } else {
267: return -dayCount30PSA(end, start);
268: }
269: }
270:
271: /**
272: * Returns the number of days between the specified start and end dates,
273: * assuming that there are thirty days in every month, and applying the
274: * European adjustment (that is, corresponding to the 30E/360 day-count
275: * convention).
276: * <P>
277: * The method handles cases where the start date is before the end date (by
278: * switching the dates around and returning a negative result).
279: *
280: * @param start the start date.
281: * @param end the end date.
282: *
283: * @return the number of days between the two dates, assuming the 30E/360
284: * day-count convention.
285: */
286: public static int dayCount30E(final SerialDate start,
287: final SerialDate end) {
288: int d1;
289: final int m1;
290: final int y1;
291: int d2;
292: final int m2;
293: final int y2;
294: if (start.isBefore(end)) {
295: d1 = start.getDayOfMonth();
296: m1 = start.getMonth();
297: y1 = start.getYYYY();
298: if (d1 == 31) { // first European adjustment
299: d1 = 30;
300: }
301: d2 = end.getDayOfMonth();
302: m2 = end.getMonth();
303: y2 = end.getYYYY();
304: if (d2 == 31) { // first European adjustment
305: d2 = 30;
306: }
307: return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
308: } else if (start.isAfter(end)) {
309: return -dayCount30E(end, start);
310: } else {
311: return 0;
312: }
313: }
314:
315: /**
316: * Returns true if the specified date is the last day in February (that is, the
317: * 28th in non-leap years, and the 29th in leap years).
318: *
319: * @param d the date to be tested.
320: *
321: * @return a boolean that indicates whether or not the specified date is
322: * the last day of February.
323: */
324: public static boolean isLastDayOfFebruary(final SerialDate d) {
325:
326: final int dom;
327: if (d.getMonth() == MonthConstants.FEBRUARY) {
328: dom = d.getDayOfMonth();
329: if (SerialDate.isLeapYear(d.getYYYY())) {
330: return (dom == 29);
331: } else {
332: return (dom == 28);
333: }
334: } else { // not even February
335: return false;
336: }
337:
338: }
339:
340: /**
341: * Returns the number of times that February 29 falls within the specified
342: * date range. The result needs to correspond to the ACT/365 (Japanese)
343: * day-count convention. The difficult cases are where the start or the
344: * end date is Feb 29 (include or not?). Need to find out how JGBs do this
345: * (since this is where the ACT/365 (Japanese) convention comes from ...
346: *
347: * @param start the start date.
348: * @param end the end date.
349: *
350: * @return the number of times that February 29 occurs within the date
351: * range.
352: */
353: public static int countFeb29s(final SerialDate start,
354: final SerialDate end) {
355: int count = 0;
356: SerialDate feb29;
357: final int y1;
358: final int y2;
359: int year;
360:
361: // check the order of the dates
362: if (start.isBefore(end)) {
363:
364: y1 = start.getYYYY();
365: y2 = end.getYYYY();
366: for (year = y1; year == y2; year++) {
367: if (SerialDate.isLeapYear(year)) {
368: feb29 = SerialDate.createInstance(29,
369: MonthConstants.FEBRUARY, year);
370: if (feb29.isInRange(start, end,
371: SerialDate.INCLUDE_SECOND)) {
372: count++;
373: }
374: }
375: }
376: return count;
377: } else {
378: return countFeb29s(end, start);
379: }
380: }
381:
382: }
|