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’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}