// bdlt_date.h                                                        -*-C++-*-
#ifndef INCLUDED_BDLT_DATE
#define INCLUDED_BDLT_DATE

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

//@PURPOSE: Provide a value-semantic type to represent dates.
//
//@CLASSES:
//  bdlt::Date: value-semantic date type consistent with the Unix calendar
//
//@SEE_ALSO: bdlt_dayofweek, bdlt_serialdateimputil
//
//@DESCRIPTION: This component defines a value-semantic class, 'bdlt::Date',
// capable of representing any valid date that is consistent with the Unix
// (POSIX) calendar restricted to the years 1 through 9999 (inclusive):
//..
//  http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cal.html
//..
// "Actual" (i.e., natural) day and date calculations are supported directly by
// 'bdlt::Date' and its associated free operators.  Calculations involving
// business days (or holidays), and day-count conventions (e.g., "ISMA30360"),
// can be found elsewhere.
//
// See 'bdlt_calendar' and the 'bbldc' day-count convention package.
//
///Valid Date Values and Their Representations
///-------------------------------------------
// A 'bdlt::Date' object *always* represents a valid date value as defined by
// the standard Unix calendar.  The value of a 'bdlt::Date' object can be
// expressed in the interface as either '(year, month, day)', the canonical
// representation of dates, or '(year, dayOfYear)'.  For example,
// '(1959, 3, 8)' represents the same valid 'bdlt::Date' value as '(1959, 67)'
// because the 8th day of the 3rd month of the year 1959 is also the 67th day
// of the year 1959.
//
// Of course, not all combinations of '(year, month, day)' and
// '(year, dayOfYear)' constitute valid values for 'bdlt::Date' objects.  A
// '(year, dayOfYear)' pair does *not* represent a valid 'bdlt::Date' value
// unless '1 <= year <= 9999' and '1 <= dayOfYear <= 366'.  Additionally, if
// 'year' is not a leap year, then the representation is not valid unless
// '1 <= dayOfYear <= 365'.
//
// In a leap year, February has 29 days instead of the usual 28.  (Thus, leap
// years have 366 days instead of the usual 365.)  Prior to 1752, the Unix
// calendar follows the convention of the Julian calendar: every year divisible
// by 4 is a leap year.  After 1752, the Unix calendar follows the (more
// accurate) Gregorian calendar: a year is leap year if it is divisible by 4,
// but *not* divisible by 100, *unless* it is *also* divisible by 400.  Note
// that 1752 is the year that Britain and its empire (including the colonies
// that later became the United States) switched from the Julian to the
// Gregorian calendar.  See:
//..
//  http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar
//  https://en.wikipedia.org/wiki/Calendar_(New_Style)_Act_1750
//..
// Moreover, the Unix calendar lacks the dates '[3 .. 13]' in September 1752,
// days that were dropped to align British dates with those used on the
// European continent.  Thus:
//..
//  assert(++bdlt::Date(1752, 9, 2) ==   bdlt::Date(1752, 9, 14));
//  assert(  bdlt::Date(1752, 9, 2) == --bdlt::Date(1752, 9, 14));
//..
// Note that two 'static' (class) methods:
//..
//  bool isValidYearDay(int year, int dayOfYear);
//  bool isValidYearMonthDay(int year, int month, int day);
//..
// are provided within 'bdlt::Date' to indicate whether the given pair or
// triple of integers would represent a valid 'bdlt::Date' value (e.g., prior
// to using them to construct a 'bdlt::Date' object).
//
///Ensuring 'bdlt::Date' Validity
///------------------------------
// Note that it is incumbent on the client of a 'bdlt::Date' object never to
// cause it, directly or indirectly, to hold an invalid value, which can occur
// only by violating explicitly stated preconditions.  For example, invoking
// 'operator++' on a date object that represents the valid 'bdlt::Date' value
// 9999/12/31 (December 31, 9999) is a contract violation that can lead to
// undefined behavior.  Similarly, attempting to set the value of an existing
// date using the 'setYearMonthDay' manipulator such that invoking
// 'isValidYearMonthDay' would return 'false' on the same '(year, month, day)'
// arguments is another contract violation.
//
// When setting a 'bdlt::Date' object to a particular value, there are two
// forms of methods provided for both of the '(year, month, day)' and
// '(year, dayOfYear)' representations of date values.  When you are *certain*
// that the value you are trying to set is valid, either of the following two
// runtime-efficient methods can be used safely:
//..
//  void setYearDay(int year, int dayOfYear);
//  void setYearMonthDay(int year, int month, int day);
//..
// If, however, the integral date attributes at hand are not known to represent
// a valid date, they must first be validated, e.g., by calling one of the two
// 'static' 'isValid*' methods, or by calling the appropriate set method having
// the 'IfValid' suffix, which will always verify validity before either
// setting the value and returning 0, or returning a non-zero status with no
// effect on the object:
//..
//  int setYearDayIfValid(int year, int dayOfYear);
//  int setYearMonthDayIfValid(int year, int month, int day);
//..
// Note that if the value is "known" to be valid, and these latter 'IfValid'
// variants are called without checking their return status, we run the risk of
// a "double fault" in that if the value is not actually valid, there is no way
// for a robust implementation (such as this one) to check for the error in a
// defensive (e.g., "DEBUG" or "SAFE") build mode.
//
///BDEX Compatibility with Legacy POSIX-Based Date
///-----------------------------------------------
// The version 1 format supported by 'bdlt::Date' for BDEX streaming is
// expressly intended for maintaining some degree of "compatibility" with
// versions of this date class that are built to use the proleptic Gregorian
// calendar.
//
// !WARNING!: Use of the proleptic Gregorian version of this class is
// *disallowed* in Bloomberg code.
//
// Over the range of dates supported by 'bdlt::Date'
// ('[0001JAN01 .. 9999DEC31]'), the proleptic Gregorian calendar (used by
// 'bdlt::Date') has two fewer days than 'cal', and some dates that exist in
// one calendar do not exist in the other; therefore, true compatibility is not
// possible.  The compatibility guaranteed by BDEX streaming version 1 is such
// that all dates in the range '[1752SEP14 .. 9999DEC31]', as well as the
// default value ('0001JAN01'), can be successfully exchanged, via BDEX,
// between 'bdlt::Date' classes built to use the POSIX calendar and those built
// to use the proleptic Gregorian calendar.
//
///ISO Standard Text Representation
///--------------------------------
// A common standard text representation of a date and time value is described
// by ISO 8601.  BDE provides the 'bdlt_iso8601util' component for conversion
// to and from the standard ISO8601 format.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Use of 'bdlt::Date'
/// - - - - - - - - - - - - - - - - - -
// The following snippets of code illustrate how to create and use a
// 'bdlt::Date' object.
//
// First, we create a default date 'd1':
//..
//  bdlt::Date d1;           assert(   1 == d1.year());
//                           assert(   1 == d1.month());
//                           assert(   1 == d1.day());
//..
// Next, we set 'd1' to July 4, 1776:
//..
//  d1.setYearMonthDay(1776, 7, 4);
//                           assert(1776 == d1.year());
//                           assert(   7 == d1.month());
//                           assert(   4 == d1.day());
//..
// We can also use 'setYearMonthDayIfValid' if we are not sure whether a
// particular year/month/day combination constitutes a valid 'bdlt::Date'.  For
// example, if we want to set 'd1' to '1900/02/29', and it turns out that year
// 1900 was not a leap year (it wasn't), there will be no effect on the current
// value of the object:
//..
//  int ret = d1.setYearMonthDayIfValid(1900, 2, 29);
//                           assert(   0 != ret);         // 1900 not leap year
//                           assert(1776 == d1.year());   // no effect on 'd1'
//                           assert(   7 == d1.month());
//                           assert(   4 == d1.day());
//..
// Then, from 'd1', we can determine the day of the year, and the day of the
// week, of July 4, 1776:
//..
//  int dayOfYear = d1.dayOfYear();
//                           assert( 186 == dayOfYear);
//
//  bdlt::DayOfWeek::Enum dayOfWeek = d1.dayOfWeek();
//                           assert(bdlt::DayOfWeek::e_THU == dayOfWeek);
//..
// Next, we create a 'bdlt::Date' object, 'd2', using the year/day-of-year
// representation for dates:
//..
//  bdlt::Date d2(1776, dayOfYear);
//                           assert(1776 == d2.year());
//                           assert( 186 == d2.dayOfYear());
//                           assert(   7 == d2.month());
//                           assert(   4 == d2.day());
//                           assert(  d1 == d2);
//..
// Then, we add six days to the value of 'd2':
//..
//  d2 += 6;                 assert(1776 == d2.year());
//                           assert(   7 == d2.month());
//                           assert(  10 == d2.day());
//..
// Now, we subtract 'd1' from 'd2', storing the (signed) difference in days
// (a.k.a. *actual* difference) in 'daysDiff':
//..
//  int daysDiff = d2 - d1;  assert(   6 == daysDiff);
//..
// Finally, we stream the value of 'd2' to 'stdout':
//..
//  bsl::cout << d2 << bsl::endl;
//..
// The streaming operator produces:
//..
//  10JUL1776
//..
// on 'stdout'.

#include <bdlscm_version.h>

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

#include <bslmf_integralconstant.h>
#include <bslmf_istriviallycopyable.h>

#include <bslh_hash.h>

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

#include <bsl_iosfwd.h>


namespace BloombergLP {
namespace bdlt {

                                  // ==========
                                  // class Date
                                  // ==========

class Date {
    // This class implements a complex-constrained, value-semantic type for
    // representing dates according to the Unix (POSIX) calendar.  Each object
    // of this class *always* represents a *valid* date value in the range
    // '[0001JAN01 .. 9999DEC31]' inclusive.  The interface of this class
    // supports 'Date' values expressed in terms of either year/month/day (the
    // canonical representation) or year/day-of-year (an alternate
    // representation).  See {Valid Date Values and Their Representations} for
    // details.

    // DATA
    int d_serialDate;  // absolute serial date (1 == 1/1/1, 2 == 1/1/2, ...)

    // FRIENDS
    friend bool operator==(const Date&, const Date&);
    friend bool operator!=(const Date&, const Date&);
    friend bool operator< (const Date&, const Date&);
    friend bool operator<=(const Date&, const Date&);
    friend bool operator>=(const Date&, const Date&);
    friend bool operator> (const Date&, const Date&);
    friend Date operator+(const Date&, int);
    friend Date operator+(int, const Date&);
    friend Date operator-(const Date&, int);
    friend int  operator-(const Date&, const Date&);
    template <class HASHALG>
    friend void hashAppend(HASHALG& hashAlg, const Date&);

  private:
    // PRIVATE CLASS METHODS
    static bool isValidSerial(int serialDate);
        // Return 'true' if the specified 'serialDate' represents a valid value
        // for a 'Date' object, and 'false' otherwise.  'serialDate' represents
        // a valid 'Date' value if it corresponds to a valid date as defined by
        // the Unix (POSIX) calendar confined to the year range '[1 .. 9999]'
        // inclusive, where serial date 1 corresponds to '0001/01/01' and each
        // successive day has a serial date value that is 1 greater than that
        // of the previous day.  See {Valid Date Values and Their
        // Representations} for details.

#ifndef BDE_OPENSOURCE_PUBLICATION
    #ifdef BDE_USE_PROLEPTIC_DATES
    #error 'BDE_USE_PROLEPTIC_DATES' option disallowed for Bloomberg code.
    #endif
#endif

#ifdef BDE_USE_PROLEPTIC_DATES
    static int convertProlepticDateToPosix(int serialDate);
        // Return the serial date in the POSIX calendar having the same
        // year-month-day representation as the specified 'serialDate'
        // represents in the proleptic Gregorian calendar.  The behavior is
        // undefined if 'Date' is using a proleptic Gregorian representation
        // and 'serialDate' has a year-month-day representation earlier than
        // 1752/09/14 that is not 0001/01/01.  Note that {BDEX Compatibility
        // with Legacy POSIX-Based Date} has further details.

    static int convertPosixDateToProleptic(int serialDate);
        // Return the serial date in the proleptic Gregorian calendar having
        // the same year-month-day representation as the specified 'serialDate'
        // represents in the POSIX calendar.  The behavior is undefined if
        // 'Date' is using a proleptic Gregorian representation and
        // 'serialDate' has a year-month-day representation earlier than
        // 1752/09/14 that is not 0001/01/01.  Note that {BDEX Compatibility
        // with Legacy POSIX-Based Date} has further details.
#endif

    // PRIVATE CREATORS
    explicit Date(int serialDate);
        // Create a date initialized with the value indicated by the specified
        // 'serialDate'.  The behavior is undefined unless 'serialDate'
        // represents a valid 'Date' value.

  public:
    // CLASS METHODS
    static bool isValidYearDay(int year, int dayOfYear);
        // Return 'true' if the specified 'year' and 'dayOfYear' represent a
        // valid value for a 'Date' object, and 'false' otherwise.  'year' and
        // 'dayOfYear' represent a valid 'Date' value if they correspond to a
        // valid date as defined by the Unix (POSIX) calendar confined to the
        // year range '[1 .. 9999]' inclusive.  See {Valid Date Values and
        // Their Representations} for details.

    static bool isValidYearMonthDay(int year, int month, int day);
        // Return 'true' if the specified 'year', 'month', and 'day' represent
        // a valid value for a 'Date' object, and 'false' otherwise.  'year',
        // 'month', and 'day' represent a valid 'Date' value if they correspond
        // to a valid date as defined by the Unix (POSIX) calendar confined to
        // the year range '[1 .. 9999]' inclusive.  See {Valid Date Values and
        // Their Representations} for details.

                                  // Aspects

    static int maxSupportedBdexVersion(int versionSelector);
        // Return the maximum valid BDEX format version, as indicated by the
        // specified 'versionSelector', to be passed to the 'bdexStreamOut'
        // method.  Note that it is highly recommended that 'versionSelector'
        // be formatted as "YYYYMMDD", a date representation.  Also note that
        // 'versionSelector' should be a *compile*-time-chosen value that
        // selects a format version supported by both externalizer and
        // unexternalizer.  See the 'bslx' package-level documentation for more
        // information on BDEX streaming of value-semantic types and
        // containers.

    // CREATORS
    Date();
        // Create a 'Date' object having the earliest supported date value,
        // i.e., having a year/month/day representation of '0001/01/01'.

    Date(int year, int dayOfYear);
        // Create a 'Date' object having the value represented by the specified
        // 'year' and 'dayOfYear'.  The behavior is undefined unless 'year' and
        // 'dayOfYear' represent a valid 'Date' value (see 'isValidYearDay').

    Date(int year, int month, int day);
        // Create a 'Date' object having the value represented by the specified
        // 'year', 'month', and 'day'.  The behavior is undefined unless
        // 'year', 'month', and 'day' represent a valid 'Date' value (see
        // 'isValidYearMonthDay').

    Date(const Date& original);
        // Create a 'Date' object having the value of the specified 'original'
        // date.

    ~Date();
        // Destroy this object.

    // MANIPULATORS
    Date& operator=(const Date& rhs);
        // Assign to this object the value of the specified 'rhs' date, and
        // return a reference providing modifiable access to this object.

    Date& operator+=(int numDays);
        // Assign to this object the value that is later by the specified
        // (signed) 'numDays' from its current value, and return a reference
        // providing modifiable access to this object.  The behavior is
        // undefined unless the resulting value falls within the range of dates
        // supported by this class (see 'isValidYearMonthDay').  Note that
        // 'numDays' may be negative.

    Date& operator-=(int numDays);
        // Assign to this object the value that is earlier by the specified
        // (signed) 'numDays' from its current value, and return a reference
        // providing modifiable access to this object.  The behavior is
        // undefined unless the resulting value falls within the range of dates
        // supported by this class (see 'isValidYearMonthDay').  Note that
        // 'numDays' may be negative.

    Date& operator++();
        // Set this object to have the value that is one day later than its
        // current value, and return a reference providing modifiable access to
        // this object.  The behavior is undefined if the year/month/day
        // representation of the current value is '9999/12/31'.

    Date& operator--();
        // Set this object to have the value that is one day earlier than its
        // current value, and return a reference providing modifiable access to
        // this object.  The behavior is undefined if the year/month/day
        // representation of the current value is '0001/01/01'.

    int addDaysIfValid(int numDays);
        // Set this object to have the value that is later by the specified
        // (signed) 'numDays' from its current value, if the resulting value
        // falls within the range of dates supported by this class (see
        // 'isValidYearMonthDay').  Return 0 on success, and a non-zero value
        // (with no effect) otherwise.  Note that 'numDays' may be negative.

    void setYearDay(int year, int dayOfYear);
        // Set this object to have the value represented by the specified
        // 'year' and 'dayOfYear'.  The behavior is undefined unless 'year' and
        // 'dayOfYear' represent a valid 'Date' value (see 'isValidYearDay').

    int setYearDayIfValid(int year, int dayOfYear);
        // Set this object to have the value represented by the specified
        // 'year' and 'dayOfYear' if they comprise a valid 'Date' value (see
        // 'isValidYearDay').  Return 0 on success, and a non-zero value (with
        // no effect) otherwise.

    void setYearMonthDay(int year, int month, int day);
        // Set this object to have the value represented by the specified
        // 'year', 'month', and 'day'.  The behavior is undefined unless
        // 'year', 'month', and 'day' represent a valid 'Date' value (see
        // 'isValidYearMonthDay').

    int setYearMonthDayIfValid(int year, int month, int day);
        // Set this object to have the value represented by the specified
        // 'year', 'month', and 'day' if they comprise a valid 'Date' value
        // (see 'isValidYearMonthDay').  Return 0 on success, and a non-zero
        // value (with no effect) otherwise.

                                  // Aspects

    template <class STREAM>
    STREAM& bdexStreamIn(STREAM& stream, int version);
        // Assign to this object the value read from the specified input
        // 'stream' using the specified 'version' format, and return a
        // reference to 'stream'.  If 'stream' is initially invalid, this
        // operation has no effect.  If 'version' is not supported, this object
        // is unaltered and 'stream' is invalidated, but otherwise unmodified.
        // If 'version' is supported but 'stream' becomes invalid during this
        // operation, this object has an undefined, but valid, state.  Note
        // that no version is read from 'stream'.  See the 'bslx' package-level
        // documentation for more information on BDEX streaming of
        // value-semantic types and containers.

    // ACCESSORS
    int day() const;
        // Return the day of the month in the range '[1 .. 31]' of this date.

    DayOfWeek::Enum dayOfWeek() const;
        // Return the day of the week in the range
        // '[DayOfWeek::e_SUN .. DayOfWeek::e_SAT]' of this date.

    int dayOfYear() const;
        // Return the day of the year in the range '[1 .. 366]' of this date.

    void getYearDay(int *year, int *dayOfYear) const;
        // Load, into the specified 'year' and 'dayOfYear', the respective
        // 'year' and 'dayOfYear' attribute values of this date.

    void getYearMonthDay(int *year, int *month, int *day) const;
        // Load, into the specified 'year', 'month', and 'day', the respective
        // 'year', 'month', and 'day' attribute values of this date.

    int month() const;
        // Return the month of the year in the range '[1 .. 12]' of this date.

    MonthOfYear::Enum monthOfYear() const;
        // Return the month of the year in the range
        // '[MonthOfYear::e_JAN .. MonthOfYear::e_DEC]' of this date.

    int year() const;
        // Return the year in the range '[1 .. 9999]' of this date.

                                  // Aspects

    template <class STREAM>
    STREAM& bdexStreamOut(STREAM& stream, int version) const;
        // Write the value of this object, using the specified 'version'
        // format, to the specified output 'stream', and return a reference to
        // 'stream'.  If 'stream' is initially invalid, this operation has no
        // effect.  If 'version' is not supported, 'stream' is invalidated, but
        // otherwise unmodified.  Note that 'version' is not written to
        // 'stream'.  See the 'bslx' package-level documentation for more
        // information on BDEX streaming of value-semantic types and
        // containers.

    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
        // Write the value of this object to the specified output 'stream' in a
        // human-readable format, and return a reference to 'stream'.
        // Optionally specify an initial indentation 'level', whose absolute
        // value is incremented recursively for nested objects.  If 'level' is
        // specified, optionally specify 'spacesPerLevel', whose absolute value
        // indicates the number of spaces per indentation level for this and
        // all of its nested objects.  If 'level' is negative, suppress
        // indentation of the first line.  If 'spacesPerLevel' is negative,
        // format the entire output on one line, suppressing all but the
        // initial indentation (as governed by 'level').  If 'stream' is not
        // valid on entry, this operation has no effect.  Note that this
        // human-readable format is not fully specified, and can change without
        // notice.

#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation

    // DEPRECATED METHODS
    static bool isValid(int year, int dayOfYear);
        // !DEPRECATED!: Use 'isValidYearDay' instead.
        //
        // Return 'true' if the specified 'year' and 'dayOfYear' represent a
        // valid value for a 'Date' object, and 'false' otherwise.  'year' and
        // 'dayOfYear' represent a valid 'Date' value if they correspond to a
        // valid date as defined by the Unix (POSIX) calendar confined to the
        // year range '[1 .. 9999]' inclusive.  See {Valid Date Values and
        // Their Representations} for details.

    static bool isValid(int year, int month, int day);
        // !DEPRECATED!: Use 'isValidYearMonthDay' instead.
        //
        // Return 'true' if the specified 'year', 'month', and 'day' represent
        // a valid value for a 'Date' object, and 'false' otherwise.  'year',
        // 'month', and 'day' represent a valid 'Date' value if they correspond
        // to a valid date as defined by the Unix (POSIX) calendar confined to
        // the year range '[1 .. 9999]' inclusive.  See {Valid Date Values and
        // Their Representations} for details.

    static int maxSupportedBdexVersion();
        // !DEPRECATED!: Use 'maxSupportedBdexVersion(int)' instead.
        //
        // Return the most current BDEX streaming version number supported by
        // this class.

#endif  // BDE_OPENSOURCE_PUBLICATION -- pending deprecation
#ifndef BDE_OMIT_INTERNAL_DEPRECATED  // BDE2.22
    static int maxSupportedVersion();
        // !DEPRECATED!: Use 'maxSupportedBdexVersion(int)' instead.
        //
        // Return the most current BDEX streaming version number supported by
        // this class.

    bsl::ostream& streamOut(bsl::ostream& stream) const;
        // !DEPRECATED!: Use 'print' instead.
        //
        // Write the value of this object to the specified output 'stream' in a
        // single-line format, and return a reference to 'stream'.  If 'stream'
        // is not valid on entry, this operation has no effect.  Note that this
        // human-readable format is not fully specified, can change without
        // notice, and is logically equivalent to:
        //..
        //  print(stream, 0, -1);
        //..

    int validateAndSetYearDay(int year, int dayOfYear);
        // !DEPRECATED!: Use 'setYearDayIfValid' instead.
        //
        // Set this object to have the value represented by the specified
        // 'year' and 'dayOfYear' if they comprise a valid 'Date' value (see
        // 'isValidYearDay').  Return 0 on success, and a non-zero value (with
        // no effect) otherwise.

    int validateAndSetYearMonthDay(int year, int month, int day);
        // !DEPRECATED!: Use 'setYearMonthDayIfValid' instead.
        //
        // Set this object to have the value represented by the specified
        // 'year', 'month', and 'day' if they comprise a valid 'Date' value
        // (see 'isValidYearMonthDay').  Return 0 on success, and a non-zero
        // value (with no effect) otherwise.

#endif // BDE_OMIT_INTERNAL_DEPRECATED -- BDE2.22

};

// FREE OPERATORS
bool operator==(const Date& lhs, const Date& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' objects have the same
    // value, and 'false' otherwise.  Two 'Date' objects have the same value if
    // each of their 'year', 'month', and 'day' attributes (respectively) have
    // the same value.

bool operator!=(const Date& lhs, const Date& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' objects do not have the
    // same value, and 'false' otherwise.  Two 'Date' objects do not have the
    // same value if any of their 'year', 'month', and 'day' attributes
    // (respectively) do not have the same value.

bsl::ostream& operator<<(bsl::ostream& stream, const Date& date);
    // Write the value of the specified 'date' object to the specified output
    // 'stream' in a single-line format, and return a reference to 'stream'.
    // If 'stream' is not valid on entry, this operation has no effect.  Note
    // that this human-readable format is not fully specified, can change
    // without notice, and is logically equivalent to:
    //..
    //  print(stream, 0, -1);
    //..

bool operator<(const Date& lhs, const Date& rhs);
    // Return 'true' if the specified 'lhs' date is earlier than the specified
    // 'rhs' date, and 'false' otherwise.

bool operator<=(const Date& lhs, const Date& rhs);
    // Return 'true' if the specified 'lhs' date is earlier than or the same as
    // the specified 'rhs' date, and 'false' otherwise.

bool operator>(const Date& lhs, const Date& rhs);
    // Return 'true' if the specified 'lhs' date is later than the specified
    // 'rhs' date, and 'false' otherwise.

bool operator>=(const Date& lhs, const Date& rhs);
    // Return 'true' if the specified 'lhs' date is later than or the same as
    // the specified 'rhs' date, and 'false' otherwise.

Date operator++(Date& date, int);
    // Set the specified 'date' object to have the value that is one day later
    // than its current value, and return the value of 'date' on entry.  The
    // behavior is undefined if the value of 'date' on entry is '9999/12/31'.

Date operator--(Date& date, int);
    // Set the specified 'date' object to have the value that is one day
    // earlier than its current value, and return the value of 'date' on entry.
    // The behavior is undefined if the value of 'date' on entry is
    // '0001/01/01'.

Date operator+(const Date& date,    int         numDays);
Date operator+(int         numDays, const Date& date);
    // Return the date value that is later by the specified (signed) 'numDays'
    // from the specified 'date'.  The behavior is undefined unless the
    // resulting value falls within the range of dates supported by this class
    // (see 'isValidYearMonthDay').  Note that 'numDays' may be negative.

Date operator-(const Date& date, int numDays);
    // Return the date value that is earlier by the specified (signed)
    // 'numDays' from the specified 'date'.  The behavior is undefined unless
    // the resulting value falls within the range of dates supported by this
    // class (see 'isValidYearMonthDay').  Note that 'numDays' may be negative.

int operator-(const Date& lhs, const Date& rhs);
    // Return the (signed) number of days between the specified 'lhs' and 'rhs'
    // dates.  Note that if 'lhs < rhs' the result will be negative.

// FREE FUNCTIONS
template <class HASHALG>
void hashAppend(HASHALG& hashAlg, const Date& object);
    // Pass the specified 'object' to the specified 'hashAlg'.  This function
    // integrates with the 'bslh' modular hashing system and effectively
    // provides a 'bsl::hash' specialization for 'Date'.

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

                                  // ----------
                                  // class Date
                                  // ----------

// PRIVATE CLASS METHODS
inline
bool Date::isValidSerial(int serialDate)
{
    return SerialDateImpUtil::isValidSerial(serialDate);
}


#ifdef BDE_USE_PROLEPTIC_DATES
inline
int Date::convertProlepticDateToPosix(int serialDate)
{
    if (1 != serialDate) { // Preserve the default value.

        serialDate += 2;   // Ensure that serial values for 1752SEP14 and later
                           // dates "align".
    }
    return serialDate;
}

inline
int Date::convertPosixDateToProleptic(int serialDate)
{
    if (serialDate > 3) {
        serialDate -= 2;  // ensure that serial values for 1752SEP14
                          // and later dates "align"
    }
    else if (serialDate > 0) {
        serialDate = 1;   // "fuzzy" default value '[1 .. 3]'
    }
    return serialDate;
}
#endif

// PRIVATE CREATORS
inline
Date::Date(int serialDate)
: d_serialDate(serialDate)
{
    BSLS_REVIEW(Date::isValidSerial(d_serialDate));
}

// CLASS METHODS
inline
bool Date::isValidYearDay(int year, int dayOfYear)
{
    return SerialDateImpUtil::isValidYearDay(year, dayOfYear);
}

inline
bool Date::isValidYearMonthDay(int year, int month, int day)
{
    return SerialDateImpUtil::isValidYearMonthDay(year, month, day);
}

                                  // Aspects

inline
int Date::maxSupportedBdexVersion(int /* versionSelector */)
{
    return 1;
}

// CREATORS
inline
Date::Date()
: d_serialDate(1)
{
}

inline
Date::Date(int year, int dayOfYear)
: d_serialDate(SerialDateImpUtil::ydToSerial(year, dayOfYear))
{
    BSLS_ASSERT_SAFE(isValidYearDay(year, dayOfYear));
}

inline
Date::Date(int year, int month, int day)
: d_serialDate(SerialDateImpUtil::ymdToSerial(year, month, day))
{
    BSLS_ASSERT_SAFE(isValidYearMonthDay(year, month, day));
}

inline
Date::Date(const Date& original)
: d_serialDate(original.d_serialDate)
{
}

inline
Date::~Date()
{
    BSLS_REVIEW(Date::isValidSerial(d_serialDate));
}

// MANIPULATORS
inline
Date& Date::operator=(const Date& rhs)
{
    d_serialDate = rhs.d_serialDate;
    return *this;
}

inline
Date& Date::operator+=(int numDays)
{
    BSLS_REVIEW(Date::isValidSerial(d_serialDate + numDays));

    d_serialDate += numDays;
    return *this;
}

inline
Date& Date::operator-=(int numDays)
{
    BSLS_REVIEW(Date::isValidSerial(d_serialDate - numDays));

    d_serialDate -= numDays;
    return *this;
}

inline
Date& Date::operator++()
{
    BSLS_REVIEW(*this != Date(9999, 12, 31));

    ++d_serialDate;
    return *this;
}

inline
Date& Date::operator--()
{
    BSLS_REVIEW(*this != Date(1, 1, 1));

    --d_serialDate;
    return *this;
}

inline
void Date::setYearDay(int year, int dayOfYear)
{
    BSLS_ASSERT_SAFE(isValidYearDay(year, dayOfYear));

    d_serialDate = SerialDateImpUtil::ydToSerial(year, dayOfYear);
}

inline
int Date::setYearDayIfValid(int year, int dayOfYear)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (isValidYearDay(year, dayOfYear)) {
        setYearDay(year, dayOfYear);
        return k_SUCCESS;                                             // RETURN
    }

    return k_FAILURE;
}

inline
void Date::setYearMonthDay(int year, int month, int day)
{
    BSLS_PRECONDITIONS_BEGIN();
    BSLS_ASSERT_SAFE(isValidYearMonthDay(year, month, day));
    BSLS_PRECONDITIONS_END();

    d_serialDate = SerialDateImpUtil::ymdToSerial(year, month, day);
}

inline
int Date::setYearMonthDayIfValid(int year, int month, int day)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (isValidYearMonthDay(year, month, day)) {
        setYearMonthDay(year, month, day);
        return k_SUCCESS;                                             // RETURN
    }

    return k_FAILURE;
}

                                  // Aspects

template <class STREAM>
STREAM& Date::bdexStreamIn(STREAM& stream, int version)
{
    if (stream) {
        switch (version) { // switch on the schema version
          case 1: {
            int tmpSerialDate = 0;

            stream.getInt24(tmpSerialDate);

#ifdef BDE_USE_PROLEPTIC_DATES
            tmpSerialDate = convertPosixDateToProleptic(tmpSerialDate);
#endif

            if (stream && Date::isValidSerial(tmpSerialDate)) {
                d_serialDate = tmpSerialDate;
            }
            else {
                stream.invalidate();
            }
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }

    return stream;
}

// ACCESSORS
inline
int Date::day() const
{
    return SerialDateImpUtil::serialToDay(d_serialDate);
}

inline
DayOfWeek::Enum Date::dayOfWeek() const
{
    return static_cast<DayOfWeek::Enum>(
                           SerialDateImpUtil::serialToDayOfWeek(d_serialDate));
}

inline
int Date::dayOfYear() const
{
    return SerialDateImpUtil::serialToDayOfYear(d_serialDate);
}

inline
void Date::getYearDay(int *year, int *dayOfYear) const
{
    BSLS_REVIEW(year);
    BSLS_REVIEW(dayOfYear);

    SerialDateImpUtil::serialToYd(year, dayOfYear, d_serialDate);
}

inline
void Date::getYearMonthDay(int *year, int *month, int *day) const
{
    BSLS_REVIEW(year);
    BSLS_REVIEW(month);
    BSLS_REVIEW(day);

    SerialDateImpUtil::serialToYmd(year, month, day, d_serialDate);
}

inline
int Date::month() const
{
    return SerialDateImpUtil::serialToMonth(d_serialDate);
}

inline
MonthOfYear::Enum Date::monthOfYear() const
{
    return static_cast<MonthOfYear::Enum>(month());
}

inline
int Date::year() const
{
    return SerialDateImpUtil::serialToYear(d_serialDate);
}

                                  // Aspects

template <class STREAM>
STREAM& Date::bdexStreamOut(STREAM& stream, int version) const
{
    if (stream) {
        switch (version) { // switch on the schema version
          case 1: {
#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation
            // Prevent a corrupt date value from escaping the process (whereby
            // it may contaminate a database, for example).

            BSLS_ASSERT_OPT(Date::isValidSerial(d_serialDate));
#endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation

#ifdef BDE_USE_PROLEPTIC_DATES
            stream.putInt24(convertProlepticDateToPosix(d_serialDate));
#else
            stream.putInt24(d_serialDate);
#endif

          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation

// DEPRECATED METHODS
inline
bool Date::isValid(int year, int dayOfYear)
{
    return isValidYearDay(year, dayOfYear);
}

inline
bool Date::isValid(int year, int month, int day)
{
    return isValidYearMonthDay(year, month, day);
}

inline
int Date::maxSupportedBdexVersion()
{
    return maxSupportedBdexVersion(0);
}

#endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation
#ifndef BDE_OMIT_INTERNAL_DEPRECATED  // BDE2.22
inline
int Date::maxSupportedVersion()
{
    return maxSupportedBdexVersion(0);
}

inline
bsl::ostream& Date::streamOut(bsl::ostream& stream) const
{
    return print(stream, 0, -1);
}

inline
int Date::validateAndSetYearDay(int year, int dayOfYear)
{
    return setYearDayIfValid(year, dayOfYear);
}

inline
int Date::validateAndSetYearMonthDay(int year, int month, int day)
{
    return setYearMonthDayIfValid(year, month, day);
}

#endif // BDE_OMIT_INTERNAL_DEPRECATED -- BDE2.22

}  // close package namespace

// FREE OPERATORS
inline
bool bdlt::operator==(const Date& lhs, const Date& rhs)
{
    return lhs.d_serialDate == rhs.d_serialDate;
}

inline
bool bdlt::operator!=(const Date& lhs, const Date& rhs)
{
    return lhs.d_serialDate != rhs.d_serialDate;
}

inline
bsl::ostream& bdlt::operator<<(bsl::ostream& stream, const Date& date)
{
    return date.print(stream, 0, -1);
}

inline
bool bdlt::operator<(const Date& lhs, const Date& rhs)
{
    return lhs.d_serialDate < rhs.d_serialDate;
}

inline
bool bdlt::operator<=(const Date& lhs, const Date& rhs)
{
    return lhs.d_serialDate <= rhs.d_serialDate;
}

inline
bool bdlt::operator>(const Date& lhs, const Date& rhs)
{
    return lhs.d_serialDate > rhs.d_serialDate;
}

inline
bool bdlt::operator>=(const Date& lhs, const Date& rhs)
{
    return lhs.d_serialDate >= rhs.d_serialDate;
}

inline
bdlt::Date bdlt::operator++(Date& date, int)
{
    BSLS_REVIEW(date != Date(9999, 12, 31));

    Date tmp(date);
    ++date;
    return tmp;
}

inline
bdlt::Date bdlt::operator--(Date& date, int)
{
    BSLS_REVIEW(date != Date(1, 1, 1));

    Date tmp(date);
    --date;
    return tmp;
}

inline
bdlt::Date bdlt::operator+(const Date& date, int numDays)
{
    BSLS_REVIEW(Date::isValidSerial(date.d_serialDate + numDays));

    return Date(date.d_serialDate + numDays);
}

inline
bdlt::Date bdlt::operator+(int numDays, const Date& date)
{
    BSLS_REVIEW(Date::isValidSerial(numDays + date.d_serialDate));

    return Date(numDays + date.d_serialDate);
}

inline
bdlt::Date bdlt::operator-(const Date& date, int numDays)
{
    BSLS_REVIEW(Date::isValidSerial(date.d_serialDate - numDays));

    return Date(date.d_serialDate - numDays);
}

inline
int bdlt::operator-(const Date& lhs, const Date& rhs)
{
    return lhs.d_serialDate - rhs.d_serialDate;
}

// FREE FUNCTIONS
template <class HASHALG>
inline
void bdlt::hashAppend(HASHALG& hashAlg, const Date& object)
{
    using ::BloombergLP::bslh::hashAppend;
    hashAppend(hashAlg, object.d_serialDate);
}

}  // close enterprise namespace

namespace bsl {

template <>
struct is_trivially_copyable<BloombergLP::bdlt::Date> : bsl::true_type {
    // This template specialization for 'is_trivially_copyable' indicates that
    // 'Date' is a trivially copyable type.
};

}  // close namespace bsl

#endif

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