/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.basics.schedule; import java.time.DayOfWeek; import java.time.LocalDate; import org.joda.convert.FromString; import org.joda.convert.ToString; import com.opengamma.strata.basics.schedule.DayRollConventions.Dom; import com.opengamma.strata.basics.schedule.DayRollConventions.Dow; import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.collect.named.ExtendedEnum; import com.opengamma.strata.collect.named.Named; /** * A convention defining how to roll dates. * <p> * A {@linkplain PeriodicSchedule periodic schedule} is determined using a periodic frequency. * When applying the frequency, the roll convention is used to fine tune the dates. * This might involve selecting the last day of the month, or the third Wednesday. * <p> * To get the next date in the schedule, take the base date and the * {@linkplain Frequency periodic frequency}. Once this date is calculated, * the roll convention is applied to produce the next schedule date. * <p> * The most common implementations are provided as constants on {@link RollConventions}. * Additional implementations may be added by implementing this interface. * <p> * All implementations of this interface must be immutable and thread-safe. */ public interface RollConvention extends Named { /** * Obtains an instance from the specified unique name. * * @param uniqueName the unique name * @return the roll convention * @throws IllegalArgumentException if the name is not known */ @FromString public static RollConvention of(String uniqueName) { ArgChecker.notNull(uniqueName, "uniqueName"); return extendedEnum().lookup(uniqueName); } /** * Obtains an instance from the day-of-month. * <p> * This convention will adjust the input date to the specified day-of-month. * The year and month of the result date will be the same as the input date. * It is intended for use with periods that are a multiple of months. * <p> * If the month being adjusted has a length less than the requested day-of-month * then the last valid day-of-month will be chosen. As such, passing 31 to this * method is equivalent to selecting the end-of-month convention. * * @param dayOfMonth the day-of-month, from 1 to 31 * @return the roll convention * @throws IllegalArgumentException if the day-of-month is invalid */ public static RollConvention ofDayOfMonth(int dayOfMonth) { return Dom.of(dayOfMonth); } /** * Obtains an instance from the day-of-week. * <p> * This convention will adjust the input date to the specified day-of-week. * It is intended for use with periods that are a multiple of weeks. * <p> * In {@code adjust()}, if the input date is not the required day-of-week, * then the next occurrence of the day-of-week is selected, up to 6 days later. * <p> * In {@code next()}, the day-of-week is selected after the frequency is added. * If the calculated date is not the required day-of-week, then the next occurrence * of the day-of-week is selected, up to 6 days later. * <p> * In {@code previous()}, the day-of-week is selected after the frequency is subtracted. * If the calculated date is not the required day-of-week, then the previous occurrence * of the day-of-week is selected, up to 6 days earlier. * * @param dayOfWeek the day-of-week * @return the roll convention */ public static RollConvention ofDayOfWeek(DayOfWeek dayOfWeek) { return Dow.of(dayOfWeek); } /** * Gets the extended enum helper. * <p> * This helper allows instances of the convention to be looked up. * It also provides the complete set of available instances. * * @return the extended enum helper */ public static ExtendedEnum<RollConvention> extendedEnum() { return RollConventions.ENUM_LOOKUP; } //------------------------------------------------------------------------- /** * Adjusts the date according to the rules of the roll convention. * <p> * See the description of each roll convention to understand the rule applied. * <p> * It is recommended to use {@code next()} and {@code previous()} rather than * directly using this method. * * @param date the date to adjust * @return the adjusted temporal */ public abstract LocalDate adjust(LocalDate date); /** * Checks if the date matches the rules of the roll convention. * <p> * See the description of each roll convention to understand the rule applied. * * @param date the date to check * @return true if the date matches this convention */ public default boolean matches(LocalDate date) { ArgChecker.notNull(date, "date"); return date.equals(adjust(date)); } //------------------------------------------------------------------------- /** * Calculates the next date in the sequence after the input date. * <p> * This takes the input date, adds the periodic frequency and adjusts the date * as necessary to match the roll convention rules. * The result will always be after the input date. * <p> * The default implementation is suitable for month-based conventions. * * @param date the date to adjust * @param periodicFrequency the periodic frequency of the schedule * @return the adjusted date */ public default LocalDate next(LocalDate date, Frequency periodicFrequency) { ArgChecker.notNull(date, "date"); ArgChecker.notNull(periodicFrequency, "periodicFrequency"); LocalDate calculated = adjust(date.plus(periodicFrequency)); if (calculated.isAfter(date) == false) { calculated = adjust(date.plusMonths(1)); } return calculated; } //------------------------------------------------------------------------- /** * Calculates the previous date in the sequence after the input date. * <p> * This takes the input date, subtracts the periodic frequency and adjusts the date * as necessary to match the roll convention rules. * The result will always be before the input date. * <p> * The default implementation is suitable for month-based conventions. * * @param date the date to adjust * @param periodicFrequency the periodic frequency of the schedule * @return the adjusted date */ public default LocalDate previous(LocalDate date, Frequency periodicFrequency) { ArgChecker.notNull(date, "date"); ArgChecker.notNull(periodicFrequency, "periodicFrequency"); LocalDate calculated = adjust(date.minus(periodicFrequency)); if (calculated.isBefore(date) == false) { calculated = adjust(date.minusMonths(1)); } return calculated; } //------------------------------------------------------------------------- /** * Gets the day-of-month that the roll convention implies. * <p> * This extracts the day-of-month for simple roll conventions. * The numeric roll conventions will return their day-of-month. * The 'EOM' convention will return 31. * All other conventions will return zero. * * @return the day-of-month that the roll convention implies, zero if not applicable */ public default int getDayOfMonth() { return 0; } /** * Gets the name that uniquely identifies this convention. * <p> * This name is used in serialization and can be parsed using {@link #of(String)}. * * @return the unique name */ @ToString @Override public abstract String getName(); }