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
047package org.jfree.date;
048
049import java.text.DateFormatSymbols;
050import 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&rsquo;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 */
068public 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        }
118        else if (s.equals(this.weekdays[Calendar.SUNDAY])) {
119            return SerialDate.SUNDAY;
120        }
121        else if (s.equals(this.weekdays[Calendar.MONDAY])) {
122            return SerialDate.MONDAY;
123        }
124        else if (s.equals(this.weekdays[Calendar.TUESDAY])) {
125            return SerialDate.TUESDAY;
126        }
127        else if (s.equals(this.weekdays[Calendar.WEDNESDAY])) {
128            return SerialDate.WEDNESDAY;
129        }
130        else if (s.equals(this.weekdays[Calendar.THURSDAY])) {
131            return SerialDate.THURSDAY;
132        }
133        else {
134            return SerialDate.FRIDAY;
135        }
136
137    }
138
139    /**
140     * Returns the actual number of days between two dates.
141     *
142     * @param start  the start date.
143     * @param end  the end date.
144     *
145     * @return the number of days between the start date and the end date.
146     */
147    public static int dayCountActual(final SerialDate start, final SerialDate end) {
148        return end.compare(start);
149    }
150
151    /**
152     * Returns the number of days between the specified start and end dates,
153     * assuming that there are thirty days in every month (that is,
154     * corresponding to the 30/360 day-count convention).
155     * <P>
156     * The method handles cases where the start date is before the end date (by
157     * switching the dates and returning a negative result).
158     *
159     * @param start  the start date.
160     * @param end  the end date.
161     *
162     * @return the number of days between the two dates, assuming the 30/360 day-count convention.
163     */
164    public static int dayCount30(final SerialDate start, final SerialDate end) {
165        final int d1;
166        final int m1;
167        final int y1;
168        final int d2;
169        final int m2;
170        final int y2;
171        if (start.isBefore(end)) {  // check the order of the dates
172            d1 = start.getDayOfMonth();
173            m1 = start.getMonth();
174            y1 = start.getYYYY();
175            d2 = end.getDayOfMonth();
176            m2 = end.getMonth();
177            y2 = end.getYYYY();
178            return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
179        }
180        else {
181            return -dayCount30(end, start);
182        }
183    }
184
185    /**
186     * Returns the number of days between the specified start and end dates,
187     * assuming that there are thirty days in every month, and applying the
188     * ISDA adjustments (that is, corresponding to the 30/360 (ISDA) day-count
189     * convention).
190     * <P>
191     * The method handles cases where the start date is before the end date (by
192     * switching the dates around and returning a negative result).
193     *
194     * @param start  the start date.
195     * @param end  the end date.
196     *
197     * @return The number of days between the two dates, assuming the 30/360
198     *      (ISDA) day-count convention.
199     */
200    public static int dayCount30ISDA(final SerialDate start, final SerialDate end) {
201        int d1;
202        final int m1;
203        final int y1;
204        int d2;
205        final int m2;
206        final int y2;
207        if (start.isBefore(end)) {
208            d1 = start.getDayOfMonth();
209            m1 = start.getMonth();
210            y1 = start.getYYYY();
211            if (d1 == 31) {  // first ISDA adjustment
212                d1 = 30;
213            }
214            d2 = end.getDayOfMonth();
215            m2 = end.getMonth();
216            y2 = end.getYYYY();
217            if ((d2 == 31) && (d1 == 30)) {  // second ISDA adjustment
218                d2 = 30;
219            }
220            return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
221        }
222        else if (start.isAfter(end)) {
223            return -dayCount30ISDA(end, start);
224        }
225        else {
226            return 0;
227        }
228    }
229
230    /**
231     * Returns the number of days between the specified start and end dates,
232     * assuming that there are thirty days in every month, and applying the PSA
233     * adjustments (that is, corresponding to the 30/360 (PSA) day-count convention).
234     * The method handles cases where the start date is before the end date (by
235     * switching the dates around and returning a negative result).
236     *
237     * @param start  the start date.
238     * @param end  the end date.
239     *
240     * @return The number of days between the two dates, assuming the 30/360
241     *      (PSA) day-count convention.
242     */
243    public static int dayCount30PSA(final SerialDate start, final SerialDate end) {
244        int d1;
245        final int m1;
246        final int y1;
247        int d2;
248        final int m2;
249        final int y2;
250
251        if (start.isOnOrBefore(end)) { // check the order of the dates
252            d1 = start.getDayOfMonth();
253            m1 = start.getMonth();
254            y1 = start.getYYYY();
255
256            if (SerialDateUtilities.isLastDayOfFebruary(start)) {
257                d1 = 30;
258            }
259            if ((d1 == 31) || SerialDateUtilities.isLastDayOfFebruary(start)) {
260                // first PSA adjustment
261                d1 = 30;
262            }
263            d2 = end.getDayOfMonth();
264            m2 = end.getMonth();
265            y2 = end.getYYYY();
266            if ((d2 == 31) && (d1 == 30)) {  // second PSA adjustment
267                d2 = 30;
268            }
269            return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
270        }
271        else {
272            return -dayCount30PSA(end, start);
273        }
274    }
275
276    /**
277     * Returns the number of days between the specified start and end dates,
278     * assuming that there are thirty days in every month, and applying the
279     * European adjustment (that is, corresponding to the 30E/360 day-count
280     * convention).
281     * <P>
282     * The method handles cases where the start date is before the end date (by
283     * switching the dates around and returning a negative result).
284     *
285     * @param start  the start date.
286     * @param end  the end date.
287     *
288     * @return the number of days between the two dates, assuming the 30E/360
289     *      day-count convention.
290     */
291    public static int dayCount30E(final SerialDate start, final SerialDate end) {
292        int d1;
293        final int m1;
294        final int y1;
295        int d2;
296        final int m2;
297        final int y2;
298        if (start.isBefore(end)) {
299            d1 = start.getDayOfMonth();
300            m1 = start.getMonth();
301            y1 = start.getYYYY();
302            if (d1 == 31) {  // first European adjustment
303                d1 = 30;
304            }
305            d2 = end.getDayOfMonth();
306            m2 = end.getMonth();
307            y2 = end.getYYYY();
308            if (d2 == 31) {  // first European adjustment
309                d2 = 30;
310            }
311            return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
312        }
313        else if (start.isAfter(end)) {
314            return -dayCount30E(end, start);
315        }
316        else {
317            return 0;
318        }
319    }
320
321    /**
322     * Returns true if the specified date is the last day in February (that is, the
323     * 28th in non-leap years, and the 29th in leap years).
324     *
325     * @param d  the date to be tested.
326     *
327     * @return a boolean that indicates whether or not the specified date is
328     *      the last day of February.
329     */
330    public static boolean isLastDayOfFebruary(final SerialDate d) {
331
332        final int dom;
333        if (d.getMonth() == MonthConstants.FEBRUARY) {
334            dom = d.getDayOfMonth();
335            if (SerialDate.isLeapYear(d.getYYYY())) {
336                return (dom == 29);
337            }
338            else {
339                return (dom == 28);
340            }
341        }
342        else { // not even February
343            return false;
344        }
345
346    }
347
348    /**
349     * Returns the number of times that February 29 falls within the specified
350     * date range.  The result needs to correspond to the ACT/365 (Japanese)
351     * day-count convention. The difficult cases are where the start or the
352     * end date is Feb 29 (include or not?).  Need to find out how JGBs do this
353     * (since this is where the ACT/365 (Japanese) convention comes from ...
354     *
355     * @param start  the start date.
356     * @param end  the end date.
357     *
358     * @return the number of times that February 29 occurs within the date
359     *      range.
360     */
361    public static int countFeb29s(final SerialDate start, final SerialDate end) {
362        int count = 0;
363        SerialDate feb29;
364        final int y1;
365        final int y2;
366        int year;
367
368        // check the order of the dates
369        if (start.isBefore(end)) {
370
371            y1 = start.getYYYY();
372            y2 = end.getYYYY();
373            for (year = y1; year == y2; year++) {
374                if (SerialDate.isLeapYear(year)) {
375                    feb29 = SerialDate.createInstance(29, MonthConstants.FEBRUARY, year);
376                    if (feb29.isInRange(start, end, SerialDate.INCLUDE_SECOND)) {
377                        count++;
378                    }
379                }
380            }
381            return count;
382        }
383        else {
384            return countFeb29s(end, start);
385        }
386    }
387
388}