// bdlt_calendarutil.h                                                -*-C++-*-
#ifndef INCLUDED_BDLT_CALENDARUTIL
#define INCLUDED_BDLT_CALENDARUTIL

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide common date manipulations requiring a calendar.
//
//@CLASSES:
//  bdlt::CalendarUtil: common date manipulations requiring a calendar
//
//@SEE_ALSO: bdlt_date, bdlt_calendar
//
//@DESCRIPTION: This component provides a `struct`, `bdlt::CalendarUtil`, that
// serves as a namespace for date-manipulation functions that require the use
// of a calendar.
//
// This utility component provides the following (static) methods:
// ```
// 'addBusinessDaysIfValid'   Add an integral number of business days to the
//                            specified original date within the valid range
//                            of the specified calendar.
//
// 'nthBusinessDayOfMonthOrMaxIfValid'
//                            Determine the 'n'th business day of the
//                            specified year and month subject to a maximum of
//                            the total number of business days in the
//                            specified year and month, based on the provided
//                            calendar.
//
// 'shiftFollowingIfValid'    If original date is not a business day, move
//                            the date forward until it is a business day.
//
// 'shiftPrecedingIfValid'    If original date is not a business day, move
//                            the date backwards until it is a business day.
//
// 'shiftModifiedFollowingIfValid'
//                            If original date is not a business day, move
//                            the date forward until it is a business day,
//                            unless the date goes into the next month, in
//                            which case the date is moved to the previous
//                            business day.
//
// 'shiftModifiedPrecedingIfValid'
//                            If original date is not a business day, move
//                            the date backward until it is a business day,
//                            unless the date goes into the previous month, in
//                            which case the date is moved to the next
//                            business day.
//
// 'shiftIfValid'             Shift a date based on a provided convention.
//                            Note that this function delegates its operation
//                            to one of the above shift functions, based on
//                            the date-shifting convention specified.
//
// 'subtractBusinessDaysIfValid'
//                            Subtract an integral number of business days
//                            from the specified original date within the
//                            valid range of the specified calendar.
// ```
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Manipulating Dates with `CalendarUtil`
///- - - - - - - - - - - - - - - - - - - - - - - - -
// Suppose that we want to determine the actual interest payment date in
// January 2014 from a US bond that pays on the 20th of each month and uses the
// modified-following date-shifting convention.
//
// We create a calendar, `calUS`, that has the calendar information populated
// for the US in 2014.  We then use the `shiftIfValid` function, provided by
// `CalendarUtil`, to compute the payment date.
//
// First, we create a date for January 1, 2014 that corresponds to the nominal
// payment date (which happens to be holiday) and a calendar with valid range
// from April 20, 2012 through April 20, 2014, typical weekend days, and the
// holiday:
// ```
// const bdlt::Date unadjustedDate(2014, 1, 20);
//
// const bdlt::Date startDate(2012, 4, 20);
// const bdlt::Date endDate(2014, 4, 20);
//
// bdlt::Calendar calUS(startDate, endDate);
// calUS.addWeekendDay(bdlt::DayOfWeek::e_SAT);
// calUS.addWeekendDay(bdlt::DayOfWeek::e_SUN);
// calUS.addHoliday(unadjustedDate);
// ```
// Now, we determine the actual payment date by invoking the `shiftIfValid`
// function:
// ```
// bdlt::Date result;
// int        status = CalendarUtil::shiftIfValid(
//                                        &result,
//                                        unadjustedDate,
//                                        calUS,
//                                        CalendarUtil::e_MODIFIED_FOLLOWING);
// ```
// Notice that `e_MODIFIED_FOLLOWING` is specified as an argument to
// `shiftIfValid` to indicate that we want to use the modified-following
// date-shifting convention.
//
// Finally, we verify that the resulting date is correct:
// ```
// const bdlt::Date expected(2014, 1, 21);
//
// assert(0 == status);
// assert(expected == result);
// ```

#include <bdlscm_version.h>

#include <bdlt_calendar.h>
#include <bdlt_calendarreverseiteratoradapter.h>
#include <bdlt_date.h>
#include <bdlt_dayofweek.h>

#include <bsls_assert.h>
#include <bsls_review.h>

namespace BloombergLP {
namespace bdlt {

                           // ===================
                           // struct CalendarUtil
                           // ===================

/// This `struct` provides a namespace for utility functions that operate on
/// dates in the context of supplied calendars.
struct CalendarUtil {

    // TYPES

    /// Enumeration used to delineate various date-shifting conventions.
    enum ShiftConvention {

        e_UNADJUSTED,           // The date is not adjusted.

        e_FOLLOWING,            // The date is adjusted using
                                // 'shiftFollowingIfValid'.

        e_PRECEDING,            // The date is adjusted using
                                // 'shiftPrecedingIfValid'.

        e_MODIFIED_FOLLOWING,   // The date is adjusted using
                                // 'shiftModifiedFollowingIfValid'.

        e_MODIFIED_PRECEDING    // The date is adjusted using
                                // 'shiftModifiedPrecedingIfValid'.
    };

    // CLASS METHODS

    /// Load, into the specified `result`, the date that is the specified
    /// `numBusinessDays` chronologically after the specified `original`
    /// date according to the specified `calendar`.  The resulting date is
    /// chronologically before the `original` date for negative values of
    /// `numBusinessDays`, the chronologically earliest business day that is
    /// on or after the `original` date for `0 == numBusinessDays`, and
    /// chronologically after the `original` date for positive values of
    /// `numBusinessDays`.  Return 0 on success, and a non-zero value,
    /// without modifying `*result`, if either the `original` date or the
    /// resulting date is not within the valid range of `calendar`.  Note
    /// that if `0 != numBusinessDays`, then the result of
    /// `addBusinessDaysIfValid(res, orig, cal, numBusinessDays)` is
    /// identical to the result of
    /// `subtractBusinessDaysIfValid(res, orig, cal, -numBusinessDays)`.
    static int addBusinessDaysIfValid(bdlt::Date            *result,
                                      const bdlt::Date&      original,
                                      const bdlt::Calendar&  calendar,
                                      int                    numBusinessDays);

    /// Load, into the specified `result`, the date corresponding to the
    /// specified `n`th business day of the specified `month` and the
    /// specified `year` based on the specified `calendar`.  A positive
    /// value of `n` indicates that counting the number of business days
    /// begins from the first calendar date of the month (inclusive), and a
    /// negative value of `n` indicates that counting the number of business
    /// days begins from the last calendar date of the month (inclusive).
    /// If there are fewer than `abs(n)` business days in the month
    /// according to the `calendar`, the business day furthest from the
    /// first date of the month is chosen if `n > 0`, and the business day
    /// furthest from the last date of the month is chosen if `n < 0`.
    /// Return 0 on success, and a non-zero value, without modifying
    /// `*result`, if the entire month specified by `year` and `month` is
    /// not within the valid range of the `calendar` or there are no
    /// business days in the month specified by `year` and `month`.  The
    /// behavior is undefined unless `n != 0`, `1 <= year <= 9999`, and
    /// `1 <= month <= 12`.
    static int nthBusinessDayOfMonthOrMaxIfValid(
                                               bdlt::Date            *result,
                                               const bdlt::Calendar&  calendar,
                                               int                    year,
                                               int                    month,
                                               int                    n);

    /// Load, into the specified `result`, the date of the chronologically
    /// earliest business day that is on or after the specified `original`
    /// date based on the specified `calendar`.  Return 0 on success, and a
    /// non-zero value, without modifying `*result`, if the `original` date
    /// is not within the valid range of `calendar` or the following
    /// business day cannot be found within the valid range of `calendar`.
    static int shiftFollowingIfValid(bdlt::Date            *result,
                                     const bdlt::Date&      original,
                                     const bdlt::Calendar&  calendar);

    /// Load, into the specified `result`, the date of the business day that
    /// is derived from the specified `original` date based on the specified
    /// `calendar` according to the specified date-shifting `convention`.
    /// Return 0 on success, and a non-zero value, without modifying
    /// `*result`, if a valid business date cannot be found according to the
    /// `convention` within the valid range of `calendar`.
    static int shiftIfValid(bdlt::Date            *result,
                            const bdlt::Date&      original,
                            const bdlt::Calendar&  calendar,
                            ShiftConvention        convention);

    /// Load, into the specified `result`, the date of the business day that
    /// is derived from the specified `original` date based on the specified
    /// `calendar` according to the specified date-shifting `convention`,
    /// except when the `original` is either the specified `specialDay` of
    /// the week, or - if the specified `extendSpecialDay` is `true` - one
    /// of the (possibly empty) set of contiguous non-business days
    /// immediately *preceding* a `specialDay` according to the `calendar`,
    /// in which case the `result` is determined using the specified
    /// `specialConvention`.  Return 0 on success, and a non-zero value,
    /// without modifying `*result`, if a valid business date cannot be
    /// found according to the above algorithm within the valid range of
    /// `calendar`.  Note that this method is useful for computing, for
    /// example, Korean bond coupon payment dates.
    static int shiftIfValid(bdlt::Date            *result,
                            const bdlt::Date&      original,
                            const bdlt::Calendar&  calendar,
                            ShiftConvention        convention,
                            bdlt::DayOfWeek::Enum  specialDay,
                            bool                   extendSpecialDay,
                            ShiftConvention        specialConvention);

    /// Load, into the specified `result`, the date of the chronologically
    /// earliest business day that is on or after the specified `original`
    /// date, unless a date cannot be found in the same month, in which case
    /// load the chronologically latest business day before the `original`
    /// date based on the specified `calendar`.  Return 0 on success, and a
    /// non-zero value, without modifying `*result`, if the `original` date
    /// is not within the valid range of `calendar` or a valid business date
    /// cannot be found according to the above algorithm within the valid
    /// range of `calendar`.
    static int shiftModifiedFollowingIfValid(bdlt::Date            *result,
                                             const bdlt::Date&      original,
                                             const bdlt::Calendar&  calendar);

    /// Load, into the specified `result`, the date of the chronologically
    /// latest business day that is on or before the specified `original`
    /// date, unless a date cannot be found in the same month, in which case
    /// load the chronologically earliest business day after the `original`
    /// date based on the specified `calendar`.  Return 0 on success, and a
    /// non-zero value, without modifying `*result`, if the `original` date
    /// is not within the valid range of `calendar` or a valid business date
    /// cannot be found according to the above algorithm within the valid
    /// range of `calendar`.
    static int shiftModifiedPrecedingIfValid(bdlt::Date            *result,
                                             const bdlt::Date&      original,
                                             const bdlt::Calendar&  calendar);

    /// Load, into the specified `result`, the date of the chronologically
    /// latest business day that is on or before the specified `original`
    /// date based on the specified `calendar`.  Return 0 on success, and a
    /// non-zero value, without modifying `*result`, if the `original` date
    /// is not within the valid range of `calendar` or the preceding
    /// business day cannot be found within the valid range of `calendar`.
    static int shiftPrecedingIfValid(bdlt::Date            *result,
                                     const bdlt::Date&      original,
                                     const bdlt::Calendar&  calendar);

    /// Load, into the specified `result`, the date that is the specified
    /// `numBusinessDays` chronologically before the specified `original`
    /// date according to the specified `calendar`.  The resulting date is
    /// chronologically before the `original` date for positive values of
    /// `numBusinessDays`, the chronologically latest business day that is
    /// on or before the `original` date for `0 == numBusinessDays`, and
    /// chronologically after the `original` date for negative values of
    /// `numBusinessDays`.  Return 0 on success, and a non-zero value,
    /// without modifying `*result`, if either the `original` date or the
    /// resulting date is not within the valid range of `calendar`.  Note
    /// that if `0 != numBusinessDays`, then the result of
    /// `subtractBusinessDaysIfValid(res, orig, cal, numBusinessDays)` is
    /// identical to the result of
    /// `addBusinessDaysIfValid(res, orig, cal, -numBusinessDays)`.
    static int subtractBusinessDaysIfValid(
                                       bdlt::Date            *result,
                                       const bdlt::Date&      original,
                                       const bdlt::Calendar&  calendar,
                                       int                    numBusinessDays);
};

// ============================================================================
//                             INLINE DEFINITIONS
// ============================================================================

                           // -------------------
                           // struct CalendarUtil
                           // -------------------

// CLASS METHODS
inline
int CalendarUtil::shiftFollowingIfValid(bdlt::Date            *result,
                                        const bdlt::Date&      original,
                                        const bdlt::Calendar&  calendar)
{
    BSLS_ASSERT(result);

    enum {
        e_SUCCESS      = 0,
        e_BAD_INPUT    = 1,
        e_OUT_OF_RANGE = 2,
        e_NOT_FOUND    = 3
    };

    if (!calendar.isInRange(original)) {
        return e_BAD_INPUT;                                           // RETURN
    }

    bdlt::Calendar::BusinessDayConstIterator iter =
                                          calendar.beginBusinessDays(original);
    if (iter != calendar.endBusinessDays()) {
        *result = *iter;
        return e_SUCCESS;                                             // RETURN
    }
    else {
        return e_OUT_OF_RANGE;                                        // RETURN
    }

    return e_NOT_FOUND;
}

inline
int CalendarUtil::shiftPrecedingIfValid(bdlt::Date            *result,
                                        const bdlt::Date&      original,
                                        const bdlt::Calendar&  calendar)
{
    BSLS_ASSERT(result);

    enum {
        e_SUCCESS      = 0,
        e_BAD_INPUT    = 1,
        e_OUT_OF_RANGE = 2,
        e_NOT_FOUND    = 3
    };

    if (!calendar.isInRange(original)) {
        return e_BAD_INPUT;                                           // RETURN
    }

    bdlt::Calendar::BusinessDayConstReverseIterator iter =
                                         calendar.rbeginBusinessDays(original);
    if (iter != calendar.rendBusinessDays()) {
        *result = *iter;
        return e_SUCCESS;                                             // RETURN
    }
    else {
        return e_OUT_OF_RANGE;                                        // RETURN
    }

    return e_NOT_FOUND;
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2018 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
