001/* 002 * ObjectLab, http://www.objectlab.co.uk/open is sponsoring the ObjectLab Kit. 003 * 004 * Based in London, we are world leaders in the design and development 005 * of bespoke applications for the securities financing markets. 006 * 007 * <a href="http://www.objectlab.co.uk/open">Click here to learn more</a> 008 * ___ _ _ _ _ _ 009 * / _ \| |__ (_) ___ ___| |_| | __ _| |__ 010 * | | | | '_ \| |/ _ \/ __| __| | / _` | '_ \ 011 * | |_| | |_) | | __/ (__| |_| |__| (_| | |_) | 012 * \___/|_.__// |\___|\___|\__|_____\__,_|_.__/ 013 * |__/ 014 * 015 * www.ObjectLab.co.uk 016 * 017 * $Id$ 018 * 019 * Copyright 2006 the original author or authors. 020 * 021 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 022 * use this file except in compliance with the License. You may obtain a copy of 023 * the License at 024 * 025 * http://www.apache.org/licenses/LICENSE-2.0 026 * 027 * Unless required by applicable law or agreed to in writing, software 028 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 029 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 030 * License for the specific language governing permissions and limitations under 031 * the License. 032 */ 033package net.objectlab.kit.datecalc.common; 034 035import static net.objectlab.kit.datecalc.common.HolidayHandlerType.BACKWARD; 036import static net.objectlab.kit.datecalc.common.HolidayHandlerType.FORWARD; 037import static net.objectlab.kit.datecalc.common.HolidayHandlerType.MODIFIED_FOLLOWING; 038import static net.objectlab.kit.datecalc.common.HolidayHandlerType.MODIFIED_PRECEDING; 039 040import java.io.Serializable; 041import java.util.ArrayList; 042import java.util.HashSet; 043import java.util.List; 044import java.util.Set; 045 046/** 047 * Abstract implementation in order to encapsulate all the common functionality 048 * between Jdk and Joda implementations. It is parameterized on <E> 049 * but basically <code>Date</code> and <code>LocalDate</code> are the only 050 * viable values for it for now. 051 * 052 * @author Marcin Jekot and Benoit Xhenseval 053 * 054 * @param <E> 055 * a representation of a date, typically JDK: Date, Calendar; 056 * Joda:LocalDate, YearMonthDay 057 * 058 */ 059public abstract class AbstractDateCalculator<E extends Serializable> implements DateCalculator<E> { 060 private static final int MONTHS_IN_YEAR = 12; 061 062 protected static final int DAYS_IN_WEEK = 7; 063 064 private String name; 065 066 private E startDate; 067 068 private E currentBusinessDate; 069 070 private HolidayCalendar<E> holidayCalendar; 071 072 private HolidayHandler<E> holidayHandler; 073 074 private int currentIncrement; 075 076 protected AbstractDateCalculator(final String name, final HolidayCalendar<E> holidayCalendar, final HolidayHandler<E> holidayHandler) { 077 this.name = name; 078 if (holidayCalendar != null) { 079 this.holidayCalendar = new ImmutableHolidayCalendar<>(holidayCalendar); 080 } else { 081 this.holidayCalendar = new ImmutableHolidayCalendar<>(new DefaultHolidayCalendar<E>()); 082 } 083 this.holidayHandler = holidayHandler; 084 } 085 086 @Override 087 public DateCalculator<E> setHolidayCalendar(final HolidayCalendar<E> calendar) { 088 if (calendar != null) { 089 if (calendar instanceof ImmutableHolidayCalendar) { 090 holidayCalendar = calendar; 091 } else { 092 holidayCalendar = new ImmutableHolidayCalendar<>(calendar); 093 } 094 } else { 095 holidayCalendar = new ImmutableHolidayCalendar<>(new DefaultHolidayCalendar<E>()); 096 } 097 return this; 098 } 099 100 @Override 101 public String getName() { 102 return name; 103 } 104 105 public void setName(final String name) { 106 this.name = name; 107 } 108 109 @Override 110 public E getStartDate() { 111 if (startDate == null) { 112 startDate = getToday(); 113 } 114 return startDate; 115 } 116 117 /** Set both start date and current date */ 118 @Override 119 public DateCalculator<E> setStartDate(final E startDate) { 120 this.startDate = startDate; 121 setCurrentBusinessDate(startDate); 122 return this; 123 } 124 125 @Override 126 public E getCurrentBusinessDate() { 127 if (currentBusinessDate == null) { 128 currentBusinessDate = getToday(); 129 } 130 return currentBusinessDate; 131 } 132 133 /** 134 * move the current date by a given tenor, this means that if a date is 135 * either a 'weekend' or holiday, it will be skipped acording to the holiday 136 * handler and not count towards the number of days to move. 137 * 138 * @param tenor the tenor. 139 * @param spotLag 140 * number of days to "spot" days, this can vary from one market 141 * to the other. 142 * @return the current businessCalendar (so one can do 143 * calendar.moveByTenor(StandardTenor.T_2M).getCurrentBusinessDate();) 144 */ 145 @Override 146 public DateCalculator<E> moveByTenor(final Tenor tenor, final int spotLag) { 147 if (tenor == null) { 148 throw new IllegalArgumentException("Tenor cannot be null"); 149 } 150 151 TenorCode tenorCode = tenor.getCode(); 152 if (tenorCode != TenorCode.OVERNIGHT && tenorCode != TenorCode.TOM_NEXT /*&& spotLag != 0*/) { 153 // get to the Spot date first: 154 moveToSpotDate(spotLag); 155 } 156 int unit = tenor.getUnits(); 157 if (tenorCode == TenorCode.WEEK) { 158 tenorCode = TenorCode.DAY; 159 unit *= DAYS_IN_WEEK; 160 } 161 162 if (tenorCode == TenorCode.YEAR) { 163 tenorCode = TenorCode.MONTH; 164 unit *= MONTHS_IN_YEAR; 165 } 166 167 return applyTenor(tenorCode, unit); 168 } 169 170 protected DateCalculator<E> applyTenor(final TenorCode tenorCode, final int unit) { 171 DateCalculator<E> calc; 172 // move by tenor 173 switch (tenorCode) { 174 case OVERNIGHT: 175 calc = moveByDays(1); 176 break; 177 case TOM_NEXT: // it would have NOT moved by 178 moveByDays(1); // calculate Tomorrow 179 calc = moveByDays(1); // then the next! 180 break; 181 case SPOT_NEXT: 182 calc = moveByDays(1); 183 break; 184 case SPOT: 185 calc = this; 186 break; 187 case DAY: 188 calc = moveByDays(unit); 189 break; 190 case MONTH: 191 calc = moveByMonths(unit); 192 break; 193 default: 194 throw new UnsupportedOperationException("Sorry not yet..."); 195 } 196 return calc; 197 } 198 199 /** 200 * Move the current date by a given tenor, please note that all tenors are 201 * relative to the CURRENT day (and NOT from spot). 202 * 203 * @param tenor 204 * the Tenor to reach. 205 * @return the current DateCalculator 206 * @since 1.1.0 207 */ 208 @Override 209 public DateCalculator<E> moveByTenor(final Tenor tenor) { 210 return moveByTenor(tenor, 0); 211 } 212 213 /** 214 * Calculate a series of Tenor codes in one go based on current day, 215 * this does NOT change the current business date. 216 * 217 * @return list of dates in same order as tenors. 218 * @since 1.1.0 219 */ 220 @Override 221 public List<E> calculateTenorDates(final List<Tenor> tenors) { 222 return calculateTenorDates(tenors, 0); 223 } 224 225 /** 226 * Calculate a series of Tenor codes in one go based on SPOT day (calculated 227 * with the spot lag), this does NOT change the current business date. 228 * 229 * @return list of dates in same order as tenors. 230 * @since 1.1.0 231 */ 232 @Override 233 public List<E> calculateTenorDates(final List<Tenor> tenors, final int spotLag) { 234 final List<E> list = new ArrayList<>(); 235 236 if (tenors != null) { 237 final E originalDate = clone(getCurrentBusinessDate()); 238 for (final Tenor tenor : tenors) { 239 moveByTenor(tenor, spotLag); 240 list.add(getCurrentBusinessDate()); 241 setCurrentBusinessDate(originalDate); 242 } 243 } 244 245 return list; 246 } 247 248 // ----------------------------------------------------------------------- 249 // 250 // ObjectLab, world leaders in the design and development of bespoke 251 // applications for the securities financing markets. 252 // www.ObjectLab.co.uk 253 // 254 // ----------------------------------------------------------------------- 255 256 protected abstract DateCalculator<E> moveByMonths(int months); 257 258 public DateCalculator<E> setHolidayHandler(final HolidayHandler<E> holidayHandler) { 259 this.holidayHandler = holidayHandler; 260 return this; 261 } 262 263 @Override 264 public String getHolidayHandlerType() { 265 return holidayHandler != null ? holidayHandler.getType() : null; 266 } 267 268 /** 269 * is the given date a non working day? 270 */ 271 @Override 272 public boolean isNonWorkingDay(final E date) { 273 if (date != null && (holidayCalendar.getEarlyBoundary() != null || holidayCalendar.getLateBoundary() != null)) { 274 checkBoundary(date); 275 } 276 return isWeekend(date) || holidayCalendar.isHoliday(date); 277 } 278 279 /** 280 * This may throw an {@link IndexOutOfBoundsException} if the date is not within the 281 * boundaries. 282 * @param date 283 */ 284 protected abstract void checkBoundary(E date); 285 286 @Override 287 public boolean isCurrentDateNonWorking() { 288 if (currentBusinessDate == null) { 289 currentBusinessDate = getToday(); 290 } 291 return isNonWorkingDay(currentBusinessDate); 292 } 293 294 @Override 295 public E forceCurrentDateNoAdjustment(final E date) { 296 currentBusinessDate = date; 297 return currentBusinessDate; 298 } 299 300 @Override 301 public E setCurrentBusinessDate(final E date) { 302 currentBusinessDate = date; 303 if (holidayHandler != null && date != null) { 304 currentBusinessDate = holidayHandler.moveCurrentDate(this); 305 } 306 if (date != null && (holidayCalendar.getEarlyBoundary() != null || holidayCalendar.getLateBoundary() != null)) { 307 checkBoundary(date); 308 } 309 return currentBusinessDate; 310 } 311 312 public HolidayHandler<E> getHolidayHandler() { 313 return holidayHandler; 314 } 315 316 protected void moveToSpotDate(final int spotLag) { 317 moveByBusinessDays(spotLag); 318 } 319 320 @Override 321 public DateCalculator<E> moveByBusinessDays(final int businessDays) { 322 checkHolidayValidity(businessDays); 323 324 final int numberOfStepsLeft = Math.abs(businessDays); 325 final int step = businessDays < 0 ? -1 : 1; 326 327 for (int i = 0; i < numberOfStepsLeft; i++) { 328 moveByDays(step); 329 } 330 331 return this; 332 } 333 334 private void checkHolidayValidity(final int businessDays) { 335 if (businessDays > 0 && holidayHandler != null 336 && (holidayHandler.getType().equals(BACKWARD) || holidayHandler.getType().equals(MODIFIED_PRECEDING))) { 337 throw new IllegalArgumentException( 338 "A " + MODIFIED_PRECEDING + " or " + BACKWARD + " does not allow positive steps for moveByBusinessDays"); 339 } else if (businessDays < 0 && holidayHandler != null 340 && (holidayHandler.getType().equals(FORWARD) || holidayHandler.getType().equals(MODIFIED_FOLLOWING))) { 341 throw new IllegalArgumentException( 342 "A " + MODIFIED_FOLLOWING + " or " + FORWARD + " does not allow negative steps for moveByBusinessDays"); 343 } 344 } 345 346 /** 347 * Allows DateCalculators to be combined into a new one, the startDate and 348 * currentBusinessDate will be the ones from the existing calendar (not the 349 * parameter one). The name will be combined name1+"/"+calendar.getName(). 350 * 351 * @param calculator 352 * return the same DateCalculator if calendar is null or the 353 * original calendar (but why would you want to do that?) 354 * @throws IllegalArgumentException 355 * if both calendars have different types of HolidayHandlers or 356 * WorkingWeek; 357 */ 358 @Override 359 public DateCalculator<E> combine(final DateCalculator<E> calculator) { 360 if (calculator == null || calculator == this) { 361 return this; 362 } 363 364 checkHolidayHandlerValidity(calculator); 365 366 final Set<E> newSet = new HashSet<>(); 367 if (holidayCalendar != null) { 368 newSet.addAll(holidayCalendar.getHolidays()); 369 } 370 371 final HolidayCalendar<E> calendarToCombine = calculator.getHolidayCalendar(); 372 checkBoundaries(calendarToCombine); 373 374 if (calendarToCombine.getHolidays() != null) { 375 newSet.addAll(calendarToCombine.getHolidays()); 376 } 377 378 final HolidayCalendar<E> newCal = new DefaultHolidayCalendar<>(newSet, 379 compareDate(holidayCalendar.getEarlyBoundary(), calendarToCombine.getEarlyBoundary(), false), 380 compareDate(holidayCalendar.getLateBoundary(), calendarToCombine.getLateBoundary(), true)); 381 382 return createNewCalculator(getName() + "/" + calculator.getName(), getStartDate(), newCal, holidayHandler); 383 } 384 385 private void checkHolidayHandlerValidity(final DateCalculator<E> calculator) { 386 if (holidayHandler == null && calculator.getHolidayHandlerType() != null 387 || holidayHandler != null && !holidayHandler.getType().equals(calculator.getHolidayHandlerType())) { 388 throw new IllegalArgumentException("Combined Calendars cannot have different handler types"); 389 } 390 } 391 392 private void checkBoundaries(final HolidayCalendar<E> calendarToCombine) { 393 if (calendarToCombine.getEarlyBoundary() != null && holidayCalendar.getEarlyBoundary() == null 394 || calendarToCombine.getEarlyBoundary() == null && holidayCalendar.getEarlyBoundary() != null) { 395 throw new IllegalArgumentException("Both Calendar to be combined must either have each Early boundaries or None."); 396 } 397 398 if (calendarToCombine.getLateBoundary() != null && holidayCalendar.getLateBoundary() == null 399 || calendarToCombine.getLateBoundary() == null && holidayCalendar.getLateBoundary() != null) { 400 throw new IllegalArgumentException("Both Calendar to be combined must either have each Late boundaries or None."); 401 } 402 } 403 404 protected abstract E getToday(); 405 406 protected abstract E compareDate(E date1, E date2, boolean returnEarliest); 407 408 protected abstract DateCalculator<E> createNewCalculator(String calcName, E theStartDate, HolidayCalendar<E> holidays, HolidayHandler<E> handler); 409 410 /** 411 * @return Returns the currentIncrement. 412 */ 413 @Override 414 public int getCurrentIncrement() { 415 return currentIncrement; 416 } 417 418 /** 419 * @param currentIncrement The currentIncrement to set. 420 */ 421 @Override 422 public DateCalculator<E> setCurrentIncrement(final int currentIncrement) { 423 this.currentIncrement = currentIncrement; 424 return this; 425 } 426 427 /** 428 * @return Returns the holidayCalendar. 429 */ 430 @Override 431 public HolidayCalendar<E> getHolidayCalendar() { 432 return holidayCalendar; 433 } 434 435 protected abstract E clone(final E date); 436} 437 438/* 439 * ObjectLab, http://www.objectlab.co.uk/open is sponsoring the ObjectLab Kit. 440 * 441 * Based in London, we are world leaders in the design and development 442 * of bespoke applications for the securities financing markets. 443 * 444 * <a href="http://www.objectlab.co.uk/open">Click here to learn more about us</a> 445 * ___ _ _ _ _ _ 446 * / _ \| |__ (_) ___ ___| |_| | __ _| |__ 447 * | | | | '_ \| |/ _ \/ __| __| | / _` | '_ \ 448 * | |_| | |_) | | __/ (__| |_| |__| (_| | |_) | 449 * \___/|_.__// |\___|\___|\__|_____\__,_|_.__/ 450 * |__/ 451 * 452 * www.ObjectLab.co.uk 453 */