// bdlt_dateutil.h                                                    -*-C++-*-
#ifndef INCLUDED_BDLT_DATEUTIL
#define INCLUDED_BDLT_DATEUTIL

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

//@PURPOSE: Provide common non-primitive operations on date objects.
//
//@CLASSES:
//  bdlt::DateUtil: namespace for non-primitive operations on date objects
//
//@SEE_ALSO: bdlt_date
//
//@DESCRIPTION: This component provides a 'struct', 'bdlt::DateUtil', that
// serves as a namespace for utility functions that operate on 'bdlt::Date'
// objects.
//
// The following list of methods are provided by 'bdlt::DateUtil':
//..
//  'isValidYYYYMMDD'             o Validate or convert to and from the
//  'convertFromYYYYMMDDRaw'        "YYYYMMDD" format
//  'convertFromYYYYMMDD'           (see {"YYYYMMDD" Format}).
//  'convertToYYYYMMDD'
//
//  'nextDayOfWeek'               o Move a date to the next or the previous
//  'nextDayOfWeekInclusive'        specified day of week.
//  'previousDayOfWeek'
//  'previousDayOfWeekInclusive'
//
//  'earliestDayOfWeekInMonth'    o Find a specified day of the week in a
//  'nthDayOfWeekInMonth'           specified year and month.
//  'lastDayOfWeekInMonth'
//  'lastDayInMonth'
//
//  'addMonthsEom'                o Add a specified number of months to a date
//  'addMonthsNoEom'                using either the end-of-month or the
//  'addMonths'                     non-end-of-month convention (see
//                                  {End-of-Month Adjustment Conventions}).
//
//  'addYearsEom'                 o Add a specified number of years to a date
//  'addYearsNoEom'                 using either the end-of-month or the
//  'addYears'                      non-end-of-month convention (see
//                                  {End-of-Month Adjustment Conventions}).
//..
//
///"YYYYMMDD" Format
///-----------------
// The "YYYYMMDD" format is a common integral representation of a date that is
// human readable and maintains appropriate ordering when sorted using integer
// comparisons.  The notation uses eight digits (from left to right): four
// digits for the year, two digits for the month, and two digits for the day of
// the month.  For example, February 1, 2014, is represented by the number
// 20140201.
//
// Note that the year is not restricted to values on or after 1000, so, for
// example, 10102 (or 00010102) represents the date January 2, 0001.
//
///End-of-Month Adjustment Conventions
///-----------------------------------
// Two adjustment conventions are used to determine the behavior of the
// functions ('addMonths' and 'addYears') that adjust a date by a particular
// number of months or years: the end-of-month convention and the
// non-end-of-month convention.  The difference between the two conventions is
// that the end-of-month convention adjusts the resulting date to the end of
// the month if the original date is the last day of the month, while the
// non-end-of-month convention does not perform this adjustment.
//
// For example, if we add 3 months to February 28, 2013 using the
// non-end-of-month convention, then the resulting date will be May 28, 2013.
// If we do the same operation except using the end-of-month convention, then
// the resulting date will be May 31, 2013.
//
// More formal definitions of the two conventions are provided below:
//
//: The End-of-Month Convention:
//:     If the original date to be adjusted is the last day of a month, or if
//:     the day of the month of the original date does not exist in the
//:     resulting date, then adjust the resulting date to be the last day of
//:     the month.
//:
//: The Non-End-of-Month Convention:
//:     If the day of the month of the original date does not exist in the
//:     resulting date, then adjust the resulting date to be the last day of
//:     the month.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Schedule Generation
/// - - - - - - - - - - - - - - -
// Suppose that given a starting date in the "YYYYMMDD" format, we want to
// generate a schedule for an event that occurs on the same day of the month
// for 12 months.
//
// First, we use the 'bdlt::DateUtil::convertFromYYYYMMDD' function to convert
// the integer into a 'bdlt::Date':
//..
//  const int startingDateYYYYMMDD = 20130430;
//
//  bdlt::Date date;
//  int rc = bdlt::DateUtil::convertFromYYYYMMDD(&date, startingDateYYYYMMDD);
//  assert(0 == rc);
//..
// Now, we use the 'addMonthsEom' function to generate the schedule.  Note that
// 'addMonthsEom' adjusts the resulting date to be the last day of the month if
// the original date is the last day of the month, while 'addMonthsNoEom' does
// not make this adjustment.
//..
//  bsl::vector<bdlt::Date> schedule;
//  schedule.push_back(date);
//
//  for (int i = 1; i < 12; ++i) {
//      schedule.push_back(bdlt::DateUtil::addMonthsEom(date, i));
//  }
//..
// Finally, we print the generated schedule to the console and observe the
// output:
//..
//  bsl::copy(schedule.begin(),
//            schedule.end(),
//            bsl::ostream_iterator<bdlt::Date>(bsl::cout, "\n"));
//
//  // Expected output on the console:
//  //
//  //   30APR2013
//  //   31MAY2013
//  //   30JUN2013
//  //   31JUL2013
//  //   31AUG2013
//  //   30SEP2013
//  //   31OCT2013
//  //   30NOV2013
//  //   31DEC2013
//  //   31JAN2014
//  //   28FEB2014
//  //   31MAR2014
//..
// Notice that the dates have been adjusted to the end of the month.  If we had
// used 'addMonthsNoEom' instead of 'addMonthsEom', this adjustment would not
// have occurred.

#include <bdlscm_version.h>

#include <bdlt_date.h>
#include <bdlt_dayofweek.h>
#include <bdlt_serialdateimputil.h>

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

namespace BloombergLP {
namespace bdlt {

                             // ===============
                             // struct DateUtil
                             // ===============

struct DateUtil {
    // This 'struct' provides a namespace for utility functions that provide
    // non-primitive operations on dates.

  private:
    // PRIVATE CLASS METHODS
    static Date addYearsEomEndOfFebruary(const Date& original, int numYears);
        // Return the date that is the specified 'numYears' from the specified
        // 'original' date (which must be either the 28th or 29th of February),
        // adjusted as necessary according to the following (end-of-month)
        // rules: (1) if 'original' is the last day of a month, adjust the
        // result to be the last day of the month, and (2) if the day of the
        // month in 'original' does not exist in the month of the result (e.g.,
        // February 29, 2001), move the resulting date to the last day of the
        // month.  The behavior is undefined unless 'original' is either the
        // 28th or 29th of February, and the resulting date results in a valid
        // 'Date' value.  Note that 'numYears' may be negative.

  public:
    // CLASS METHODS
    static Date addMonths(const Date& original, int numMonths, bool eomFlag);
        // Return the date that is the specified 'numMonths' from the specified
        // 'original' date, adjusted as necessary according to the specified
        // 'eomFlag' (end-of-month flag).  If 'eomFlag' is 'true' and
        // 'original' is the last day of the month, then adjust the result to
        // be the last day of the month; if 'eomFlag' is 'false', then no such
        // adjustment is performed.  In any case, if the day of the month in
        // 'original' does not exist in the month of the result (e.g., February
        // 29, 2001), move the resulting date to the last day of the month.
        // The behavior is undefined unless the operation results in a valid
        // 'Date' value.  Note that 'numMonths' may be negative.

    static Date addMonthsEom(const Date& original, int numMonths);
        // Return the date that is the specified 'numMonths' from the specified
        // 'original' date, adjusted as necessary according to the following
        // (end-of-month) rules: (1) if 'original' is the last day of a month,
        // adjust the result to be the last day of the month, and (2) if the
        // day of the month in 'original' does not exist in the month of the
        // result (e.g., February 30), move the resulting date to the last day
        // of the month.  The behavior is undefined unless the operation
        // results in a valid 'Date' value.  Note that 'numMonths' may be
        // negative.

    static Date addMonthsNoEom(const Date& original, int numMonths);
        // Return the date that is the specified 'numMonths' from the specified
        // 'original' date, adjusted as necessary according to the following
        // (non-end-of-month) rule: if the day of the month in 'original' does
        // not exist in the month of the result (e.g., February 29, 2001), move
        // the resulting date to the last day of the month.  The behavior is
        // undefined unless the operation results in a valid 'Date' value.
        // Note that 'numMonths' may be negative.

    static Date addYears(const Date& original, int numYears, bool eomFlag);
        // Return the date that is the specified 'numYears' from the specified
        // 'original' date, adjusted as necessary according to the specified
        // 'eomFlag' (end-of-month flag).  If 'eomFlag' is 'true' and
        // 'original' is the last day of the month, then adjust the result to
        // be the last day of the month; if 'eomFlag' is 'false', then no such
        // adjustment is performed.  In any case, if the day of the month in
        // 'original' does not exist in the month of the result (e.g., February
        // 29, 2001), move the resulting date to the last day of the month.
        // The behavior is undefined unless the operation results in a valid
        // 'Date' value.  Note that 'numYears' may be negative.

    static Date addYearsEom(const Date& original, int numYears);
        // Return the date that is the specified 'numYears' from the specified
        // 'original' date, adjusted as necessary according to the following
        // (end-of-month) rules: (1) if 'original' is the last day of a month,
        // adjust the result to be the last day of the month, and (2) if the
        // day of the month in 'original' does not exist in the month of the
        // result (e.g., February 29, 2001), move the resulting date to the
        // last day of the month.  The behavior is undefined unless the
        // operation results in a valid 'Date' value.  Note that 'numYears' may
        // be negative.

    static Date addYearsNoEom(const Date& original, int numYears);
        // Return the date that is the specified 'numYears' from the specified
        // 'original' date, adjusted as necessary according to the following
        // (non-end-of-month) rule: if the day of the month in 'original' does
        // not exist in the month of the result (e.g., February 30), move the
        // resulting date to the last day of the month.  The behavior is
        // undefined unless the operation results in a valid 'Date' value.
        // Note that 'numYears' may be negative.

    static int convertFromYYYYMMDD(Date *result, int yyyymmddValue);
        // Load, into the specified 'result', the 'Date' value represented by
        // the specified 'yyyymmddValue' in the "YYYYMMDD" format.  Return 0 on
        // success, and a non-zero value, with no effect on 'result', if
        // 'yyyymmddValue' does not represent a valid 'Date'.

    static Date convertFromYYYYMMDDRaw(int yyyymmddValue);
        // Return the 'Date' value represented by the specified 'yyyymmddValue'
        // in the "YYYYMMDD" format.  The behavior is undefined unless
        // 'yyyymmddValue' represents a valid 'Date'.

    static int convertToYYYYMMDD(const Date& date);
        // Return the integer value in the "YYYYMMDD" format that represents
        // the specified 'date'.

    static Date earliestDayOfWeekInMonth(int             year,
                                         int             month,
                                         DayOfWeek::Enum dayOfWeek);
        // Return the earliest date in the specified 'month' of the specified
        // 'year' that falls on the specified 'dayOfWeek'.  The behavior is
        // undefined unless '1 <= year <= 9999' and '1 <= month <= 12'.

    static bool isValidYYYYMMDD(int yyyymmddValue);
        // Return 'true' if the specified 'yyyymmddValue' represents a valid
        // 'Date' value in the "YYYYMMDD" format, and 'false' otherwise.

    static Date lastDayInMonth(int year, int month);
        // Return the latest date in the specified 'month' of the specified
        // 'year'.  The behavior is undefined unless '1 <= year <= 9999' and
        // '1 <= month <= 12'.

    static Date lastDayOfWeekInMonth(int             year,
                                     int             month,
                                     DayOfWeek::Enum dayOfWeek);
        // Return the latest date in the specified 'month' of the specified
        // 'year' that falls on the specified 'dayOfWeek'.  The behavior is
        // undefined unless '1 <= year <= 9999' and '1 <= month <= 12'.

    static Date nextDayOfWeek(DayOfWeek::Enum dayOfWeek, const Date& date);
        // Return the first date *after* the specified 'date' that falls on the
        // specified 'dayOfWeek'.  The behavior is undefined unless the
        // resulting date is no later than 9999/12/31.

    static Date nextDayOfWeekInclusive(DayOfWeek::Enum dayOfWeek,
                                       const Date&     date);
        // Return the first date *on* or *after* the specified 'date' that
        // falls on the specified 'dayOfWeek'.  The behavior is undefined
        // unless the resulting date is no later than 9999/12/31.

    static Date nthDayOfWeekInMonth(int             year,
                                    int             month,
                                    DayOfWeek::Enum dayOfWeek,
                                    int             n);
        // Return the date in the specified 'month' of the specified 'year'
        // corresponding to the specified 'n'th occurrence of the specified
        // 'dayOfWeek'.  If 'n < 0', return the date corresponding to the
        // '-n'th occurrence of the 'dayOfWeek' counting from the end of the
        // 'month' towards the first of the 'month'.  If '5 == n' and a result
        // cannot be found in 'month', then return the date of the first
        // 'dayOfWeek' in the following month.  If '-5 == n' and a result
        // cannot be found in 'month', then return the date of the last
        // 'dayOfWeek' in the previous month.  The behavior is undefined unless
        // '1 <= year <= 9999', '1 <= month <= 12', 'n != 0', '-5 <= n <= 5',
        // and the resulting date is neither earlier than 0001/01/01 nor later
        // than 9999/12/31.
        //
        // For example:
        //..
        //  nthDayOfWeekInMonth(2004, 11, DayOfWeek::e_THURSDAY, 4);
        //..
        // returns November 25, 2004, the fourth Thursday in November, 2004.

    static Date previousDayOfWeek(DayOfWeek::Enum dayOfWeek, const Date& date);
        // Return the last date *before* the specified 'date' that falls on the
        // specified 'dayOfWeek'.  The behavior is undefined unless the
        // resulting date is no earlier than 1/1/1.

    static Date previousDayOfWeekInclusive(DayOfWeek::Enum dayOfWeek,
                                           const Date&     date);
        // Return the last date *on* or *before* the specified 'date' that
        // falls on the specified 'dayOfWeek'.  The behavior is undefined
        // unless the resulting date is no earlier than 1/1/1.
};

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

                             // ---------------
                             // struct DateUtil
                             // ---------------

// CLASS METHODS
inline
Date DateUtil::addMonths(const Date& original, int numMonths, bool eomFlag)
{

    return eomFlag ? addMonthsEom(original, numMonths)
                   : addMonthsNoEom(original, numMonths);
}

inline
Date DateUtil::addYears(const Date& original, int numYears, bool eomFlag)
{

    return eomFlag ? addYearsEom(original, numYears)
                   : addYearsNoEom(original, numYears);
}

inline
Date DateUtil::addYearsEom(const Date& original, int numYears)
{
    BSLS_REVIEW(   1 <= original.year() + numYears);
    BSLS_REVIEW(9999 >= original.year() + numYears);

    if (2 == original.month() && 28 <= original.day()) {
        return addYearsEomEndOfFebruary(original, numYears);          // RETURN
    }
    return Date(original.year() + numYears, original.month(), original.day());
}

inline
Date DateUtil::addYearsNoEom(const Date& original, int numYears)
{
    BSLS_REVIEW(   1 <= original.year() + numYears);
    BSLS_REVIEW(9999 >= original.year() + numYears);

    const int newYear = original.year() + numYears;

    if (2 == original.month() && 29 == original.day()) {
        return Date(newYear,
                    original.month(),
                    SerialDateImpUtil::isLeapYear(newYear) ? 29 : 28);
                                                                      // RETURN

    }
    return Date(newYear, original.month(), original.day());
}

inline
int DateUtil::convertFromYYYYMMDD(Date *result, int yyyymmddValue)
{
    BSLS_REVIEW(result);

    if (!isValidYYYYMMDD(yyyymmddValue)) {
        return 1;                                                     // RETURN
    }
    *result = convertFromYYYYMMDDRaw(yyyymmddValue);

    return 0;
}

inline
Date DateUtil::convertFromYYYYMMDDRaw(int yyyymmddValue)
{
    BSLS_ASSERT_SAFE(isValidYYYYMMDD(yyyymmddValue));

    return Date(yyyymmddValue / 10000,
                (yyyymmddValue / 100) % 100,
                yyyymmddValue % 100);
}

inline
int DateUtil::convertToYYYYMMDD(const Date& date)
{
    return date.year() * 10000 + date.month() * 100 + date.day();
}

inline
Date DateUtil::earliestDayOfWeekInMonth(int             year,
                                        int             month,
                                        DayOfWeek::Enum dayOfWeek)
{
    BSLS_ASSERT_SAFE(1 <= year);   BSLS_ASSERT_SAFE(year  <= 9999);
    BSLS_ASSERT_SAFE(1 <= month);  BSLS_ASSERT_SAFE(month <= 12);

    return nextDayOfWeekInclusive(dayOfWeek, Date(year, month, 1));
}

inline
bool DateUtil::isValidYYYYMMDD(int yyyymmddValue)
{
    const int day    = yyyymmddValue % 100;
    yyyymmddValue   /= 100;
    const int month  = yyyymmddValue % 100;

    return SerialDateImpUtil::isValidYearMonthDay(yyyymmddValue / 100,
                                                  month,
                                                  day);
}

inline
Date DateUtil::lastDayInMonth(int year, int month)
{
    BSLS_REVIEW(1 <= year);   BSLS_REVIEW(year  <= 9999);
    BSLS_REVIEW(1 <= month);  BSLS_REVIEW(month <= 12);

    return Date(year,
                month,
                SerialDateImpUtil::lastDayOfMonth(year, month));
}

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

#endif

// ----------------------------------------------------------------------------
// Copyright 2016 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 ----------------------------------