// bdlt_datetime.h                                                    -*-C++-*-
#ifndef INCLUDED_BDLT_DATETIME
#define INCLUDED_BDLT_DATETIME

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

//@PURPOSE: Provide a value-semantic type representing both date and time.
//
//@CLASSES:
//  bdlt::Datetime: date and time value (at least microsecond resolution)
//
//@SEE_ALSO: bdlt_date, bdlt_time, bdlt_datetimetz, bdlt_datetimeformatter,
//  bdlt_formatdoc
//
//@DESCRIPTION: This component implements a value-semantic type,
// `bdlt::Datetime`, that represents the composition of a date and a time
// value.  The combined "date+time" value of a `bdlt::Datetime` object is
// expressed textually as "yyyy/mm/dd_hh:mm:ss.ssssss", where "yyyy/mm/dd"
// represents the "date" part of the value and "hh:mm:ss.ssssss" represents the
// "time" part.
//
// In addition to the usual value-semantic complement of methods for getting
// and setting value, the `bdlt::Datetime` class provides methods and operators
// for making relative adjustments to value (`addDays`, `addTime`, `addHours`,
// etc.).  In particular, note that adding units of time to a `bdlt::Datetime`
// object can affect the values of both the time and date parts of the object.
// For example, invoking `addHours(2)` on a `bdlt::Datetime` object whose value
// is "1987/10/03_22:30:00.000000" updates the value to
// "1987/10/04_00:30:00.000000".
//
///Valid `bdlt::Datetime` Values and Their Representations
///-------------------------------------------------------
// The "date" part of a `bdlt::Datetime` value has a range of validity
// identical to a `bdlt::Date` object -- i.e., valid dates (according to the
// Unix [POSIX] calendar) having years in the range `[1 .. 9999]`.  The valid
// time values are `[00:00:00.000000 .. 23:59:59.999999]`.  Furthermore, the
// unset time value (i.e., 24:00:00.000000, corresponding to the default
// constructed value for `bdlt::Time`) is available for every valid date.  Note
// that the supported range of time does *not* allow for the injection of leap
// seconds.  The value "0001/01/01_24:00:00.000000" is the default constructed
// value of `bdlt::Datetime`.
//
// Furthermore, consistent with the `bdlt::Time` type, a `bdlt::Datetime`
// object whose "time" part has the default constructed value, behaves the
// same, with respect to manipulators and (most) free operators, as if the
// "time" part had the value 00:00:00.000000.  As for `bdlt::Time`, the
// behavior of all `bdlt::Datetime` relational comparison operators is
// undefined if the "time" part of either operand is 24:00:00.000000.
// Consequently, `bdlt::Datetime` objects whose "time" part has the default
// constructed value must *not* be used as keys for the standard associative
// containers, since `operator<` is not defined for such objects.
//
///Attributes
///----------
// Conceptually, the two primary attributes of `bdlt::Datetime` are the
// constituent date and time values.  These attributes are given the special
// designation "part" in this component (i.e., the "time" part and the "date"
// part, respectively) to distinguish them from the many other attributes (see
// below) that derive from these two main parts.
// ```
// Name  Related Type  Default          Range
// ----  ------------  ---------------  ------------------------------------
// date  bdlt::Date    0001/01/01       [0001/01/01      .. 9999/12/31]
// time  bdlt::Time    24:00:00.000000  [00:00:00.000000 .. 23:59:59.999999]
// ```
// A `bdlt::Datetime` object can be used in terms of its "date" and "time"
// parts or, if appropriate to an application, the object can be viewed as a
// single, integrated type having the combined individual attributes of date
// and time.  Accessors and manipulators are provided for each of these eight
// (derived) attributes:
// ```
// Name         Type  Default  Range        Constraint
// -----------  ----  -------  -----------  -----------------------------
// year         int    1       [1 .. 9999]  none
// month        int    1       [1 ..   12]  none
// day          int    1       [1 ..   31]  must exist for year and month
// hour         int   24       [0 ..   24]  none
// minute       int    0       [0 ..   59]  must be 0 if '24 == hour'
// second       int    0       [0 ..   59]  must be 0 if '24 == hour'
// millisecond  int    0       [0 ..  999]  must be 0 if '24 == hour'
// microsecond  int    0       [0 ..  999]  must be 0 if '24 == hour'
// ```
// There are two additional "date" part attributes to `bdlt::Datetime`:
// ```
// Name      Type                  Default Range        Constraint
// --------- --------------------- ------- ------------ ----------------------
// dayOfYear int                   1       [  1 .. 366] 366 only on leap years
// dayOfWeek bdlt::DayOfWeek::Enum SAT     [SUN .. SAT] tied to calendar day
// ```
// where `dayOfYear` tracks the value of `year/month/day` (and *vice* *versa*),
// and `dayOfWeek` can be accessed but not explicitly set.
//
///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 Syntax
///- - - - - - - - - - - -
// Values represented by objects of type `bdlt::Datetime` are used widely in
// practice.  The values of the individual attributes resulting from a
// default-constructed `bdlt::Datetime` object, `dt`, are
// "0001/01/01_24:00:00.000000":
// ```
// bdlt::Datetime dt;          assert( 1 == dt.date().year());
//                             assert( 1 == dt.date().month());
//                             assert( 1 == dt.date().day());
//                             assert(24 == dt.hour());
//                             assert( 0 == dt.minute());
//                             assert( 0 == dt.second());
//                             assert( 0 == dt.millisecond());
//                             assert( 0 == dt.microsecond());
// ```
// We can then set `dt` to have a specific value, say, 8:43pm on January 6,
// 2013:
// ```
// dt.setDatetime(2013, 1, 6, 20, 43);
//                             assert(2013 == dt.date().year());
//                             assert(   1 == dt.date().month());
//                             assert(   6 == dt.date().day());
//                             assert(  20 == dt.hour());
//                             assert(  43 == dt.minute());
//                             assert(   0 == dt.second());
//                             assert(   0 == dt.millisecond());
//                             assert(   0 == dt.microsecond());
// ```
// Now suppose we add 6 hours and 9 seconds to this value.  There is more than
// one way to do it:
// ```
// bdlt::Datetime dt2(dt);
// dt2.addHours(6);
// dt2.addSeconds(9);
//                             assert(2013 == dt2.date().year());
//                             assert(   1 == dt2.date().month());
//                             assert(   7 == dt2.date().day());
//                             assert(   2 == dt2.hour());
//                             assert(  43 == dt2.minute());
//                             assert(   9 == dt2.second());
//                             assert(   0 == dt2.millisecond());
//                             assert(   0 == dt2.microsecond());
//
// bdlt::Datetime dt3(dt);
// dt3.addTime(6, 0, 9);
//                             assert(dt2 == dt3);
// ```
// Notice that (in both cases) the date changed as a result of adding time;
// however, changing just the date never affects the time:
// ```
// dt3.addDays(10);
//                             assert(2013 == dt3.date().year());
//                             assert(   1 == dt3.date().month());
//                             assert(  17 == dt3.date().day());
//                             assert(   2 == dt3.hour());
//                             assert(  43 == dt3.minute());
//                             assert(   9 == dt3.second());
//                             assert(   0 == dt3.millisecond());
//                             assert(   0 == dt3.microsecond());
// ```
// We can also add more than a day's worth of time:
// ```
// dt2.addHours(240);
//                             assert(dt3 == dt2);
// ```
// The individual arguments can also be negative:
// ```
// dt2.addTime(-246, 0, -10, 1000);  // -246 h, -10 s, +1000 ms
//                             assert(dt == dt2);
// ```
// Finally, we stream the value of `dt2` to `stdout`:
// ```
// bsl::cout << dt2 << bsl::endl;
// ```
// The streaming operator produces the following output on `stdout`:
// ```
// 06JAN2013_20:43:00.000000
// ```
//
///Example 2: Creating a Schedule of Equal Time Intervals
/// - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Calculations involving date and time values are difficult to get correct
// manually; consequently, people tend to schedule events on natural time
// boundaries (e.g., on the hour) even if that is sub-optimal.  Having a class
// such as `bdlt::Datetime` makes doing date and time calculations trivial.
//
// Suppose one wants to divide into an arbitrary interval such as the time
// between sunset and sunrise into an arbitrary number (say 7) of equal
// intervals (perhaps to use as a duty roster for teams making astronomical
// observations).
//
// First, we create objects containing values for the start and end of the time
// interval:
// ```
// bdlt::Datetime  sunset(2014, 6, 26, 20, 31, 23); // New York City
// bdlt::Datetime sunrise(2014, 6, 27,  5, 26, 51); // New York City
// ```
// Then, we calculate the length of each shift in milliseconds (for good
// precision -- we may be synchronizing astronomical instruments).  Note that
// the difference of `sunrise` and `sunset` creates a temporary
// `bdlt::DatetimeInterval` object:
// ```
// const int                numShifts = 7;
// const bsls::Types::Int64 shiftLengthInMsec
//                                    = (sunrise - sunset).totalMilliseconds()
//                                    / numShifts;
// ```
// Now, we calculate (and print to `stdout`) the beginning and end times for
// each shift:
// ```
// for (int i = 0; i <= numShifts; ++i) {
//     bdlt::Datetime startOfShift(sunset);
//     startOfShift.addMilliseconds(shiftLengthInMsec * i);
//     bsl::cout << startOfShift << bsl::endl;
// }
// ```
// Finally, we observe:
// ```
// 26JUN2014_20:31:23.000000
// 26JUN2014_21:47:52.714000
// 26JUN2014_23:04:22.428000
// 27JUN2014_00:20:52.142000
// 27JUN2014_01:37:21.856000
// 27JUN2014_02:53:51.570000
// 27JUN2014_04:10:21.284000
// 27JUN2014_05:26:50.998000
// ```
// Notice how our objects (since they manage both "date" and "time of day"
// parts of each point in time) seamlessly handle the transition between the
// two days.

#include <bdlscm_version.h>

#include <bdlt_date.h>
#include <bdlt_datetimeimputil.h>
#include <bdlt_datetimeinterval.h>
#include <bdlt_dayofweek.h>
#include <bdlt_time.h>
#include <bdlt_timeunitratio.h>

#include <bdlb_bitutil.h>

#include <bslh_hash.h>

#include <bslmf_integralconstant.h>
#include <bslmf_isbitwisecopyable.h>

#include <bsls_assert.h>
#include <bsls_atomic.h>
#include <bsls_performancehint.h>
#include <bsls_preconditions.h>
#include <bsls_review.h>
#include <bsls_stackaddressutil.h>
#include <bsls_timeinterval.h>
#include <bsls_types.h>

#include <bsl_iosfwd.h>
#include <bsl_cstring.h> // memset
#include <bsl_sstream.h>

namespace BloombergLP {
namespace bdlt {

                               // ==============
                               // class Datetime
                               // ==============

/// This class implements a simply-constrained value-semantic type
/// representing the composition of date and time values.  Valid date values
/// for the "date" part of a `Datetime` object are the same as those defined
/// for `Date` objects; similarly, valid time values for the "time" part of
/// a `Datetime` object are similar to those defined for `Time` objects (but
/// with additional precision).  Relational operators are disallowed on
/// `Datetime` objects whose "time" part has the same value as that of a
/// default constructed `Time` object.
class Datetime {
    // PRIVATE TYPES
    enum {
        k_NUM_TIME_BITS = 37,
        k_DEFAULT_FRACTIONAL_SECOND_PRECISION = 6
    };

  private:
    // CLASS DATA
    static const bsls::Types::Uint64 k_MAX_US_FROM_EPOCH;

    static const bsls::Types::Uint64 k_REP_MASK  = 0x8000000000000000ULL;
    static const bsls::Types::Uint64 k_DATE_MASK = 0xffffffe000000000ULL;
    static const bsls::Types::Uint64 k_TIME_MASK = 0x0000001fffffffffULL;

    static bsls::AtomicInt64 s_invalidRepresentationCount;

    // DATA
    bsls::Types::Uint64 d_value;  // encoded offset from the epoch

    // FRIENDS
    friend DatetimeInterval operator-(const Datetime&, const Datetime&);

    friend bool operator==(const Datetime&, const Datetime&);
    friend bool operator!=(const Datetime&, const Datetime&);
    friend bool operator< (const Datetime&, const Datetime&);
    friend bool operator<=(const Datetime&, const Datetime&);
    friend bool operator> (const Datetime&, const Datetime&);
    friend bool operator>=(const Datetime&, const Datetime&);
    template <class HASHALG>
    friend void hashAppend(HASHALG& hashAlg, const Datetime&);

    // PRIVATE MANIPULATOR

    /// Assign to `d_value` the representation of a datetime such that the
    /// difference between this datetime and the epoch is the specified
    /// `totalMicroseconds`.
    void setMicrosecondsFromEpoch(bsls::Types::Uint64 totalMicroseconds);

    // PRIVATE ACCESSORS

    /// Return the difference, measured in microseconds, between this
    /// datetime value, with 24:00:00.000000 converted to 0:00:00.000000,
    /// and the epoch.
    bsls::Types::Uint64 microsecondsFromEpoch() const;

    /// If `d_value` is a valid representation, return `d_value`.
    /// Otherwise, return the representation of the datetime corresponding
    /// to the datetime implied by assuming the value in `d_value` is the
    /// concatenation of a `Date` and a `Time`, and log or assert the
    /// detection of an invalid date.
    bsls::Types::Uint64 updatedRepresentation() const;

    /// Return `true` if the representation is valid.  Invoke a review
    /// failure notifying of an invalid use of a `bdlt::Datetime` instance
    /// and return `false` if the representation is invalid and
    /// `BSLS_ASSERT_SAFE` is inactive.  The behavior is undefined if the
    /// representation is invalid and `BSLS_ASSERT_SAFE` is active.
    bool validateAndTraceLogRepresentation() const;

  public:
    // CLASS METHODS

    /// Return `true` if the specified `year`, `month`, and `day` attribute
    /// values, and the optionally specified `hour`, `minute`, `second`,
    /// `millisecond`, and `microsecond` attribute values, represent a valid
    /// `Datetime` value, and `false` otherwise.  Unspecified trailing
    /// optional parameters default to 0.  `year`, `month`, `day`, `hour`,
    /// `minute`, `second`, `millisecond`, and `microsecond` attribute
    /// values represent a valid `Datetime` value if
    /// `true == Date::isValidYearMonthDay(year, month, day)`,
    /// `0 <= hour < 24`, `0 <= minute < 60`, `0 <= second < 60`,
    /// `0 <= millisecond < 1000`, and `0 <= microsecond < 1000`.
    /// Additionally, a valid `year`, `month`, `day` with the time portion
    /// equal to 24:00:00.000000 also represents a valid `Datetime` value.
    static bool isValid(int year,
                        int month,
                        int day,
                        int hour = 0,
                        int minute = 0,
                        int second = 0,
                        int millisecond = 0,
                        int microsecond = 0);

                                  // Aspects

    /// 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.
    static int maxSupportedBdexVersion(int versionSelector);

    // CREATORS

    /// Create a `Datetime` object whose "date" and "time" parts have their
    /// respective default-constructed values, "0001/01/01" and
    /// "24:00:00.000000".
    Datetime();

    /// Create a `Datetime` object whose "date" part has the value of the
    /// specified `date` and whose "time" part has the value
    /// "00:00:00.000000".
    Datetime(const Date& date);                                     // IMPLICIT

    /// Create a `Datetime` object whose "date" and "time" parts have the
    /// values of the specified `date` and `time`, respectively.
    Datetime(const Date& date, const Time& time);

    /// Create a `Datetime` object whose "date" part has the value
    /// represented by the specified `year`, `month`, and `day` attributes,
    /// and whose "time" part has the value represented by the optionally
    /// specified `hour`, `minute`, `second`, `millisecond`, and
    /// `microsecond` attributes.  Unspecified trailing optional parameters
    /// default to 0.  The behavior is undefined unless the eight attributes
    /// (collectively) represent a valid `Datetime` value (see `isValid`).
    Datetime(int year,
             int month,
             int day,
             int hour = 0,
             int minute = 0,
             int second = 0,
             int millisecond = 0,
             int microsecond = 0);

    /// Create a `Datetime` object having the value of the specified
    /// `original` object.
    Datetime(const Datetime& original);

    /// Destroy this `Datetime` object.
    //! ~Datetime() = default;

    // MANIPULATORS

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

    /// Add to this object the value of the specified `rhs` object, and
    /// return a reference providing modifiable access to this object.  If
    /// `24 == hour()` on entry, set the `hour` attribute of this object to
    /// 0 before performing the addition.  The behavior is undefined unless
    /// the resulting value is valid for `Datetime` (see `isValid`).
    Datetime& operator+=(const bsls::TimeInterval& rhs);

    /// Subtract from this object the value of the specified `rhs` object,
    /// and return a reference providing modifiable access to this object.
    /// If `24 == hour()` on entry, set the `hour` attribute of this object
    /// to 0 before performing the subtraction.  The behavior is undefined
    /// unless the resulting value is valid for `Datetime` (see `isValid`).
    Datetime& operator-=(const bsls::TimeInterval& rhs);

    /// Add to this object the value of the specified `rhs` object, and
    /// return a reference providing modifiable access to this object.  If
    /// `24 == hour()` on entry, set the `hour` attribute of this object to
    /// 0 before performing the addition.  The behavior is undefined unless
    /// the resulting value is valid for `Datetime` (see `isValid`).
    Datetime& operator+=(const DatetimeInterval& rhs);

    /// Subtract from this object the value of the specified `rhs` object,
    /// and return a reference providing modifiable access to this object.
    /// If `24 == hour()` on entry, set the `hour` attribute of this object
    /// to 0 before performing the subtraction.  The behavior is undefined
    /// unless the resulting value is valid for `Datetime` (see `isValid`).
    Datetime& operator-=(const DatetimeInterval& rhs);

    /// Set the value of this object to a `Datetime` whose "date" part has
    /// the value represented by the specified `date`, and whose "time" part
    /// has the value represented by the optionally specified `hour`,
    /// `minute`, `second`, `millisecond`, and `microsecond` attributes.
    /// Unspecified trailing optional parameters default to 0.  The behavior
    /// is undefined unless the attributes (collectively) represent a valid
    /// `Datetime` value (see `isValid`).
    void setDatetime(const Date& date,
                     int         hour = 0,
                     int         minute = 0,
                     int         second = 0,
                     int         millisecond = 0,
                     int         microsecond = 0);

    /// Set the value of this object to a `Datetime` whose "date" part has
    /// the value represented by the specified `date`, and whose "time" part
    /// has the value represented by the specified `time`.
    void setDatetime(const Date& date, const Time& time);

    /// Set the value of this object to a `Datetime` whose "date" part has
    /// the value represented by the specified `year`, `month`, and `day`
    /// attributes, and whose "time" part has the value represented by the
    /// optionally specified `hour`, `minute`, `second`, `millisecond`, and
    /// `microsecond` attributes.  Unspecified trailing optional parameters
    /// default to 0.  The behavior is undefined unless the eight attributes
    /// (collectively) represent a valid `Datetime` value (see `isValid`).
    void setDatetime(int year,
                     int month,
                     int day,
                     int hour = 0,
                     int minute = 0,
                     int second = 0,
                     int millisecond = 0,
                     int microsecond = 0);

    /// Set the "date" part of this object to have the value represented by
    /// the specified `year`, `month`, and `day` attributes, and set the
    /// "time" part to have the value represented by the optionally
    /// specified `hour`, `minute`, `second`, `millisecond`, and
    /// `microsecond` attributes, if the eight attribute values
    /// (collectively) represent a valid `Datetime` value (see `isValid`).
    /// Unspecified trailing optional parameters default to 0.  Return 0 on
    /// success, and a non-zero value (with no effect) otherwise.
    int setDatetimeIfValid(int year,
                           int month,
                           int day,
                           int hour = 0,
                           int minute = 0,
                           int second = 0,
                           int millisecond = 0,
                           int microsecond = 0);

    /// Set the value of this object to a `Datetime` whose "date" part has
    /// the value represented by the specified `date`, and whose "time" part
    /// has the value represented by the optionally specified `hour`,
    /// `minute`, `second`, `millisecond`, and `microsecond` attributes, if
    /// the attribute values (collectively) represent a valid `Datetime`
    /// value (see `isValid`).  Unspecified trailing optional parameters
    /// default to 0.  Return 0 on success, and a non-zero value (with no
    /// effect) otherwise.
    int setDatetimeIfValid(const Date& date,
                           int         hour = 0,
                           int         minute = 0,
                           int         second = 0,
                           int         millisecond = 0,
                           int         microsecond = 0);

    /// Set the "date" part of this object to have the value of the
    /// specified `date`.  Note that this method has no effect on the "time"
    /// part of this object.
    void setDate(const Date& date);

    /// Set the "date" part of this object to have the value represented by
    /// the specified `year` and `dayOfYear` attribute values.  The behavior
    /// is undefined unless `year` and `dayOfYear` represent a valid `Date`
    /// value (i.e., `true == Date::isValidYearDay(year, dayOfYear)`).  Note
    /// that this method has no effect on the "time" part of this object.
    void setYearDay(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
    /// `Date::isValidYearDay`).  Return 0 on success, and a non-zero value
    /// (with no effect) otherwise.
    int setYearDayIfValid(int year, int dayOfYear);

    /// Set the "date" part of this object to have the value represented by
    /// the specified `year`, `month`, and `day` attribute values.  The
    /// behavior is undefined unless `year`, `month`, and `day` represent a
    /// valid `Date` value (i.e.,
    /// `true == Date::isValidYearMonthDay(year, month, day)`).  Note that
    /// this method has no effect on the "time" part of this object.
    void setYearMonthDay(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 `Date::isValidYearMonthDay`).  Return 0 on success, and a
    /// non-zero value (with no effect) otherwise.
    int setYearMonthDayIfValid(int year, int month, int day);

    /// Set the "time" part of this object to have the value of the
    /// specified `time`.  Note that this method has no effect on the "date"
    /// part of this object.
    void setTime(const Time& time);

    /// Set the "time" part of this object to have the value represented by
    /// the specified `hour` attribute value and the optionally specified
    /// `minute`, `second`, `millisecond`, and `microsecond` attribute
    /// values.  Unspecified trailing optional parameters default to 0.  The
    /// behavior is undefined unless `hour`, `minute`, `second`,
    /// `millisecond`, and `microsecond` represent a valid "time" portion of
    /// a `Datetime` value.  Note that this method has no effect on the
    /// "date" part of this object.
    void setTime(int hour,
                 int minute = 0,
                 int second = 0,
                 int millisecond = 0,
                 int microsecond = 0);

    /// Set the "time" part of this object to have the value represented by
    /// the specified `hour` attribute value and the optionally specified
    /// `minute`, `second`, `millisecond`, and `microsecond` attribute
    /// values if they comprise a valid "time" portion of a `DateTime`
    /// value.  Unspecified trailing optional parameters default to 0.
    /// Return 0 on success, and a non-zero value (with no effect)
    /// otherwise.  Note that this method has no effect on the "date" part
    /// of this object.
    int setTimeIfValid(int hour,
                       int minute = 0,
                       int second = 0,
                       int millisecond = 0,
                       int microsecond = 0);

    /// Set the "hour" attribute of this object to the specified `hour`
    /// value.  If `24 == hour`, set the `minute`, `second`, `millisecond`,
    /// and `microsecond` attributes to 0.  The behavior is undefined
    /// unless `0 <= hour <= 24`.  Note that this method has no effect on
    /// the "date" part of this object.
    void setHour(int hour);

    /// Set the "hour" attribute of this object to the specified `hour`
    /// value if `0 <= hour <= 24`.  If `24 == hour`, set the `minute`,
    /// `second`, `millisecond`, and `microsecond` attributes to 0.  Return
    /// 0 on success, and a non-zero value (with no effect) otherwise.  Note
    /// that this method has no effect on the "date" part of this object.
    int setHourIfValid(int hour);

    /// Set the "minute" attribute of this object to the specified `minute`
    /// value.  If `24 == hour()`, set the `hour` attribute to 0.  The
    /// behavior is undefined unless `0 <= minute <= 59`.  Note that this
    /// method has no effect on the "date" part of this object.
    void setMinute(int minute);

    /// Set the "minute" attribute of this object to the specified `minute`
    /// value if `0 <= minute <= 59`.  If `24 == hour()`, set the `hour`
    /// attribute to 0.  Return 0 on success, and a non-zero value (with no
    /// effect) otherwise.  Note that this method has no effect on the
    /// "date" part of this object.
    int setMinuteIfValid(int minute);

    /// Set the "second" attribute of this object to the specified `second`
    /// value.  If `24 == hour()`, set the `hour` attribute to 0.  The
    /// behavior is undefined unless `0 <= second <= 59`.  Note that this
    /// method has no effect on the "date" part of this object.
    void setSecond(int second);

    /// Set the "second" attribute of this object to the specified `second`
    /// value if `0 <= second <= 59`.  If `24 == hour()`, set the `hour`
    /// attribute to 0.  Return 0 on success, and a non-zero value (with no
    /// effect) otherwise.  Note that this method has no effect on the
    /// "date" part of this object.
    int setSecondIfValid(int second);

    /// Set the "millisecond" attribute of this object to the specified
    /// `millisecond` value.  If `24 == hour()`, set the `hour` attribute to
    /// 0.  The behavior is undefined unless `0 <= millisecond <= 999`.
    /// Note that this method has no effect on the "date" part of this
    /// object.
    void setMillisecond(int millisecond);

    /// Set the "millisecond" attribute of this object to the specified
    /// `millisecond` value if `0 <= millisecond <= 999`.  If
    /// `24 == hour()`, set the `hour` attribute to 0.  Return 0 on success,
    /// and a non-zero value (with no effect) otherwise.  Note that this
    /// method has no effect on the "date" part of this object.
    int setMillisecondIfValid(int millisecond);

    /// Set the "microsecond" attribute of this object to the specified
    /// `microsecond` value.  If `24 == hour()`, set the `hour` attribute to
    /// 0.  The behavior is undefined unless `0 <= microsecond <= 999`.
    /// Note that this method has no effect on the "date" part of this
    /// object.
    void setMicrosecond(int microsecond);

    /// Set the "microsecond" attribute of this object to the specified
    /// `microsecond` value if `0 <= microsecond <= 999`.  If
    /// `24 == hour()`, set the `hour` attribute to 0.  Return 0 on success,
    /// and a non-zero value (with no effect) otherwise.  Note that this
    /// method has no effect on the "date" part of this object.
    int setMicrosecondIfValid(int microsecond);

    /// Add the specified number of `days` to the value of this object.
    /// Return a reference providing modifiable access to this object.  The
    /// behavior is undefined unless the resulting value is in the valid
    /// range for a `Datetime` object.  Note that this method has no effect
    /// on the "time" part of this object.  Also note that `days` may be
    /// positive, 0, or negative.
    Datetime& addDays(int days);

    /// Add the specified number of `days` to the value of this object, if
    /// the resulting value is in the valid range for a `Datetime` object.
    /// Return 0 on success, and a non-zero value (with no effect)
    /// otherwise.  Note that this method has no effect on the "time" part
    /// of this object.  Also note that `days` may be positive, 0, or
    /// negative.
    int addDaysIfValid(int days);

    /// Add the specified number of `hours`, and the optionally specified
    /// number of `minutes`, `seconds`, `milliseconds`, and `microseconds`
    /// to the value of this object, adjusting the "date" part of this
    /// object accordingly.  Unspecified trailing optional parameters
    /// default to 0.  Return a reference providing modifiable access to
    /// this object.  If `24 == hour()` on entry, set the `hour` attribute
    /// to 0 before performing the addition.  The behavior is undefined
    /// unless the resulting value is in the valid range for a `Datetime`
    /// object.  Note that each argument independently may be positive,
    /// negative, or 0.
    Datetime& addTime(bsls::Types::Int64 hours,
                 bsls::Types::Int64 minutes = 0,
                 bsls::Types::Int64 seconds = 0,
                 bsls::Types::Int64 milliseconds = 0,
                 bsls::Types::Int64 microseconds = 0);

    /// Add the specified number of `hours`, and the optionally specified
    /// number of `minutes`, `seconds`, `milliseconds`, and `microseconds`
    /// to the value of this object, adjusting the "date" part of this
    /// object accordingly, if the resulting value is in the valid range for
    /// a `Datetime` object.  Unspecified trailing optional parameters
    /// default to 0.  If `24 == hour()` on entry, set the `hour` attribute
    /// to 0 before performing the addition.  Return 0 on success, and a
    /// non-zero value (with no effect) otherwise.  Note that each argument
    /// independently may be positive, negative, or 0.
    int addTimeIfValid(bsls::Types::Int64 hours,
                       bsls::Types::Int64 minutes = 0,
                       bsls::Types::Int64 seconds = 0,
                       bsls::Types::Int64 milliseconds = 0,
                       bsls::Types::Int64 microseconds = 0);

    /// Add the specified number of `hours` to the value of this object,
    /// adjusting the "date" part of the object accordingly.  Return a
    /// reference providing modifiable access to this object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  The behavior is undefined unless the
    /// resulting value is in the valid range for a `Datetime` object.  Note
    /// that `hours` may be positive, negative, or 0.
    Datetime& addHours(bsls::Types::Int64 hours);

    /// Add the specified number of `hours` to the value of this object,
    /// adjusting the "date" part of the object accordingly, if the
    /// resulting value is in the valid range for a `Datetime` object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  Return 0 on success, and a non-zero value
    /// (with no effect) otherwise.  Note that `hours` may be positive,
    /// negative, or 0.
    int addHoursIfValid(bsls::Types::Int64 hours);

    /// Add the specified number of `minutes` to the value of this object,
    /// adjusting the "date" part of the object accordingly.  Return a
    /// reference providing modifiable access to this object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  The behavior is undefined unless the
    /// resulting value is in the valid range for a `Datetime` object.  Note
    /// that `minutes` may be positive, negative, or 0.
    Datetime& addMinutes(bsls::Types::Int64 minutes);

    /// Add the specified number of `minutes` to the value of this object,
    /// adjusting the "date" part of the object accordingly, if the
    /// resulting value is in the valid range for a `Datetime` object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  Return 0 on success, and a non-zero value
    /// (with no effect) otherwise.  Note that `minutes` may be positive,
    /// negative, or 0.
    int addMinutesIfValid(bsls::Types::Int64 minutes);

    /// Add the specified number of `seconds` to the value of this object,
    /// adjusting the "date" part of the object accordingly.  Return a
    /// reference providing modifiable access to this object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  The behavior is undefined unless the
    /// resulting value is in the valid range for a `Datetime` object.  Note
    /// that `seconds` may be positive, negative, or 0.
    Datetime& addSeconds(bsls::Types::Int64 seconds);

    /// Add the specified number of `seconds` to the value of this object,
    /// adjusting the "date" part of the object accordingly, if the
    /// resulting value is in the valid range for a `Datetime` object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  Return 0 on success, and a non-zero value
    /// (with no effect) otherwise.  Note that `seconds` may be positive,
    /// negative, or 0.
    int addSecondsIfValid(bsls::Types::Int64 seconds);

    /// Add the specified number of `milliseconds` to the value of this
    /// object, adjusting the "date" part of the object accordingly.  Return
    /// a reference providing modifiable access to this object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  The behavior is undefined unless the
    /// resulting value is in the valid range for a `Datetime` object.  Note
    /// that `milliseconds` may be positive, negative, or 0.
    Datetime& addMilliseconds(bsls::Types::Int64 milliseconds);

    /// Add the specified number of `milliseconds` to the value of this
    /// object, adjusting the "date" part of the object accordingly, if the
    /// resulting value is in the valid range for a `Datetime` object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  Return 0 on success, and a non-zero value
    /// (with no effect) otherwise.  Note that `milliseconds` may be
    /// positive, negative, or 0.
    int addMillisecondsIfValid(bsls::Types::Int64 milliseconds);

    /// Add the specified number of `microseconds` to the value of this
    /// object, adjusting the "date" part of the object accordingly.  Return
    /// a reference providing modifiable access to this object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  The behavior is undefined unless the
    /// resulting value is in the valid range for a `Datetime` object.  Note
    /// that `microseconds` may be positive, negative, or 0.
    Datetime& addMicroseconds(bsls::Types::Int64 microseconds);

    /// Add the specified number of `microseconds` to the value of this
    /// object, adjusting the "date" part of the object accordingly, if the
    /// resulting value is in the valid range for a `Datetime` object.  If
    /// `24 == hour()` on entry, set the `hour` attribute to 0 before
    /// performing the addition.  Return 0 on success, and a non-zero value
    /// (with no effect) otherwise.  Note that `microseconds` may be
    /// positive, negative, or 0.
    int addMicrosecondsIfValid(bsls::Types::Int64 microseconds);

                                  // Aspects

    /// 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.
    template <class STREAM>
    STREAM& bdexStreamIn(STREAM& stream, int version);

    // ACCESSORS

    /// Return the value of the "date" part of this object.
    Date date() const;

    /// Return the value of the `day` (of the month) attribute of this
    /// object.
    int day() const;

    /// Return the value of the `dayOfWeek` attribute associated with the
    /// `day` (of the month) attribute of this object.
    DayOfWeek::Enum dayOfWeek() const;

    /// Return the value of the `dayOfYear` attribute of this object.
    int dayOfYear() const;

    /// Load, into the specified `hour`, and the optionally specified
    /// `minute`, `second`, `millisecond`, and `microsecond` the respective
    /// `hour`, `minute`, `second`, `millisecond`, and `microsecond`
    /// attribute values from this time object.  Unspecified arguments
    /// default to 0.  Supplying 0 for an address argument suppresses the
    /// loading of the value for the corresponding attribute, but has no
    /// effect on the loading of other attribute values.
    void getTime(int *hour,
                 int *minute = 0,
                 int *second = 0,
                 int *millisecond = 0,
                 int *microsecond = 0) const;

    /// Return the value of the `hour` attribute of this object.
    int hour() const;

    /// Return the value of the `microsecond` attribute of this object.
    int microsecond() const;

    /// Return the value of the `millisecond` attribute of this object.
    int millisecond() const;

    /// Return the value of the `minute` attribute of this object.
    int minute() const;

    /// Return the value of the `month` attribute of this object.
    int month() const;

    /// Return the value of the `second` attribute of this object.
    int second() const;

    /// Return the value of the "time" part of this object.
    Time time() const;

    /// Return the value of the `year` attribute of this object.
    int year() const;

    /// Efficiently write to the specified `result` buffer no more than the
    /// specified `numBytes` of a representation of the value of this
    /// object.  Optionally specify `fractionalSecondPrecision` digits to
    /// indicate how many fractional second digits to output.  If
    /// `fractionalSecondPrecision` is not specified then 6 fractional
    /// second digits will be output (3 digits for milliseconds and 3 digits
    /// for microseconds).  Return the number of characters (not including
    /// the null character) that would have been written if the limit due to
    /// `numBytes` were not imposed.  `result` is null-terminated unless
    /// `numBytes` is 0.  The behavior is undefined unless `0 <= numBytes`,
    /// `0 <= fractionalSecondPrecision <= 6`, and `result` refers to at
    /// least `numBytes` contiguous bytes.  Note that the return value is
    /// greater than or equal to `numBytes` if the output representation was
    /// truncated to avoid `result` overrun.
    int printToBuffer(char *result,
                      int   numBytes,
                      int   fractionalSecondPrecision = 6) const;

                                  // Aspects

    /// 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.
    template <class STREAM>
    STREAM& bdexStreamOut(STREAM& stream, int version) 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.
    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;

#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation

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

#endif  // BDE_OPENSOURCE_PUBLICATION -- pending deprecation
#ifndef BDE_OMIT_INTERNAL_DEPRECATED  // BDE2.22

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

    /// Format this datetime to the specified output `stream` and return a
    /// reference to the modifiable `stream`.
    ///
    /// @DEPRECATED: Use `print` instead.
    bsl::ostream& streamOut(bsl::ostream& stream) const;

    /// Set the "date" part of this object's value to the specified `year`,
    /// `month`, and `day`, and the "time" part to the optionally specified
    /// `hour`, `minute`, `second`, and `millisecond`, if they represent a
    /// valid `Datetime` value, with trailing fields that are not specified
    /// set to 0.  Return 0 on success, and a non-zero value (with no
    /// effect) otherwise.
    ///
    /// @DEPRECATED: Use `setDatetimeIfValid` instead.
    int validateAndSetDatetime(int year,
                               int month,
                               int day,
                               int hour = 0,
                               int minute = 0,
                               int second = 0,
                               int millisecond = 0);

#endif  // BDE_OMIT_INTERNAL_DEPRECATED -- BDE2.22

};

// FREE OPERATORS

/// Return a `Datetime` object having a value that is the sum of the
/// specified `lhs` (`Datetime`) and the specified `rhs`
/// (`bsls::TimeInterval`).  If `24 == lhs.hour()`, the result is the same
/// as if the `hour` attribute of `lhs` is 0.  The behavior is undefined
/// unless the resulting value is in the valid range for a `Datetime`
/// object.
Datetime operator+(const Datetime& lhs, const bsls::TimeInterval& rhs);

/// Return a `Datetime` object having a value that is the sum of the
/// specified `lhs` (`bsls::TimeInterval`) and the specified `rhs`
/// (`Datetime`).  If `24 == rhs.hour()`, the result is the same as if the
/// `hour` attribute of `rhs` is 0.  The behavior is undefined unless the
/// resulting value is in the valid range for a `Datetime` object.
Datetime operator+(const bsls::TimeInterval& lhs, const Datetime& rhs);

/// Return a `Datetime` object having a value that is the sum of the
/// specified `lhs` (`Datetime`) and the specified `rhs`
/// (`DatetimeInterval`).  If `24 == lhs.hour()`, the result is the same as
/// if the `hour` attribute of `lhs` is 0.  The behavior is undefined unless
/// the resulting value is in the valid range for a `Datetime` object.
Datetime operator+(const Datetime& lhs, const DatetimeInterval& rhs);

/// Return a `Datetime` object having a value that is the sum of the
/// specified `lhs` (`DatetimeInterval`) and the specified `rhs`
/// (`Datetime`).  If `24 == rhs.hour()`, the result is the same as if the
/// `hour` attribute of `rhs` is 0.  The behavior is undefined unless the
/// resulting value is in the valid range for a `Datetime` object.
Datetime operator+(const DatetimeInterval& lhs, const Datetime& rhs);

/// Return a `Datetime` object having a value that is the difference between
/// the specified `lhs` (`Datetime`) and the specified `rhs`
/// (`bsls::TimeInterval`).  If `24 == lhs.hour()`, the result is the same
/// as if the `hour` attribute of `lhs` is 0.  The behavior is undefined
/// unless the resulting value is in the valid range for a `Datetime`
/// object.
Datetime operator-(const Datetime& lhs, const bsls::TimeInterval& rhs);

/// Return a `Datetime` object having a value that is the difference between
/// the specified `lhs` (`Datetime`) and the specified `rhs`
/// (`DatetimeInterval`).  If `24 == lhs.hour()`, the result is the same as
/// if the `hour` attribute of `lhs` is 0.  The behavior is undefined unless
/// the resulting value is in the valid range for a `Datetime` object.
Datetime operator-(const Datetime& lhs, const DatetimeInterval& rhs);

/// Return a `DatetimeInterval` object having a value that is the difference
/// between the specified `lhs` (`Datetime`) and the specified `rhs`
/// (`Datetime`).  If the `hour` attribute of either operand is 24, the
/// result is the same as if that `hour` attribute is 0.  The behavior is
/// undefined unless the resulting value is in the valid range for a
/// `DatetimeInterval` object.
DatetimeInterval operator-(const Datetime& lhs, const Datetime& rhs);

/// Return `true` if the specified `lhs` and `rhs` objects have the same
/// value, and `false` otherwise.  Two `Datetime` objects have the same
/// value if they have the same values for their "date" and "time" parts,
/// respectively.
bool operator==(const Datetime& lhs, const Datetime& rhs);

/// Return `true` if the specified `lhs` and `rhs` `Datetime` objects do not
/// have the same value, and `false` otherwise.  Two `Datetime` objects do
/// not have the same value if they do not have the same values for either
/// of their "date" or "time" parts, respectively.
bool operator!=(const Datetime& lhs, const Datetime& rhs);

/// Return `true` if the value of the specified `lhs` object is less than
/// the value of the specified `rhs` object, and `false` otherwise.  A
/// `Datetime` object `a` is less than a `Datetime` object `b` if
/// `a.date() < b.date()`, or if `a.date() == b.date()` and the time portion
/// of `a` is less than the time portion of `b`.  The behavior is undefined
/// unless `24 != lhs.hour() && 24 != rhs.hour()`.
bool operator<(const Datetime& lhs, const Datetime& rhs);

/// Return `true` if the value of the specified `lhs` object is less than or
/// equal to the value of the specified `rhs` object, and `false` otherwise.
/// The behavior is undefined unless `24 != lhs.hour() && 24 != rhs.hour()`.
bool operator<=(const Datetime& lhs, const Datetime& rhs);

/// Return `true` if the value of the specified `lhs` object is greater than
/// the value of the specified `rhs` object, and `false` otherwise.  A
/// `Datetime` object `a` is greater than a `Datetime` object `b` if
/// `a.date() > b.date()`, or if `a.date() == b.date()` and the time portion
/// of `a` is greater than the time portion of `b`.  The behavior is
/// undefined unless `24 != lhs.hour() && 24 != rhs.hour()`.
bool operator>(const Datetime& lhs, const Datetime& rhs);

/// Return `true` if the value of the specified `lhs` object is greater than
/// or equal to the value of the specified `rhs` object, and `false`
/// otherwise.  The behavior is undefined unless
/// `24 != lhs.hour() && 24 != rhs.hour()`.
bool operator>=(const Datetime& lhs, const Datetime& rhs);

/// Write the value of the specified `object` 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);
/// ```
bsl::ostream& operator<<(bsl::ostream& stream, const Datetime& object);

// FREE FUNCTIONS

/// 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 `Datetime`.
template <class HASHALG>
void hashAppend(HASHALG& hashAlg, const Datetime& object);

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

                               // --------------
                               // class Datetime
                               // --------------

// PRIVATE MANIPULATOR
inline
void Datetime::setMicrosecondsFromEpoch(bsls::Types::Uint64 totalMicroseconds)
{
    d_value = ((totalMicroseconds / TimeUnitRatio::k_US_PER_D)
                                                            << k_NUM_TIME_BITS)
            + totalMicroseconds % TimeUnitRatio::k_US_PER_D;
    d_value |= k_REP_MASK;
}

// PRIVATE ACCESSORS
inline
bsls::Types::Uint64 Datetime::microsecondsFromEpoch() const
{
    if (validateAndTraceLogRepresentation()) {
        int h = hour();

        bsls::Types::Uint64 value = d_value & (~k_REP_MASK);

        if (TimeUnitRatio::k_H_PER_D_32 != h) {
            return (value >> k_NUM_TIME_BITS) * TimeUnitRatio::k_US_PER_D
                 + (value & k_TIME_MASK);                             // RETURN
        }

        return (value >> k_NUM_TIME_BITS)
                                        * TimeUnitRatio::k_US_PER_D;  // RETURN
    }

#if BSLS_PLATFORM_IS_LITTLE_ENDIAN
    bsls::Types::Uint64 days = (d_value & 0xffffffff) - 1;
    bsls::Types::Uint64 milliseconds =
                                (d_value >> 32) % TimeUnitRatio::k_MS_PER_D_32;
#else
    bsls::Types::Uint64 days = (d_value >> 32) - 1;
    bsls::Types::Uint64 milliseconds =
                         (d_value & 0xffffffff) % TimeUnitRatio::k_MS_PER_D_32;
#endif

    return TimeUnitRatio::k_US_PER_D  * days
         + TimeUnitRatio::k_US_PER_MS * milliseconds;
}

inline
bsls::Types::Uint64 Datetime::updatedRepresentation() const
{
    if (validateAndTraceLogRepresentation()) {
        return d_value;                                               // RETURN
    }

#if BSLS_PLATFORM_IS_LITTLE_ENDIAN
    bsls::Types::Uint64 days = (d_value & 0xffffffff) - 1;
    bsls::Types::Uint64 milliseconds = d_value >> 32;
#else
    bsls::Types::Uint64 days = (d_value >> 32) - 1;
    bsls::Types::Uint64 milliseconds = d_value & 0xffffffff;
#endif

    return (days << k_NUM_TIME_BITS)
         | (TimeUnitRatio::k_US_PER_MS * milliseconds)
         | k_REP_MASK;
}

inline
bool Datetime::validateAndTraceLogRepresentation() const
{
    if (BSLS_PERFORMANCEHINT_PREDICT_LIKELY(k_REP_MASK <= d_value)) {
        return true;                                                  // RETURN
    }
    BSLS_ASSERT_SAFE(
                 0 && "detected invalid `bdlt::Datetime`; see TEAM 579660115");
    BSLS_REVIEW_INVOKE(
                      "detected invalid `bdlt::Datetime`; see TEAM 579660115");

    return false;
}

// CLASS METHODS
inline
bool Datetime::isValid(int year,
                       int month,
                       int day,
                       int hour,
                       int minute,
                       int second,
                       int millisecond,
                       int microsecond)
{
    return Date::isValidYearMonthDay(year, month, day)
        && (   (0 <= hour      && hour   < bdlt::TimeUnitRatio::k_H_PER_D_32 &&
                0 <= minute    && minute < bdlt::TimeUnitRatio::k_M_PER_H_32 &&
                0 <= second    && second < bdlt::TimeUnitRatio::k_S_PER_M_32 &&
                0 <= millisecond && millisecond
                                       < bdlt::TimeUnitRatio::k_MS_PER_S_32  &&
                0 <= microsecond && microsecond
                                       < bdlt::TimeUnitRatio::k_US_PER_MS_32)
            || (bdlt::TimeUnitRatio::k_H_PER_D_32 == hour &&
                0                                 == minute &&
                0                                 == second &&
                0                                 == millisecond &&
                0                                 == microsecond));
}

                                  // Aspects

inline
int Datetime::maxSupportedBdexVersion(int versionSelector)
{
    if (versionSelector >= 20160411) {
        return 2;                                                     // RETURN
    }
    return 1;
}

// CREATORS
inline
Datetime::Datetime()
: d_value(TimeUnitRatio::k_US_PER_D)
{
    d_value |= k_REP_MASK;
}

inline
Datetime::Datetime(const Date& date)
: d_value(static_cast<bsls::Types::Uint64>(date - Date()) << k_NUM_TIME_BITS)
{
    d_value |= k_REP_MASK;
}

inline
Datetime::Datetime(const Date& date, const Time& time)
{
    setDatetime(date, time);
}

inline
Datetime::Datetime(int year,
                   int month,
                   int day,
                   int hour,
                   int minute,
                   int second,
                   int millisecond,
                   int microsecond)
{
    setDatetime(year,
                month,
                day,
                hour,
                minute,
                second,
                millisecond,
                microsecond);
}

inline
Datetime::Datetime(const Datetime& original)
: d_value(original.d_value)
{
    d_value = updatedRepresentation();
}

// MANIPULATORS
inline
Datetime& Datetime::operator=(const Datetime& rhs)
{
    d_value = rhs.d_value;
    d_value = updatedRepresentation();

    return *this;
}

inline
Datetime& Datetime::operator+=(const bsls::TimeInterval& rhs)
{
    BSLS_ASSERT_SAFE( rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                               k_MAX_US_FROM_EPOCH - microsecondsFromEpoch()));

    BSLS_ASSERT_SAFE(-rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                                                     microsecondsFromEpoch()));

    bsls::Types::Uint64 totalMicroseconds =
                             microsecondsFromEpoch() + rhs.totalMicroseconds();
    setMicrosecondsFromEpoch(totalMicroseconds);

    return *this;
}

inline
Datetime& Datetime::operator-=(const bsls::TimeInterval& rhs)
{
    BSLS_ASSERT_SAFE(-rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                               k_MAX_US_FROM_EPOCH - microsecondsFromEpoch()));

    BSLS_ASSERT_SAFE( rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                                                     microsecondsFromEpoch()));

    bsls::Types::Uint64 totalMicroseconds =
                             microsecondsFromEpoch() - rhs.totalMicroseconds();
    setMicrosecondsFromEpoch(totalMicroseconds);

    return *this;
}

inline
Datetime& Datetime::operator+=(const DatetimeInterval& rhs)
{
    BSLS_ASSERT_SAFE( rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                               k_MAX_US_FROM_EPOCH - microsecondsFromEpoch()));

    BSLS_ASSERT_SAFE(-rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                                                     microsecondsFromEpoch()));

    bsls::Types::Uint64 totalMicroseconds =
                             microsecondsFromEpoch() + rhs.totalMicroseconds();
    setMicrosecondsFromEpoch(totalMicroseconds);

    return *this;
}

inline
Datetime& Datetime::operator-=(const DatetimeInterval& rhs)
{
    BSLS_ASSERT_SAFE(-rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                               k_MAX_US_FROM_EPOCH - microsecondsFromEpoch()));

    BSLS_ASSERT_SAFE( rhs.totalMicroseconds()
                     <= static_cast<bsls::Types::Int64>(
                                                     microsecondsFromEpoch()));

    bsls::Types::Uint64 totalMicroseconds =
                             microsecondsFromEpoch() - rhs.totalMicroseconds();
    setMicrosecondsFromEpoch(totalMicroseconds);

    return *this;
}

inline
void Datetime::setDatetime(const Date& date,
                           int         hour,
                           int         minute,
                           int         second,
                           int         millisecond,
                           int         microsecond)
{
    BSLS_ASSERT_SAFE(Datetime::isValid(date.year(),
                                       date.month(),
                                       date.day(),
                                       hour,
                                       minute,
                                       second,
                                       millisecond,
                                       microsecond));

    d_value = (static_cast<bsls::Types::Uint64>(date - Date())
                                                            << k_NUM_TIME_BITS)
            + TimeUnitRatio::k_US_PER_H  * hour
            + TimeUnitRatio::k_US_PER_M  * minute
            + TimeUnitRatio::k_US_PER_S  * second
            + TimeUnitRatio::k_US_PER_MS * millisecond
            + microsecond;

    d_value |= k_REP_MASK;
}

inline
void Datetime::setDatetime(const Date& date, const Time& time)
{
    if (24 != time.hour()) {
        d_value = (static_cast<bsls::Types::Uint64>(date - Date())
                                                            << k_NUM_TIME_BITS)
                + (time - Time(0)).totalMicroseconds();
    }
    else {
        d_value = (static_cast<bsls::Types::Uint64>(date - Date())
                                                            << k_NUM_TIME_BITS)
                + TimeUnitRatio::k_US_PER_D;
    }

    d_value |= k_REP_MASK;
}

inline
void Datetime::setDatetime(int year,
                           int month,
                           int day,
                           int hour,
                           int minute,
                           int second,
                           int millisecond,
                           int microsecond)
{
    BSLS_ASSERT_SAFE(Datetime::isValid(year,
                                       month,
                                       day,
                                       hour,
                                       minute,
                                       second,
                                       millisecond,
                                       microsecond));

    setDatetime(Date(year, month, day),
                hour,
                minute,
                second,
                millisecond,
                microsecond);
}

inline
int Datetime::setDatetimeIfValid(int year,
                                 int month,
                                 int day,
                                 int hour,
                                 int minute,
                                 int second,
                                 int millisecond,
                                 int microsecond)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (Datetime::isValid(year,
                          month,
                          day,
                          hour,
                          minute,
                          second,
                          millisecond,
                          microsecond)) {
        setDatetime(Date(year, month, day),
                    hour,
                    minute,
                    second,
                    millisecond,
                    microsecond);

        return k_SUCCESS;                                             // RETURN
    }

    return k_FAILURE;
}

inline
int Datetime::setDatetimeIfValid(const Date& date,
                                 int         hour,
                                 int         minute,
                                 int         second,
                                 int         millisecond,
                                 int         microsecond)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (Datetime::isValid(date.year(),
                          date.month(),
                          date.day(),
                          hour,
                          minute,
                          second,
                          millisecond,
                          microsecond)) {
        setDatetime(date,
                    hour,
                    minute,
                    second,
                    millisecond,
                    microsecond);

        return k_SUCCESS;                                             // RETURN
    }

    return k_FAILURE;
}

inline
void Datetime::setDate(const Date& date)
{
    d_value = updatedRepresentation();

    d_value = (static_cast<bsls::Types::Uint64>(date - Date())
                                                            << k_NUM_TIME_BITS)
            | (d_value & k_TIME_MASK);

    d_value |= k_REP_MASK;
}

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

    setDate(Date(year, dayOfYear));
}

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

    if (Date::isValidYearDay(year, dayOfYear)) {
        setDate(Date(year, dayOfYear));
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

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

    setDate(Date(year, month, day));
}

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

    if (Date::isValidYearMonthDay(year, month, day)) {
        setDate(Date(year, month, day));
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
void Datetime::setTime(const Time& time)
{
    d_value = updatedRepresentation();

    if (24 != time.hour()) {
        d_value = (d_value & k_DATE_MASK)
                | (time - Time(0)).totalMicroseconds();
    }
    else {
        d_value = (d_value & k_DATE_MASK) | TimeUnitRatio::k_US_PER_D;
    }
}

inline
void Datetime::setTime(int hour,
                       int minute,
                       int second,
                       int millisecond,
                       int microsecond)
{
    BSLS_PRECONDITIONS_BEGIN();
    BSLS_ASSERT_SAFE(
               (0 <= hour      && hour   < bdlt::TimeUnitRatio::k_H_PER_D_32 &&
                0 <= minute    && minute < bdlt::TimeUnitRatio::k_M_PER_H_32 &&
                0 <= second    && second < bdlt::TimeUnitRatio::k_S_PER_M_32 &&
                0 <= millisecond && millisecond
                                       < bdlt::TimeUnitRatio::k_MS_PER_S_32  &&
                0 <= microsecond && microsecond
                                       < bdlt::TimeUnitRatio::k_US_PER_MS_32)
            || (bdlt::TimeUnitRatio::k_H_PER_D_32 == hour &&
                0                                 == minute &&
                0                                 == second &&
                0                                 == millisecond &&
                0                                 == microsecond));
    BSLS_PRECONDITIONS_END();

    d_value = updatedRepresentation();

    d_value = TimeUnitRatio::k_US_PER_H  * hour
            + TimeUnitRatio::k_US_PER_M  * minute
            + TimeUnitRatio::k_US_PER_S  * second
            + TimeUnitRatio::k_US_PER_MS * millisecond
            + microsecond
            + (d_value & k_DATE_MASK);
}

inline
int Datetime::setTimeIfValid(int hour,
                             int minute,
                             int second,
                             int millisecond,
                             int microsecond)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (       (0 <= hour      && hour   < bdlt::TimeUnitRatio::k_H_PER_D_32 &&
                0 <= minute    && minute < bdlt::TimeUnitRatio::k_M_PER_H_32 &&
                0 <= second    && second < bdlt::TimeUnitRatio::k_S_PER_M_32 &&
                0 <= millisecond && millisecond
                                       < bdlt::TimeUnitRatio::k_MS_PER_S_32  &&
                0 <= microsecond && microsecond
                                       < bdlt::TimeUnitRatio::k_US_PER_MS_32)
            || (bdlt::TimeUnitRatio::k_H_PER_D_32 == hour &&
                0                                 == minute &&
                0                                 == second &&
                0                                 == millisecond &&
                0                                 == microsecond)) {
        setTime(hour, minute, second, millisecond, microsecond);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
void Datetime::setHour(int hour)
{
    BSLS_ASSERT_SAFE(0 <= hour);
    BSLS_ASSERT_SAFE(     hour <= 24);

    d_value = updatedRepresentation();

    if (TimeUnitRatio::k_H_PER_D_32 != hour) {
        bsls::Types::Uint64 microseconds = d_value & k_TIME_MASK;
        microseconds = microseconds % TimeUnitRatio::k_US_PER_H
                     + hour         * TimeUnitRatio::k_US_PER_H;
        d_value = microseconds | (d_value & k_DATE_MASK);
    }
    else {
        d_value = TimeUnitRatio::k_US_PER_D | (d_value & k_DATE_MASK);
    }
}

inline
int Datetime::setHourIfValid(int hour)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (0 <= hour && hour <= 24) {
        setHour(hour);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
void Datetime::setMinute(int minute)
{
    BSLS_ASSERT_SAFE(0 <= minute);
    BSLS_ASSERT_SAFE(     minute <= 59);

    d_value = updatedRepresentation();

    if (TimeUnitRatio::k_H_PER_D_32 != hour()) {
        bsls::Types::Uint64 microseconds = d_value & k_TIME_MASK;
        microseconds = microseconds / TimeUnitRatio::k_US_PER_H
                                                    * TimeUnitRatio::k_US_PER_H
                     + microseconds % TimeUnitRatio::k_US_PER_M
                     + minute       * TimeUnitRatio::k_US_PER_M;
        d_value = microseconds | (d_value & k_DATE_MASK);
    }
    else {
        d_value = TimeUnitRatio::k_US_PER_M * minute
                | (d_value & k_DATE_MASK);
    }
}

inline
int Datetime::setMinuteIfValid(int minute)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (0 <= minute && minute <= 59) {
        setMinute(minute);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
void Datetime::setSecond(int second)
{
    BSLS_ASSERT_SAFE(0 <= second);
    BSLS_ASSERT_SAFE(     second <= 59);

    d_value = updatedRepresentation();

    if (TimeUnitRatio::k_H_PER_D_32 != hour()) {
        bsls::Types::Uint64 microseconds = d_value & k_TIME_MASK;
        microseconds = microseconds / TimeUnitRatio::k_US_PER_M
                                                    * TimeUnitRatio::k_US_PER_M
                     + microseconds % TimeUnitRatio::k_US_PER_S
                     + second       * TimeUnitRatio::k_US_PER_S;
        d_value = microseconds | (d_value & k_DATE_MASK);
    }
    else {
        d_value = TimeUnitRatio::k_US_PER_S * second
                | (d_value & k_DATE_MASK);
    }
}

inline
int Datetime::setSecondIfValid(int second)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (0 <= second && second <= 59) {
        setSecond(second);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
void Datetime::setMillisecond(int millisecond)
{
    BSLS_ASSERT_SAFE(0 <= millisecond);
    BSLS_ASSERT_SAFE(     millisecond <= 999);

    d_value = updatedRepresentation();

    if (TimeUnitRatio::k_H_PER_D_32 != hour()) {
        bsls::Types::Uint64 microseconds = d_value & k_TIME_MASK;
        microseconds = microseconds / TimeUnitRatio::k_US_PER_S
                                                    * TimeUnitRatio::k_US_PER_S
                     + microseconds % TimeUnitRatio::k_US_PER_MS
                     + millisecond  * TimeUnitRatio::k_US_PER_MS;
        d_value = microseconds | (d_value & k_DATE_MASK);
    }
    else {
        d_value = TimeUnitRatio::k_US_PER_MS * millisecond
                | (d_value & k_DATE_MASK);
    }
}

inline
int Datetime::setMillisecondIfValid(int millisecond)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (0 <= millisecond && millisecond <= 999) {
        setMillisecond(millisecond);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
void Datetime::setMicrosecond(int microsecond)
{
    BSLS_ASSERT_SAFE(0 <= microsecond);
    BSLS_ASSERT_SAFE(     microsecond <= 999);

    d_value = updatedRepresentation();

    if (TimeUnitRatio::k_H_PER_D_32 != hour()) {
        bsls::Types::Uint64 microseconds = d_value & k_TIME_MASK;
        microseconds = microseconds / TimeUnitRatio::k_US_PER_MS
                                                   * TimeUnitRatio::k_US_PER_MS
                     + microsecond;
        d_value = microseconds | (d_value & k_DATE_MASK);
    }
    else {
        d_value = (d_value & k_DATE_MASK) | microsecond;
    }
}

inline
int Datetime::setMicrosecondIfValid(int microsecond)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (0 <= microsecond && microsecond <= 999) {
        setMicrosecond(microsecond);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
Datetime& Datetime::addDays(int days)
{
    BSLS_ASSERT_SAFE(0 == Date(date()).addDaysIfValid(days));

    d_value = updatedRepresentation();  // needed to avoid double logging from
                                        // `date` and then `setDate`

    setDate(date() + days);

    return *this;
}

inline
int Datetime::addDaysIfValid(int days)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (0 == Date(date()).addDaysIfValid(days)) {
        addDays(days);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
Datetime& Datetime::addTime(bsls::Types::Int64 hours,
                            bsls::Types::Int64 minutes,
                            bsls::Types::Int64 seconds,
                            bsls::Types::Int64 milliseconds,
                            bsls::Types::Int64 microseconds)
{
    // Reduce the input parameters to `days` and `microseconds`, without any
    // constraints on the representation, without the possibility of overflow
    // or underflow.

    bsls::Types::Int64 days = hours        / TimeUnitRatio::k_H_PER_D
                            + minutes      / TimeUnitRatio::k_M_PER_D
                            + seconds      / TimeUnitRatio::k_S_PER_D
                            + milliseconds / TimeUnitRatio::k_MS_PER_D
                            + microseconds / TimeUnitRatio::k_US_PER_D;

    hours        %= TimeUnitRatio::k_H_PER_D;
    minutes      %= TimeUnitRatio::k_M_PER_D;
    seconds      %= TimeUnitRatio::k_S_PER_D;
    milliseconds %= TimeUnitRatio::k_MS_PER_D;
    microseconds %= TimeUnitRatio::k_US_PER_D;

    microseconds = hours             * TimeUnitRatio::k_US_PER_H
                 + minutes           * TimeUnitRatio::k_US_PER_M
                 + seconds           * TimeUnitRatio::k_US_PER_S
                 + milliseconds      * TimeUnitRatio::k_US_PER_MS
                 + microseconds;

    // Modify the representation to ensure `days` and `microseconds` have the
    // same sign (i.e., both are positive or both are negative or `days == 0`).

    days         += microseconds / TimeUnitRatio::k_US_PER_D;
    microseconds %= TimeUnitRatio::k_US_PER_D;

    if (days > 0 && microseconds < 0) {
        --days;
        microseconds += TimeUnitRatio::k_US_PER_D;
    }
    else if (days < 0 && microseconds > 0) {
        ++days;
        microseconds -= TimeUnitRatio::k_US_PER_D;
    }

    // Piecewise add the `days` and `microseconds` to this datetime.

    bsls::Types::Uint64 totalMicroseconds = microsecondsFromEpoch();

    BSLS_ASSERT_SAFE(days <=  static_cast<bsls::Types::Int64>
                                     ((k_MAX_US_FROM_EPOCH - totalMicroseconds)
                                                 / TimeUnitRatio::k_US_PER_D));
    BSLS_ASSERT_SAFE(days >= -static_cast<bsls::Types::Int64>
                              (totalMicroseconds / TimeUnitRatio::k_US_PER_D));

    totalMicroseconds += days * TimeUnitRatio::k_US_PER_D;

    BSLS_ASSERT_SAFE(microseconds <=  static_cast<bsls::Types::Int64>
                                    (k_MAX_US_FROM_EPOCH - totalMicroseconds));
    BSLS_ASSERT_SAFE(microseconds >= -static_cast<bsls::Types::Int64>
                                                          (totalMicroseconds));

    totalMicroseconds += microseconds;

    // Assign the value.

    setMicrosecondsFromEpoch(totalMicroseconds);

    return *this;
}

inline
int Datetime::addTimeIfValid(bsls::Types::Int64 hours,
                             bsls::Types::Int64 minutes,
                             bsls::Types::Int64 seconds,
                             bsls::Types::Int64 milliseconds,
                             bsls::Types::Int64 microseconds)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    // Reduce the input parameters to `days` and `microseconds`, without any
    // constraints on the representation, without the possibility of overflow
    // or underflow.

    bsls::Types::Int64 days = hours        / TimeUnitRatio::k_H_PER_D
                            + minutes      / TimeUnitRatio::k_M_PER_D
                            + seconds      / TimeUnitRatio::k_S_PER_D
                            + milliseconds / TimeUnitRatio::k_MS_PER_D
                            + microseconds / TimeUnitRatio::k_US_PER_D;

    hours        %= TimeUnitRatio::k_H_PER_D;
    minutes      %= TimeUnitRatio::k_M_PER_D;
    seconds      %= TimeUnitRatio::k_S_PER_D;
    milliseconds %= TimeUnitRatio::k_MS_PER_D;
    microseconds %= TimeUnitRatio::k_US_PER_D;

    microseconds = hours             * TimeUnitRatio::k_US_PER_H
                 + minutes           * TimeUnitRatio::k_US_PER_M
                 + seconds           * TimeUnitRatio::k_US_PER_S
                 + milliseconds      * TimeUnitRatio::k_US_PER_MS
                 + microseconds;

    // Modify the representation to ensure `days` and `microseconds` have the
    // same sign (i.e., both are positive or both are negative or `days == 0`).

    days         += microseconds / TimeUnitRatio::k_US_PER_D;
    microseconds %= TimeUnitRatio::k_US_PER_D;

    if (days > 0 && microseconds < 0) {
        --days;
        microseconds += TimeUnitRatio::k_US_PER_D;
    }
    else if (days < 0 && microseconds > 0) {
        ++days;
        microseconds -= TimeUnitRatio::k_US_PER_D;
    }

    // Piecewise add the `days` and `microseconds` to this datetime.

    bsls::Types::Uint64 totalMicroseconds = microsecondsFromEpoch();

    if (!( days <=  static_cast<bsls::Types::Int64>
                                     ((k_MAX_US_FROM_EPOCH - totalMicroseconds)
                                                   / TimeUnitRatio::k_US_PER_D)
        && days >= -static_cast<bsls::Types::Int64>
                            (totalMicroseconds / TimeUnitRatio::k_US_PER_D))) {
        return k_FAILURE;                                             // RETURN
    }

    totalMicroseconds += days * TimeUnitRatio::k_US_PER_D;

    if (!( microseconds <=  static_cast<bsls::Types::Int64>
                                      (k_MAX_US_FROM_EPOCH - totalMicroseconds)
        && microseconds >= -static_cast<bsls::Types::Int64>
                                                        (totalMicroseconds))) {
        return k_FAILURE;                                             // RETURN
    }

    totalMicroseconds += microseconds;

    // Assign the value.

    setMicrosecondsFromEpoch(totalMicroseconds);
    return k_SUCCESS;
}

inline
Datetime& Datetime::addHours(bsls::Types::Int64 hours)
{
    BSLS_ASSERT_SAFE(hours <=  static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                 / TimeUnitRatio::k_US_PER_H));
    BSLS_ASSERT_SAFE(hours >= -static_cast<bsls::Types::Int64>
                        (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_H));

    bsls::Types::Uint64 totalMicroseconds = microsecondsFromEpoch();

    setMicrosecondsFromEpoch(hours * TimeUnitRatio::k_US_PER_H
                                                          + totalMicroseconds);

    return *this;
}

inline
int Datetime::addHoursIfValid(bsls::Types::Int64 hours)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (   hours <= static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                   / TimeUnitRatio::k_US_PER_H)
        && hours >= -static_cast<bsls::Types::Int64>
                       (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_H)) {
        addHours(hours);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
Datetime& Datetime::addMinutes(bsls::Types::Int64 minutes)
{
    BSLS_ASSERT_SAFE(minutes <=  static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                 / TimeUnitRatio::k_US_PER_M));
    BSLS_ASSERT_SAFE(minutes >= -static_cast<bsls::Types::Int64>
                        (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_M));

    bsls::Types::Uint64 totalMicroseconds = microsecondsFromEpoch();

    setMicrosecondsFromEpoch(minutes * TimeUnitRatio::k_US_PER_M
                                                          + totalMicroseconds);
    return *this;
}

inline
int Datetime::addMinutesIfValid(bsls::Types::Int64 minutes)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (   minutes <=  static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                 / TimeUnitRatio::k_US_PER_M)
        && minutes >= -static_cast<bsls::Types::Int64>
                       (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_M)) {
        addMinutes(minutes);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
Datetime& Datetime::addSeconds(bsls::Types::Int64 seconds)
{
    BSLS_ASSERT_SAFE(seconds <=  static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                 / TimeUnitRatio::k_US_PER_S));
    BSLS_ASSERT_SAFE(seconds >= -static_cast<bsls::Types::Int64>
                        (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_S));

    bsls::Types::Uint64 totalMicroseconds = microsecondsFromEpoch();

    setMicrosecondsFromEpoch(seconds * TimeUnitRatio::k_US_PER_S
                                                          + totalMicroseconds);
    return *this;
}

inline
int Datetime::addSecondsIfValid(bsls::Types::Int64 seconds)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (   seconds <=  static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                   / TimeUnitRatio::k_US_PER_S)
        && seconds >= -static_cast<bsls::Types::Int64>
                       (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_S)) {
        addSeconds(seconds);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
Datetime& Datetime::addMilliseconds(bsls::Types::Int64 milliseconds)
{
    BSLS_ASSERT_SAFE(milliseconds <=  static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                / TimeUnitRatio::k_US_PER_MS));
    BSLS_ASSERT_SAFE(milliseconds >= -static_cast<bsls::Types::Int64>
                       (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_MS));

    bsls::Types::Uint64 totalMicroseconds = microsecondsFromEpoch();

    setMicrosecondsFromEpoch(milliseconds * TimeUnitRatio::k_US_PER_MS
                                                          + totalMicroseconds);
    return *this;
}

inline
int Datetime::addMillisecondsIfValid(bsls::Types::Int64 milliseconds)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (   milliseconds <=  static_cast<bsls::Types::Int64>
                               ((k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
                                                  / TimeUnitRatio::k_US_PER_MS)
        && milliseconds >= -static_cast<bsls::Types::Int64>
                      (microsecondsFromEpoch() / TimeUnitRatio::k_US_PER_MS)) {
        addMilliseconds(milliseconds);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

inline
Datetime& Datetime::addMicroseconds(bsls::Types::Int64 microseconds)
{
    BSLS_ASSERT_SAFE(microseconds <=  static_cast<bsls::Types::Int64>
                              (k_MAX_US_FROM_EPOCH - microsecondsFromEpoch()));
    BSLS_ASSERT_SAFE(microseconds >= -static_cast<bsls::Types::Int64>
                                                    (microsecondsFromEpoch()));

    bsls::Types::Uint64 totalMicroseconds = microsecondsFromEpoch();

    setMicrosecondsFromEpoch(microseconds + totalMicroseconds);

    return *this;
}

inline
int Datetime::addMicrosecondsIfValid(bsls::Types::Int64 microseconds)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    if (   microseconds <=  static_cast<bsls::Types::Int64>
                                (k_MAX_US_FROM_EPOCH - microsecondsFromEpoch())
        && microseconds >= -static_cast<bsls::Types::Int64>
                                                   (microsecondsFromEpoch())) {
        addMicroseconds(microseconds);
        return k_SUCCESS;                                             // RETURN
    }
    return k_FAILURE;
}

                                  // Aspects

template <class STREAM>
STREAM& Datetime::bdexStreamIn(STREAM& stream, int version)
{
    if (stream) {
        switch (version) { // switch on the schema version
          case 2: {
            bsls::Types::Uint64 tmp = 0;    // Note that we have to initialize
                                            // this to a value to silence
                                            // potential -Wmaybe-uninitialized

            stream.getUint64(tmp);

            if (   stream
                && tmp <= (DatetimeImpUtil::k_MAX_VALUE & (~k_REP_MASK))) {
                d_value = tmp;
                d_value |= k_REP_MASK;
            }
            else {
                stream.invalidate();
            }
          } break;
          case 1: {
            Date date;
            Time time;

            date.bdexStreamIn(stream, 1);
            time.bdexStreamIn(stream, 1);

            if (stream) {
                setDatetime(date, time);
            }
            else {
                stream.invalidate();
            }
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

// ACCESSORS
inline
Date Datetime::date() const
{
    bsls::Types::Uint64 value = updatedRepresentation() & (~k_REP_MASK);

    return Date() + static_cast<int>(value >> k_NUM_TIME_BITS);
}

inline
int Datetime::day() const
{
    return date().day();
}

inline
DayOfWeek::Enum Datetime::dayOfWeek() const
{
    return date().dayOfWeek();
}

inline
int Datetime::dayOfYear() const
{
    return date().dayOfYear();
}

inline
void Datetime::getTime(int *hour,
                       int *minute,
                       int *second,
                       int *millisecond,
                       int *microsecond) const
{
    bsls::Types::Uint64 microseconds = updatedRepresentation() & k_TIME_MASK;

    if (hour) {
        *hour = static_cast<int>(microseconds / TimeUnitRatio::k_US_PER_H);
    }
    if (minute) {
        *minute = static_cast<int>(  microseconds
                                   / TimeUnitRatio::k_US_PER_M
                                   % TimeUnitRatio::k_M_PER_H);
    }
    if (second) {
        *second = static_cast<int>(  microseconds
                                   / TimeUnitRatio::k_US_PER_S
                                   % TimeUnitRatio::k_S_PER_M);
    }
    if (millisecond) {
        *millisecond = static_cast<int>(  microseconds
                                        / TimeUnitRatio::k_US_PER_MS
                                        % TimeUnitRatio::k_MS_PER_S);
    }
    if (microsecond) {
        *microsecond = static_cast<int>(
                                    microseconds % TimeUnitRatio::k_US_PER_MS);
    }
}

inline
int Datetime::hour() const
{
    bsls::Types::Uint64 microseconds = updatedRepresentation() & k_TIME_MASK;

    return static_cast<int>(microseconds / TimeUnitRatio::k_US_PER_H);
}

inline
int Datetime::microsecond() const
{
    bsls::Types::Uint64 microseconds = updatedRepresentation() & k_TIME_MASK;

    return static_cast<int>(microseconds % TimeUnitRatio::k_US_PER_MS);
}

inline
int Datetime::millisecond() const
{
    bsls::Types::Uint64 microseconds = updatedRepresentation() & k_TIME_MASK;

    return static_cast<int>(  microseconds
                            / TimeUnitRatio::k_US_PER_MS
                            % TimeUnitRatio::k_MS_PER_S);
}

inline
int Datetime::minute() const
{
    bsls::Types::Uint64 microseconds = updatedRepresentation() & k_TIME_MASK;

    return static_cast<int>(  microseconds
                            / TimeUnitRatio::k_US_PER_M
                            % TimeUnitRatio::k_M_PER_H);
}

inline
int Datetime::month() const
{
    return date().month();
}

inline
int Datetime::second() const
{
    bsls::Types::Uint64 microseconds = updatedRepresentation() & k_TIME_MASK;

    return static_cast<int>(  microseconds
                            / TimeUnitRatio::k_US_PER_S
                            % TimeUnitRatio::k_S_PER_M);
}

inline
Time Datetime::time() const
{
    int hour;
    int minute;
    int second;
    int millisecond;
    int microsecond;

    getTime(&hour, &minute, &second, &millisecond, &microsecond);

    return Time(hour, minute, second, millisecond, microsecond);
}

inline
int Datetime::year() const
{
    return date().year();
}

                                  // Aspects

template <class STREAM>
STREAM& Datetime::bdexStreamOut(STREAM& stream, int version) const
{
    if (stream) {
        switch (version) { // switch on the schema version
          case 2: {
            bsls::Types::Uint64 value =
                                       updatedRepresentation() & (~k_REP_MASK);

            stream.putUint64(value);
          } break;
          case 1: {
            date().bdexStreamOut(stream, 1);
            time().bdexStreamOut(stream, 1);
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation
inline
int Datetime::maxSupportedBdexVersion()
{
    return maxSupportedBdexVersion(0);
}

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

inline
bsl::ostream& Datetime::streamOut(bsl::ostream& stream) const
{
    return stream << *this;
}

inline
int Datetime::validateAndSetDatetime(int year,
                                     int month,
                                     int day,
                                     int hour,
                                     int minute,
                                     int second,
                                     int millisecond)
{
    return setDatetimeIfValid(year,
                              month,
                              day,
                              hour,
                              minute,
                              second,
                              millisecond);
}

#endif  // BDE_OMIT_INTERNAL_DEPRECATED -- BDE2.22

}  // close package namespace

// FREE OPERATORS
inline
bdlt::Datetime bdlt::operator+(const Datetime&           lhs,
                               const bsls::TimeInterval& rhs)
{
    Datetime result(lhs);

    return result += rhs;
}

inline
bdlt::Datetime bdlt::operator+(const bsls::TimeInterval& lhs,
                               const Datetime&           rhs)
{
    Datetime result(rhs);

    return result += lhs;
}

inline
bdlt::Datetime bdlt::operator+(const Datetime&         lhs,
                               const DatetimeInterval& rhs)
{
    Datetime result(lhs);

    return result += rhs;
}

inline
bdlt::Datetime bdlt::operator+(const DatetimeInterval& lhs,
                               const Datetime&         rhs)
{
    Datetime result(rhs);

    return result += lhs;
}

inline
bdlt::Datetime bdlt::operator-(const Datetime&           lhs,
                               const bsls::TimeInterval& rhs)
{
    Datetime result(lhs);

    return result -= rhs;
}

inline
bdlt::Datetime bdlt::operator-(const Datetime&         lhs,
                               const DatetimeInterval& rhs)
{
    Datetime result(lhs);

    return result -= rhs;
}

inline
bdlt::DatetimeInterval bdlt::operator-(const Datetime& lhs,
                                       const Datetime& rhs)
{
    bsls::Types::Uint64 lhsTotalMicroseconds = lhs.microsecondsFromEpoch();
    bsls::Types::Uint64 rhsTotalMicroseconds = rhs.microsecondsFromEpoch();

    if (lhsTotalMicroseconds >= rhsTotalMicroseconds) {
        lhsTotalMicroseconds -= rhsTotalMicroseconds;

        return bdlt::DatetimeInterval(
                               0, 0, 0, 0, 0, lhsTotalMicroseconds);  // RETURN
    }

    rhsTotalMicroseconds -= lhsTotalMicroseconds;

    return bdlt::DatetimeInterval(
        0, 0, 0, 0, 0, -static_cast<bsls::Types::Int64>(rhsTotalMicroseconds));
}

inline
bool bdlt::operator==(const Datetime& lhs, const Datetime& rhs)
{
    bsls::Types::Uint64 lhsValue = lhs.updatedRepresentation();
    bsls::Types::Uint64 rhsValue = rhs.updatedRepresentation();

    return lhsValue == rhsValue;
}

inline
bool bdlt::operator!=(const Datetime& lhs, const Datetime& rhs)
{
    bsls::Types::Uint64 lhsValue = lhs.updatedRepresentation();
    bsls::Types::Uint64 rhsValue = rhs.updatedRepresentation();

    return lhsValue != rhsValue;
}

inline
bool bdlt::operator<(const Datetime& lhs, const Datetime& rhs)
{
    BSLS_ASSERT_SAFE(24 != lhs.hour());
    BSLS_ASSERT_SAFE(24 != rhs.hour());

    bsls::Types::Uint64 lhsValue = lhs.updatedRepresentation();
    bsls::Types::Uint64 rhsValue = rhs.updatedRepresentation();

    return lhsValue < rhsValue;
}

inline
bool bdlt::operator<=(const Datetime& lhs, const Datetime& rhs)
{
    BSLS_ASSERT_SAFE(24 != lhs.hour());
    BSLS_ASSERT_SAFE(24 != rhs.hour());

    bsls::Types::Uint64 lhsValue = lhs.updatedRepresentation();
    bsls::Types::Uint64 rhsValue = rhs.updatedRepresentation();

    return lhsValue <= rhsValue;
}

inline
bool bdlt::operator>(const Datetime& lhs, const Datetime& rhs)
{
    BSLS_ASSERT_SAFE(24 != lhs.hour());
    BSLS_ASSERT_SAFE(24 != rhs.hour());

    bsls::Types::Uint64 lhsValue = lhs.updatedRepresentation();
    bsls::Types::Uint64 rhsValue = rhs.updatedRepresentation();

    return lhsValue > rhsValue;
}

inline
bool bdlt::operator>=(const Datetime& lhs, const Datetime& rhs)
{
    BSLS_ASSERT_SAFE(24 != lhs.hour());
    BSLS_ASSERT_SAFE(24 != rhs.hour());

    bsls::Types::Uint64 lhsValue = lhs.updatedRepresentation();
    bsls::Types::Uint64 rhsValue = rhs.updatedRepresentation();

    return lhsValue >= rhsValue;
}

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

namespace bslmf {

// TRAITS

/// This template specialization for `IsBitwiseCopyable` indicates that
/// `bdlt::Datetime` is a bitwise copyable type.
template <>
struct IsBitwiseCopyable<BloombergLP::bdlt::Datetime> : bsl::true_type {
};

}  // close namespace bslmf
}  // close enterprise namespace

#endif

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