/*
 Copyright (C) 2008 Richard Gomes

 This source code is release under the BSD License.

 This file is part of JQuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://jquantlib.org/

 JQuantLib is free software: you can redistribute it and/or modify it
 under the terms of the JQuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 . The license is also available online at
 .

 This program 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 license for more details.

 JQuantLib is based on QuantLib. http://quantlib.org/
 When applicable, the original copyright notice follows this notice.
 */

/*
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
 Copyright (C) 2003, 2004, 2005, 2006 StatPro Italia srl
 Copyright (C) 2004 Jeff Yu

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 . The license is also available online at
 .

 This program 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 license for more details.
 */

package org.jquantlib.time;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jquantlib.QL;
import org.jquantlib.lang.annotation.QualityAssurance;
import org.jquantlib.lang.annotation.QualityAssurance.Quality;
import org.jquantlib.lang.annotation.QualityAssurance.Version;
import org.jquantlib.lang.exceptions.LibraryException;

/**
 * This class provides methods for determining whether a date is a business day
 * or a holiday for a given market, and for incrementing/decrementing a date of
 * a given number of business days.
 * 

* A calendar should be defined for specific exchange holiday schedule or for * general country holiday schedule. Legacy city holiday schedule calendars will * be moved to the exchange/country convention. * * @author Richard Gomes */ @QualityAssurance(quality = Quality.Q3_DOCUMENTATION, version = Version.V097, reviewers = { "Zahid Hussain", "Richard Gomes" }) public class Calendar { // // public static fields // public static final String UNKNOWN_MARKET = "Unknown market"; public static final String UKNOWN_BUSINESS_DAY_CONVENTION = "Unknown business day convention"; // // protected fields // /** * All extended classes *must* initialize this field. * * @see "Pimpl" idiom */ protected Impl impl; // // public constructors // public Calendar() { // empty! } // // public methods // /** * Returns whether or not the calendar is initialized */ public boolean empty() /* @ReadOnly */{ return impl == null; } /** * Returns the name of the calendar. warning This method is used for output * and comparison between calendars. It is not meant to be used for * writing switch-on-type code. * * @return String - name of calendar */ public String name() /* @ReadOnly */{ return impl.name(); } /** * Returns true if the date is a business day for the given market. */ public boolean isBusinessDay(final Date d) /* @ReadOnly */{ if (impl.addedHolidays.contains(d)) return false; if (impl.removedHolidays.contains(d)) return true; return impl.isBusinessDay(d); } /** * Returns true if the date is a holiday for the given market. */ public boolean isHoliday(final Date d) /* @ReadOnly */{ return !isBusinessDay(d); } /** * Returns true iff the weekday is part of the weekend for the * given market. */ public boolean isWeekend(final Weekday w) /* @ReadOnly */{ return impl.isWeekend(w); } /** * Returns true if the date is last business day for the month in * given market. */ public boolean isEndOfMonth(final Date d) /* @ReadOnly */{ return (d.month() != adjust(d.add(1)).month()); } /** * Returns last business day of the month to which the given date belongs * * @param d * @return last business Date based on passed date */ public Date endOfMonth(final Date d) /* @ReadOnly */{ return adjust(Date.endOfMonth(d), BusinessDayConvention.Preceding); } /** * Adds a date to the set of holidays for the given calendar. *

* NOTE: This method does not affect pre-defined calendars, returning * silently. * */ public void addHoliday(final Date d) { // if d was a genuine holiday previously removed, revert the change impl.removedHolidays.remove(d); // if it's already a holiday, leave the calendar alone. // Otherwise, add it. if (impl.isBusinessDay(d)) { impl.addedHolidays.add(d); } } /** * Removes a date from the set of holidays for the given calendar. *

* NOTE: This method does not affect pre-defined calendars, returning * silently. */ public void removeHoliday(final Date d) { // if d was an artificially-added holiday, revert the change impl.addedHolidays.remove(d); // if it's already a business day, leave the calendar alone. // Otherwise, add it. if (!impl.isBusinessDay(d)) { impl.removedHolidays.add(d); } } /** * Returns the holidays between two dates */ public static List holidayList(final Calendar c, final Date from, final Date to, final boolean includeWeekEnds) { QL.require(to.gt(from), "'from' date (" + from.toString() + ") must be earlier than 'to' date (" + to.toString() + ")"); final List result = new ArrayList(); for (Date d = from.clone(); d.le(to); d=d.add(1)) { if (c.isHoliday(d) && (includeWeekEnds || !c.isWeekend(d.weekday()))) { result.add(d); } } return result; } /** * Adjusts a non-business day to the appropriate near business day * with respect to the given convention. *

* Assumes convention BusinessDayConvention.Following * * @note The input date is not modified */ public Date adjust(final Date date) /* @ReadOnly */ { return adjust(date, BusinessDayConvention.Following); } /** * Adjusts a non-business day to the appropriate near business day * with respect to the given convention. * * @note The input date is not modified */ public Date adjust(final Date d, final BusinessDayConvention c) /* @ReadOnly */ { if (c == BusinessDayConvention.Unadjusted) return d.clone(); final Date d1 = d.clone(); if (c == BusinessDayConvention.Following || c == BusinessDayConvention.ModifiedFollowing) { while (isHoliday(d1)) { d1.inc(); } if (c == BusinessDayConvention.ModifiedFollowing) { if (d1.month() != d.month()) return adjust(d, BusinessDayConvention.Preceding); } } else if (c == BusinessDayConvention.Preceding || c == BusinessDayConvention.ModifiedPreceding) { while (isHoliday(d1)) { d1.dec(); } if (c == BusinessDayConvention.ModifiedPreceding && d1.month() != d.month()) return adjust(d, BusinessDayConvention.Following); } else throw new LibraryException(UKNOWN_BUSINESS_DAY_CONVENTION); return d1; } /** * Advances the given date as specified by the given period and * returns the result. * * @note The input date is not modified. */ public Date advance(final Date date, final Period period, final BusinessDayConvention convention) /* @ReadOnly */ { return advance(date, period, convention, false); } /** * Advances the given date as specified by the given period and * returns the result. * * @note The input date is not modified. */ public Date advance( final Date date, final Period period, final BusinessDayConvention convention, final boolean endOfMonth) /* @ReadOnly */{ return advance(date, period.length(), period.units(), convention, endOfMonth); } /** * Advances the given date of the given number of business days and * returns the result. * @note The input date is not modified. */ public Date advance( final Date date, final int n, final TimeUnit unit) /* @ReadOnly */ { return advance(date, n, unit, BusinessDayConvention.Following, false); } /** * Advances the given date as specified by the given period and * returns the result. * * @note The input date is not modified. */ public Date advance(final Date date, final Period period) /* @ReadOnly */ { return advance(date, period, BusinessDayConvention.Following, false); } /** * Advances the given date of the given number of business days and * returns the result. * @note The input date is not modified. */ public Date advance( final Date d, int n, final TimeUnit unit, final BusinessDayConvention c, final boolean endOfMonth) /* @ReadOnly */{ QL.require(d != null && !d.isNull(), "null date"); if (n == 0) return adjust(d, c); else if (unit == TimeUnit.Days) { final Date d1 = d.clone(); if (n > 0) { while (n > 0) { d1.inc(); while (isHoliday(d1)) { d1.inc(); } n--; } } else { while (n < 0) { d1.dec(); while (isHoliday(d1)) { d1.dec(); } n++; } } return d1; } else if (unit == TimeUnit.Weeks) { final Date d1 = d.add(new Period(n, unit)); return adjust(d1, c); } else { final Date d1 = d.add(new Period(n, unit)); // we are sure the unit is Months or Years if (endOfMonth && isEndOfMonth(d)) return endOfMonth(d1); return adjust(d1, c); } } /** * Calculates the number of business days between two given dates and * returns the result. */ public int businessDaysBetween(final Date from, final Date to) /* @ReadOnly */{ return businessDaysBetween(from, to, true, false); } public int businessDaysBetween(final Date from, final Date to, final boolean includeFirst, final boolean includeLast) /* @ReadOnly */{ int wd = 0; if (from.ne(to)) { if (from.lt(to)) { // the last one is treated separately to avoid // incrementing Date::maxDate() for (Date d = from.clone(); d.lt(to); d=d.add(1)) { if (isBusinessDay(d)) { ++wd; } } if (isBusinessDay(to)) { ++wd; } } else if (from.gt(to)) { for (Date d = to.clone(); d.lt(from); d=d.add(1)) { if (isBusinessDay(d)) { ++wd; } } if (isBusinessDay(from)) { ++wd; } } if (isBusinessDay(from) && !includeFirst) { wd--; } if (isBusinessDay(to) && !includeLast) { wd--; } if (from.gt(to)) { wd = -wd; } } return wd; } // // public static methods // public static boolean eq(final Calendar c1, final Calendar c2) { return (c1.empty() && c2.empty()) || (!c1.empty() && !c2.empty() && c1.name().equals(c2.name())); } public static boolean ne(final Calendar c1, final Calendar c2) { return !eq(c1, c2); } // // protected inner classes // protected abstract class Impl { private final Set addedHolidays = new HashSet(); private final Set removedHolidays = new HashSet(); protected Impl() { // only extended classes can instantiate } public abstract String name(); public abstract boolean isBusinessDay(final Date d); public abstract boolean isWeekend(Weekday w); } protected abstract class WesternImpl extends Impl { private final short easterMonday[] = { 98, 90, 103, 95, 114, 106, 91, 111, 102, // 1901-1909 87, 107, 99, 83, 103, 95, 115, 99, 91, 111, // 1910-1919 96, 87, 107, 92, 112, 103, 95, 108, 100, 91, // 1920-1929 111, 96, 88, 107, 92, 112, 104, 88, 108, 100, // 1930-1939 85, 104, 96, 116, 101, 92, 112, 97, 89, 108, // 1940-1949 100, 85, 105, 96, 109, 101, 93, 112, 97, 89, // 1950-1959 109, 93, 113, 105, 90, 109, 101, 86, 106, 97, // 1960-1969 89, 102, 94, 113, 105, 90, 110, 101, 86, 106, // 1970-1979 98, 110, 102, 94, 114, 98, 90, 110, 95, 86, // 1980-1989 106, 91, 111, 102, 94, 107, 99, 90, 103, 95, // 1990-1999 115, 106, 91, 111, 103, 87, 107, 99, 84, 103, // 2000-2009 95, 115, 100, 91, 111, 96, 88, 107, 92, 112, // 2010-2019 104, 95, 108, 100, 92, 111, 96, 88, 108, 92, // 2020-2029 112, 104, 89, 108, 100, 85, 105, 96, 116, 101, // 2030-2039 93, 112, 97, 89, 109, 100, 85, 105, 97, 109, // 2040-2049 101, 93, 113, 97, 89, 109, 94, 113, 105, 90, // 2050-2059 110, 101, 86, 106, 98, 89, 102, 94, 114, 105, // 2060-2069 90, 110, 102, 86, 106, 98, 111, 102, 94, 114, // 2070-2079 99, 90, 110, 95, 87, 106, 91, 111, 103, 94, // 2080-2089 107, 99, 91, 103, 95, 115, 107, 91, 111, 103, // 2090-2099 88, 108, 100, 85, 105, 96, 109, 101, 93, 112, // 2100-2109 97, 89, 109, 93, 113, 105, 90, 109, 101, 86, // 2110-2119 106, 97, 89, 102, 94, 113, 105, 90, 110, 101, // 2120-2129 86, 106, 98, 110, 102, 94, 114, 98, 90, 110, // 2130-2139 95, 86, 106, 91, 111, 102, 94, 107, 99, 90, // 2140-2149 103, 95, 115, 106, 91, 111, 103, 87, 107, 99, // 2150-2159 84, 103, 95, 115, 100, 91, 111, 96, 88, 107, // 2160-2169 92, 112, 104, 95, 108, 100, 92, 111, 96, 88, // 2170-2179 108, 92, 112, 104, 89, 108, 100, 85, 105, 96, // 2180-2189 116, 101, 93, 112, 97, 89, 109, 100, 85, 105 // 2190-2199 }; @Override public boolean isWeekend(final Weekday w) { return w == Weekday.Saturday || w == Weekday.Sunday; } /** * expressed relative to first day of year * @param y - year * @return */ protected int easterMonday(final int y) { return easterMonday[y-1901]; } } protected abstract class OrthodoxImpl extends Impl { private final short easterMonday[] = { 105, 118, 110, 102, 121, 106, 126, 118, 102, // 1901-1909 122, 114, 99, 118, 110, 95, 115, 106, 126, 111, // 1910-1919 103, 122, 107, 99, 119, 110, 123, 115, 107, 126, // 1920-1929 111, 103, 123, 107, 99, 119, 104, 123, 115, 100, // 1930-1939 120, 111, 96, 116, 108, 127, 112, 104, 124, 115, // 1940-1949 100, 120, 112, 96, 116, 108, 128, 112, 104, 124, // 1950-1959 109, 100, 120, 105, 125, 116, 101, 121, 113, 104, // 1960-1969 117, 109, 101, 120, 105, 125, 117, 101, 121, 113, // 1970-1979 98, 117, 109, 129, 114, 105, 125, 110, 102, 121, // 1980-1989 106, 98, 118, 109, 122, 114, 106, 118, 110, 102, // 1990-1999 122, 106, 126, 118, 103, 122, 114, 99, 119, 110, // 2000-2009 95, 115, 107, 126, 111, 103, 123, 107, 99, 119, // 2010-2019 111, 123, 115, 107, 127, 111, 103, 123, 108, 99, // 2020-2029 119, 104, 124, 115, 100, 120, 112, 96, 116, 108, // 2030-2039 128, 112, 104, 124, 116, 100, 120, 112, 97, 116, // 2040-2049 108, 128, 113, 104, 124, 109, 101, 120, 105, 125, // 2050-2059 117, 101, 121, 113, 105, 117, 109, 101, 121, 105, // 2060-2069 125, 110, 102, 121, 113, 98, 118, 109, 129, 114, // 2070-2079 106, 125, 110, 102, 122, 106, 98, 118, 110, 122, // 2080-2089 114, 99, 119, 110, 102, 115, 107, 126, 118, 103, // 2090-2099 123, 115, 100, 120, 112, 96, 116, 108, 128, 112, // 2100-2109 104, 124, 109, 100, 120, 105, 125, 116, 108, 121, // 2110-2119 113, 104, 124, 109, 101, 120, 105, 125, 117, 101, // 2120-2129 121, 113, 98, 117, 109, 129, 114, 105, 125, 110, // 2130-2139 102, 121, 113, 98, 118, 109, 129, 114, 106, 125, // 2140-2149 110, 102, 122, 106, 126, 118, 103, 122, 114, 99, // 2150-2159 119, 110, 102, 115, 107, 126, 111, 103, 123, 114, // 2160-2169 99, 119, 111, 130, 115, 107, 127, 111, 103, 123, // 2170-2179 108, 99, 119, 104, 124, 115, 100, 120, 112, 103, // 2180-2189 116, 108, 128, 119, 104, 124, 116, 100, 120, 112 // 2190-2199 }; @Override public boolean isWeekend(final Weekday w) { return w == Weekday.Saturday || w == Weekday.Sunday; } /** * @return the offset of the Easter Monday relative to the * first day of the year */ protected final int easterMonday(final int year) { return easterMonday[year-1901]; } } }

Ads help maintain this website.