    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}