// bdlt_datetz.h                                                      -*-C++-*-
#ifndef INCLUDED_BDLT_DATETZ
#define INCLUDED_BDLT_DATETZ

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

//@PURPOSE: Provide a representation of a date with time zone offset.
//
//@CLASSES:
//  bdlt::DateTz: local-date value with time zone offset from UTC
//
//@SEE_ALSO: bdlt_date, bdlt_datetimetz, bdlt_datetzformatter, bdlt_formatdoc
//
//@DESCRIPTION: This component provides a single value-semantic class,
// `bdlt::DateTz`, that represents a date value in a particular time zone.
// Each `bdlt::DateTz` object contains a time zone offset from UTC (in minutes)
// and a `bdlt::Date` value in that time zone.  For logical consistency, the
// date value and offset should correspond to a geographically valid time zone,
// but such consistency is the user's responsibility.  This component does not
// enforce logical constraints on any values.
//
///Caveats on Time Zone Support
///----------------------------
// A `bdlt::DateTz` value is intended to be interpreted as a value in a local
// time zone, along with the offset of that value from UTC.  However, there are
// some problems with this simple interpretation.  First of all, the offset
// value may not correspond to any time zone that has ever existed.  For
// example, the offset value could be set to one minute, or to 1,234 minutes.
// The meaning of the resulting "local time" value is always clear, but the
// local time might not correspond to any geographical or historical time zone.
//
// The second problem is more subtle.  A given offset from UTC might be "valid"
// in that it corresponds to a real time zone, but the actual date value might
// not exist in that time zone.  To make matters worse, a "valid" offset may
// not (indeed, rarely will) specify one time zone uniquely.  Moreover, the
// date value might be valid in one time zone corresponding to a given offset,
// and not in another time zone.
//
// For these reasons (and others), this component cannot and does not perform
// any validation relating to time zones or offsets.  The user must take care
// to honor the "local date" contract of this component.
//
///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: Representing Dates In Different Time Zones
///- - - - - - - - - - - - - - - - - - - - - - - - - - -
// Suppose that we need to compare dates in different time zones.  The
// `bdlt::DateTz` type helps us to accomplish this by providing the
// `utcStartTime` method, which returns a `bdlt::Datetime` value corresponding
// to the UTC "point in time" when the local date starts (i.e. 000 hours local
// time).
//
// First, we default construct an object `dateTz1`, which has an offset of 0,
// implying that the object represents a date in the UTC time zone.
// ```
// bdlt::DateTz dateTz1;
// assert(0                   == dateTz1.offset());
// assert(dateTz1.localDate() == dateTz1.utcStartTime().date());
// assert(dateTz1.localDate() == bdlt::Date());
// ```
// Notice the value of a default contructed `bdlt::DateTz` object is the same
// as that of a default constructed `bdlt::Date` object.
//
// Then, we construct two objects `dateTz2` and `dateTz3` to have a local date
// of 2013/12/31 in the EST time zone (UTC-5) and the pacific time zone (UTC-8)
// respectively:
// ```
// bdlt::DateTz dateTz2 (bdlt::Date(2013, 12, 31), -5 * 60);
// bdlt::DateTz dateTz3 (bdlt::Date(2013, 12, 31), -8 * 60);
// ```
// Next, we compare the local dates of the two `DateTz` objects, and verify
// that they compare equal:
// ```
// bdlt::Date localDate(2013, 12, 31);
// assert(localDate == dateTz2.localDate());
// assert(localDate == dateTz3.localDate());
// ```
// Now, we compare the starting time of the two `DateTz` objects using the
// `utcStartTime` method:
// ```
// assert(dateTz2.utcStartTime() < dateTz3.utcStartTime());
// ```

#include <bdlscm_version.h>

#include <bdlt_date.h>
#include <bdlt_datetime.h>

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

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

#include <bsl_iosfwd.h>

namespace BloombergLP {
namespace bdlt {

                             // ============
                             // class DateTz
                             // ============

/// This value-semantic class describes a date value in a particular time
/// zone, which is indicated using an offset from UTC (in minutes).
class DateTz {
    // PRIVATE TYPES

    /// This enumeration specifies the minimum and maximum time zone offset
    /// values.
    enum ValidOffsetRange {
        k_MAX_OFFSET =  1440,
        k_MIN_OFFSET = -1440
    };

    // DATA
    Date d_localDate;  // date value in timezone specified by `d_offset`
    int  d_offset;     // offset from UTC (in minutes)

  public:
    // CLASS METHODS

    /// Return `true` if the specified `localDate` and the specified time
    /// zone `offset` represent a valid `DateTz` value, and `false`
    /// otherwise.  A `localDate` and `offset` represent a valid `DateTz`
    /// value if `offset` is in the range `( -1440 .. 1440 )`.  Note that a
    /// `true` result from this function does not guarantee that `offset`
    /// corresponds to any geographical or historical time zone.  Also note
    /// that a `true` result from this function does not guarantee that
    /// `localDate` itself is a valid `Date` object.
    static bool isValid(const Date& localDate, int offset);

                                  // 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 `DateTz` object having the (default) attribute values.
    DateTz();

    /// Create a `DateTz` object having a local date value equal to the
    /// specified `localDate` and a time zone offset value from UTC equal to
    /// the specified `offset` (in minutes).  The behavior is undefined
    /// unless `offset` is in the range `( -1440 .. 1440 )`.  Note that this
    /// method provides no validation, and it is the user's responsibility
    /// to ensure that `offset` represents a valid time zone and that
    /// `localDate` represents a valid date in that time zone.
    DateTz(const Date& localDate, int offset);

    /// Create a `DateTz` object having the same value as the specified
    /// `original` object.
    DateTz(const DateTz& original);

    // The following destructor is generated by the compiler, except in "SAFE"
    // build modes (e.g., to enable the checking of class invariants).

    /// Destroy this object.
    ~DateTz();

    // MANIPULATORS

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

    /// Set the local date and the time zone offset of this object to the
    /// specified `localDate` and `offset` values respectively.  The
    /// behavior is undefined unless `offset` is in the range
    /// `( -1440 .. 1440 )`.  Note that this method provides no validation,
    /// and it is the user's responsibility to assure the consistency of the
    /// resulting value.
    void setDateTz(const Date& localDate, int offset);

    /// Set the local date and time zone offset of this object to the
    /// specified `localDate` and `offset` values respectively if
    /// `localDate` and `offset` represent a valid `DateTz` value.  Return 0
    /// on success, and a non-zero value with no effect on this `DateTz`
    /// object otherwise.
    int setDateTzIfValid(const Date& localDate, int offset);

                                  // 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 a `Date` object having the value of the local date
    /// represented by this object.  Note that this is the `Date` supplied
    /// at construction and may not correspond to the actual time zone
    /// offset of the local system.
    Date localDate() const;

    /// Return the time zone offset of this `DateTz` object.  Note that the
    /// offset is in minutes from UTC.
    int offset() const;

    /// Return a `Datetime` object having the value of the UTC "point in
    /// time" when the local date starts (i.e., 0000 hours local time).  The
    /// behavior is undefined unless the local date starting time represents
    /// a valid `Datetime` value for the UTC timezone.  Note that the
    /// returned value is equal to:
    /// ```
    /// Datetime(localDate()).addMinutes(-offset());
    /// ```
    Datetime utcStartTime() 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 the 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 a `Datetime` object having the value of the UTC "point in
    /// time" when the local date starts (i.e., 0000 hours local time).  The
    /// behavior is undefined unless the local date starting time represents
    /// a valid `Datetime` value for the UTC timezone.  Note that the
    /// returned value is equal to:
    /// ```
    /// Datetime(localDate()).addMinutes(-offset());
    /// ```
    ///
    /// @DEPRECATED: replaced by `utcStartTime`.
    Datetime gmtStartTime() const;

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

    /// Set the local date and time zone offset of this object to the
    /// specified `localDate` and `offset` values respectively if
    /// `localDate` and `offset` represent a valid `DateTz` value.  Return 0
    /// on success, and a non-zero value with no effect on this `DateTz`
    /// object otherwise.
    int validateAndSetDateTz(const Date& localDate, int offset);

#endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation

};

// FREE OPERATORS

/// Return `true` if the specified `lhs` and `rhs` `DateTz` objects have the
/// same value, and `false` otherwise.  Two `DateTz` objects have the same
/// value if they have the same local date value and the same time zone
/// offset value.
bool operator==(const DateTz& lhs, const DateTz& rhs);

/// Return `true` if the specified `lhs` and `rhs` `DateTz` objects do not
/// have the same value, and `false` otherwise.  Two `DateTz` objects do not
/// have the same value if they do not have the same local date values or
/// the same time zone offset values.
bool operator!=(const DateTz& lhs, const DateTz& rhs);

/// Write the value of the specified `rhs` object to the specified output
/// `stream` in a single-line format, and return a reference providing
/// modifiable access to `stream`.  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.  Also note that this
/// method has the same behavior as `object.print(stream, 0, -1)`, but with
/// the attribute names elided.
bsl::ostream& operator<<(bsl::ostream& stream, const DateTz& rhs);

// 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 `DateTz`.  Note that two
/// objects which represent the same UTC time but have different offsets
/// will not (necessarily) hash to the same value.
template <class HASHALG>
void hashAppend(HASHALG& hashAlg, const DateTz& object);

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

                                // ------------
                                // class DateTz
                                // ------------

// CLASS METHODS
inline
bool DateTz::isValid(const Date&, int offset)
{
     return k_MIN_OFFSET < offset
         && k_MAX_OFFSET > offset;
}

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

// CREATORS
inline
DateTz::DateTz()
: d_localDate()
, d_offset(0)
{
}

inline
DateTz::DateTz(const Date& localDate, int offset)
: d_localDate(localDate)
, d_offset(offset)
{
    BSLS_REVIEW(isValid(localDate, offset));
}

inline
DateTz::DateTz(const DateTz& original)
: d_localDate(original.d_localDate)
, d_offset(original.d_offset)
{
}

inline
DateTz::~DateTz()
{
    BSLS_REVIEW(isValid(d_localDate, d_offset));
}

// MANIPULATORS
inline
DateTz& DateTz::operator=(const DateTz& rhs)
{
    d_localDate = rhs.d_localDate;
    d_offset    = rhs.d_offset;

    return *this;
}

inline
void DateTz::setDateTz(const Date& localDate, int offset)
{
    BSLS_REVIEW(isValid(localDate, offset));

    d_localDate = localDate;
    d_offset    = offset;
}

inline
int DateTz::setDateTzIfValid(const Date& localDate, int offset)
{
    if (isValid(localDate, offset)) {
        setDateTz(localDate, offset);
        return 0;                                                     // RETURN
    }
    return -1;
}

                                  // Aspects

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

            int offset = 0;
            stream.getInt32(offset);

            if (stream && isValid(localDate, offset)) {
                d_localDate = localDate;
                d_offset    = offset;
            }
            else {
                stream.invalidate();
            }
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

// ACCESSORS
inline
Date DateTz::localDate() const
{
    return d_localDate;
}

inline
int DateTz::offset() const
{
    return d_offset;
}

inline
Datetime DateTz::utcStartTime() const
{
    Datetime utc(d_localDate, Time(0,0,0,0));
    utc.addMinutes(-d_offset);
    return utc;
}

                                  // Aspects

template <class STREAM>
STREAM& DateTz::bdexStreamOut(STREAM& stream, int version) const
{
    if (stream) {
        switch (version) { // switch on the schema version
          case 1: {
            d_localDate.bdexStreamOut(stream, 1);
            stream.putInt32(d_offset);
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation
// DEPRECATED
inline
Datetime DateTz::gmtStartTime() const
{
    return utcStartTime();
}

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

inline
int DateTz::validateAndSetDateTz(const Date& localDate, int offset)
{
    if (isValid(localDate, offset)) {
        setDateTz(localDate, offset);
        return 0;                                                     // RETURN
    }
    return -1;
}

#endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation

}  // close package namespace

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

inline
bool bdlt::operator!=(const DateTz& lhs, const DateTz& rhs)
{
    return lhs.offset()    != rhs.offset()
        || lhs.localDate() != rhs.localDate();
}

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

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

namespace bslmf {

// TRAITS

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

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

#endif

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