/* ======================================================================== * JCommon : a free general purpose class library for the Java(tm) platform * ======================================================================== * * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jcommon/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * ------------------------ * SerialDateUtilities.java * ------------------------ * (C) Copyright 2001-2003, by Object Refinery Limited. * * Original Author: David Gilbert (for Object Refinery Limited); * Contributor(s): -; * * $Id: SerialDateUtilities.java,v 1.6 2005/11/16 15:58:40 taqua Exp $ * * Changes (from 26-Oct-2001) * -------------------------- * 26-Oct-2001 : Changed package to com.jrefinery.date.*; * 08-Dec-2001 : Dropped isLeapYear() method (DG); * 04-Mar-2002 : Renamed SerialDates.java --> SerialDateUtilities.java (DG); * 25-Jun-2002 : Fixed a bug in the dayCountActual() method (DG); * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG); * */ package org.jfree.date; import java.text.DateFormatSymbols; import java.util.Calendar; /** * A utility class that provides a number of useful methods (some static). * Many of these are used in the implementation of the day-count convention * classes. I recognise some limitations in this implementation: * <p> * [1] some of the methods assume that the default Calendar is a * GregorianCalendar (used mostly to determine leap years) - so the code * won’t work if some other Calendar is the default. I'm not sure how * to handle this properly? * <p> * [2] a whole bunch of static methods isn't very object-oriented - but I couldn't think of a good * way to extend the Date and Calendar classes to add the functions I required, * so static methods are doing the job for now. * * @author David Gilbert */ public class SerialDateUtilities { /** The default date format symbols. */ private DateFormatSymbols dateFormatSymbols; /** Strings representing the weekdays. */ private String[] weekdays; /** Strings representing the months. */ private String[] months; /** * Creates a new utility class for the default locale. */ public SerialDateUtilities() { this.dateFormatSymbols = new DateFormatSymbols(); this.weekdays = this.dateFormatSymbols.getWeekdays(); this.months = this.dateFormatSymbols.getMonths(); } /** * Returns an array of strings representing the days-of-the-week. * * @return an array of strings representing the days-of-the-week. */ public String[] getWeekdays() { return this.weekdays; } /** * Returns an array of strings representing the months. * * @return an array of strings representing the months. */ public String[] getMonths() { return this.months; } /** * Converts the specified string to a weekday, using the default locale. * * @param s a string representing the day-of-the-week. * * @return an integer representing the day-of-the-week. */ public int stringToWeekday(final String s) { if (s.equals(this.weekdays[Calendar.SATURDAY])) { return SerialDate.SATURDAY; } else if (s.equals(this.weekdays[Calendar.SUNDAY])) { return SerialDate.SUNDAY; } else if (s.equals(this.weekdays[Calendar.MONDAY])) { return SerialDate.MONDAY; } else if (s.equals(this.weekdays[Calendar.TUESDAY])) { return SerialDate.TUESDAY; } else if (s.equals(this.weekdays[Calendar.WEDNESDAY])) { return SerialDate.WEDNESDAY; } else if (s.equals(this.weekdays[Calendar.THURSDAY])) { return SerialDate.THURSDAY; } else { return SerialDate.FRIDAY; } } /** * Returns the actual number of days between two dates. * * @param start the start date. * @param end the end date. * * @return the number of days between the start date and the end date. */ public static int dayCountActual(final SerialDate start, final SerialDate end) { return end.compare(start); } /** * Returns the number of days between the specified start and end dates, * assuming that there are thirty days in every month (that is, * corresponding to the 30/360 day-count convention). * <P> * The method handles cases where the start date is before the end date (by * switching the dates and returning a negative result). * * @param start the start date. * @param end the end date. * * @return the number of days between the two dates, assuming the 30/360 day-count convention. */ public static int dayCount30(final SerialDate start, final SerialDate end) { final int d1; final int m1; final int y1; final int d2; final int m2; final int y2; if (start.isBefore(end)) { // check the order of the dates d1 = start.getDayOfMonth(); m1 = start.getMonth(); y1 = start.getYYYY(); d2 = end.getDayOfMonth(); m2 = end.getMonth(); y2 = end.getYYYY(); return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1); } else { return -dayCount30(end, start); } } /** * Returns the number of days between the specified start and end dates, * assuming that there are thirty days in every month, and applying the * ISDA adjustments (that is, corresponding to the 30/360 (ISDA) day-count * convention). * <P> * The method handles cases where the start date is before the end date (by * switching the dates around and returning a negative result). * * @param start the start date. * @param end the end date. * * @return The number of days between the two dates, assuming the 30/360 * (ISDA) day-count convention. */ public static int dayCount30ISDA(final SerialDate start, final SerialDate end) { int d1; final int m1; final int y1; int d2; final int m2; final int y2; if (start.isBefore(end)) { d1 = start.getDayOfMonth(); m1 = start.getMonth(); y1 = start.getYYYY(); if (d1 == 31) { // first ISDA adjustment d1 = 30; } d2 = end.getDayOfMonth(); m2 = end.getMonth(); y2 = end.getYYYY(); if ((d2 == 31) && (d1 == 30)) { // second ISDA adjustment d2 = 30; } return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1); } else if (start.isAfter(end)) { return -dayCount30ISDA(end, start); } else { return 0; } } /** * Returns the number of days between the specified start and end dates, * assuming that there are thirty days in every month, and applying the PSA * adjustments (that is, corresponding to the 30/360 (PSA) day-count convention). * The method handles cases where the start date is before the end date (by * switching the dates around and returning a negative result). * * @param start the start date. * @param end the end date. * * @return The number of days between the two dates, assuming the 30/360 * (PSA) day-count convention. */ public static int dayCount30PSA(final SerialDate start, final SerialDate end) { int d1; final int m1; final int y1; int d2; final int m2; final int y2; if (start.isOnOrBefore(end)) { // check the order of the dates d1 = start.getDayOfMonth(); m1 = start.getMonth(); y1 = start.getYYYY(); if (SerialDateUtilities.isLastDayOfFebruary(start)) { d1 = 30; } if ((d1 == 31) || SerialDateUtilities.isLastDayOfFebruary(start)) { // first PSA adjustment d1 = 30; } d2 = end.getDayOfMonth(); m2 = end.getMonth(); y2 = end.getYYYY(); if ((d2 == 31) && (d1 == 30)) { // second PSA adjustment d2 = 30; } return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1); } else { return -dayCount30PSA(end, start); } } /** * Returns the number of days between the specified start and end dates, * assuming that there are thirty days in every month, and applying the * European adjustment (that is, corresponding to the 30E/360 day-count * convention). * <P> * The method handles cases where the start date is before the end date (by * switching the dates around and returning a negative result). * * @param start the start date. * @param end the end date. * * @return the number of days between the two dates, assuming the 30E/360 * day-count convention. */ public static int dayCount30E(final SerialDate start, final SerialDate end) { int d1; final int m1; final int y1; int d2; final int m2; final int y2; if (start.isBefore(end)) { d1 = start.getDayOfMonth(); m1 = start.getMonth(); y1 = start.getYYYY(); if (d1 == 31) { // first European adjustment d1 = 30; } d2 = end.getDayOfMonth(); m2 = end.getMonth(); y2 = end.getYYYY(); if (d2 == 31) { // first European adjustment d2 = 30; } return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1); } else if (start.isAfter(end)) { return -dayCount30E(end, start); } else { return 0; } } /** * Returns true if the specified date is the last day in February (that is, the * 28th in non-leap years, and the 29th in leap years). * * @param d the date to be tested. * * @return a boolean that indicates whether or not the specified date is * the last day of February. */ public static boolean isLastDayOfFebruary(final SerialDate d) { final int dom; if (d.getMonth() == MonthConstants.FEBRUARY) { dom = d.getDayOfMonth(); if (SerialDate.isLeapYear(d.getYYYY())) { return (dom == 29); } else { return (dom == 28); } } else { // not even February return false; } } /** * Returns the number of times that February 29 falls within the specified * date range. The result needs to correspond to the ACT/365 (Japanese) * day-count convention. The difficult cases are where the start or the * end date is Feb 29 (include or not?). Need to find out how JGBs do this * (since this is where the ACT/365 (Japanese) convention comes from ... * * @param start the start date. * @param end the end date. * * @return the number of times that February 29 occurs within the date * range. */ public static int countFeb29s(final SerialDate start, final SerialDate end) { int count = 0; SerialDate feb29; final int y1; final int y2; int year; // check the order of the dates if (start.isBefore(end)) { y1 = start.getYYYY(); y2 = end.getYYYY(); for (year = y1; year == y2; year++) { if (SerialDate.isLeapYear(year)) { feb29 = SerialDate.createInstance(29, MonthConstants.FEBRUARY, year); if (feb29.isInRange(start, end, SerialDate.INCLUDE_SECOND)) { count++; } } } return count; } else { return countFeb29s(end, start); } } }