// bdlt_timetz.h                                                      -*-C++-*-
#ifndef INCLUDED_BDLT_TIMETZ
#define INCLUDED_BDLT_TIMETZ

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

//@PURPOSE: Provide a representation of a time with time zone offset.
//
//@CLASSES:
//  bdlt::TimeTz: local-time value with time zone offset from UTC
//
//@SEE_ALSO: bdlt_time, bdlt_datetimetz
//
//@DESCRIPTION: This component provides a single, simply constrained
// value-semantic class, 'bdlt::TimeTz', that represents a time value in a
// particular time zone.  Each 'bdlt::TimeTz' object contains a time zone
// offset from UTC (in minutes) and a 'bdlt::Time' value in that time zone.
// For logical consistency, the time 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.
//
// The 'localTime' and 'utcTime' methods return 'bdlt::Time' values
// corresponding to the local time and UTC time represented by the object,
// respectively.  In addition, the 'offset' method returns the time zone offset
// in minutes from UTC (i.e., 'UTC + offset' equals local time).
//
///Attributes
///----------
//..
//  Name                Type         Default         Simple Constraints
//  ------------------  -----------  --------------  ------------------
//  localTime           bdlt::Time   '24:00:00.000'  none
//  offset              int          0               ( -1440 .. 1440 )
//..
//: o 'localTime': local time in the timezone described by 'offset'.
//:
//: o 'offset': offset from UTC (in minutes) of the time zone in which
//:   'localTime' occurs.
//
///Caveats on Time Zone Support
///----------------------------
// A 'bdlt::TimeTz' 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.
//
// 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 time" 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: Comparing Times from Multiple Time Zones
///- - - - - - - - - - - - - - - - - - - - - - - - - -
// Some legacy systems may represent points in time as a combination of a local
// time-of-day plus an offset from UTC, with an underlying assumption that the
// dates on which points in time occur can be inferred from context.  Assuming
// that we know that two such times fall on the same (local) calendar date, we
// can determine whether or not the two times coincide by comparing their
// 'bdlt::TimeTz' representations.
//
// First, we define three 'bdlt::TimeTz' objects representing the time in three
// different time zones on the same (local) date:
//..
//  bdlt::TimeTz newYorkTime(bdlt::Time(9, 30, 0, 0.0),
//                           -5 * bdlt::TimeUnitRatio::k_MINUTES_PER_HOUR);
//  bdlt::TimeTz chicagoTime(bdlt::Time(8, 30, 0, 0.0),
//                           -6 * bdlt::TimeUnitRatio::k_MINUTES_PER_HOUR);
//  bdlt::TimeTz phoenixTime(bdlt::Time(6, 30, 0, 0.0),
//                           -7 * bdlt::TimeUnitRatio::k_MINUTES_PER_HOUR);
//..
// Then, we observe that the local times are distinct:
//..
//  assert(newYorkTime.localTime() != chicagoTime.localTime());
//  assert(chicagoTime.localTime() != phoenixTime.localTime());
//  assert(phoenixTime.localTime() != newYorkTime.localTime());
//..
// Next, we observe that 'newYorkTime' and 'chicagoTime' actually represent the
// same point in time:
//..
//  assert(newYorkTime.utcTime() == chicagoTime.utcTime());
//..
// Finally, we observe that 'phoenixTime' is one hour earlier than
// 'newYorkTime':
//..
//  bdlt::DatetimeInterval delta =
//                               newYorkTime.utcTime() - phoenixTime.utcTime();
//
//  assert(0 == delta.days());
//  assert(1 == delta.hours());
//  assert(0 == delta.minutes());
//  assert(0 == delta.seconds());
//  assert(0 == delta.milliseconds());
//..

#include <bdlscm_version.h>

#include <bdlt_time.h>

#include <bslh_hash.h>

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

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

#include <bsl_iosfwd.h>


namespace BloombergLP {
namespace bdlt {

                                // ============
                                // class TimeTz
                                // ============

class TimeTz {
    // This value-semantic class describes a time value in a particular time
    // zone, which is indicated using an offset from UTC (in minutes).  The
    // offset is available via the 'offset' method, and is defined by the
    // relationship: 'localTime() - offset() == utcTime'.  The time and offset
    // values are logically assumed to correspond to geographically valid
    // values, however, this constraint is not enforced.
    //
    // This class:
    //: o supports a complete set of *value-semantic* operations
    //: o supports BDEX streaming
    // For terminology see 'bsldoc_glossary'.

    // PRIVATE TYPES
    enum ValidOffsetRange {
        // This enumeration specifies the minimum and maximum time zone offset
        // values.

        k_MAX_OFFSET =  1440,
        k_MIN_OFFSET = -1440
    };

    // DATA
    Time d_localTime;    // time value in timezone specified by 'd_offset'
    int  d_offset;       // offset from UTC (in minutes)

  public:
    // CLASS METHODS
    static bool isValid(const Time& localTime, int offset);
        // Return 'true' if the specified 'localTime' and the specified time
        // zone 'offset' represent a valid 'TimeTz' value, and 'false'
        // otherwise.  A 'localTime' and 'offset' represent a valid 'TimeTz'
        // value if 'offset' is in the range '( -1440 .. 1440 )', and 'offset'
        // is 0 if 'localTime' has the value '24:00:00.000'.  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
        // 'localTime' itself is a valid 'Time' object.

                               // BDEX Streaming

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

    // CREATORS
    TimeTz();
        // Create a 'TimeTz' object having the (default) attribute values.

    TimeTz(const Time& localTime, int offset);
        // Create a 'TimeTz' object whose local time and offset attributes have
        // the specified 'localTime' and 'offset' values respectively.  The
        // behavior is undefined unless 'offset' is in the range
        // '( -1440 .. 1440 )', and 'offset' is 0 if 'localTime' has the value
        // '24:00:00.000'.  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 'localTime' represents a valid time in that
        // time zone.

    TimeTz(const TimeTz& original);
        // Construct a 'TimeTz' object having the same value as the specified
        // 'original' 'TimeTz' object.

    ~TimeTz();
        // Destroy this object.

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

    void setTimeTz(const Time& localTime, int offset);
        // Set the local time and time zone offset attributes of this object to
        // the specified 'localTime' 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.

    int setTimeTzIfValid(const Time& localTime, int offset);
        // Set the local time and the time zone offset of this object to the
        // specified 'localTime' and 'offset' values respectively if
        // 'localTime' and 'offset' represent a valid 'TimeTz' value, and leave
        // the object unmodified otherwise.  Return 0 on success, and a
        // non-zero value otherwise.

                                  // Aspects

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

    // ACCESSORS
    Time localTime() const;
        // Return a 'Time' object having the value of the local time attribute
        // of this object.  Note that the 'Time' value returned is the value
        // stored in this object, and may be different from the local time of
        // the system.

    int offset() const;
        // Return the time zone offset of this object in minutes from UTC.

    Time utcTime() const;
        // Return a 'Time' object having the value of the UTC time represented
        // by this object.  Note that the returned value is equal to
        // 'localTime() - offset()' minutes.

                                  // Aspects

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

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

#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation

    // DEPRECATED
    Time gmtTime() const;
        // !DEPRECATED!: replaced by 'utcTime'.
        //
        // Return a 'Time' object having the value of the UTC time represented
        // by this object.  Note that the returned value is equal to
        // 'localTime() - offset()' minutes.

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

    int validateAndSetTimeTz(const Time& localTime, int offset);
        // !DEPRECATED!: replaced by 'setTimeTzIfValid'.
        //
        // Set the local time and the time zone offset of this object to the
        // specified 'localTime' and 'offset' values respectively if
        // 'localTime' and 'offset' represent a valid 'TimeTz' value.  Return 0
        // on success, and a non-zero value with no effect on this object
        // otherwise.

#endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation
};

// FREE OPERATORS
bool operator==(const TimeTz& lhs, const TimeTz& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' objects have the same
    // value, and 'false' otherwise.  Two 'TimeTz' objects have the same value
    // if their corresponding 'localTime' and 'offset' attributes have the same
    // values.

bool operator!=(const TimeTz& lhs, const TimeTz& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' objects do not have the
    // same value, and 'false' otherwise.  Two 'TimeTz' objects do not have the
    // same value if any of their corresponding 'localTime' and 'offset'
    // attributes have different values.

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

// FREE FUNCTIONS
template <class HASHALG>
void hashAppend(HASHALG& hashAlg, const TimeTz& object);
    // Pass the specified 'object' to the specified 'hashAlg'.  This function
    // integrates with the 'bslh' modular hashing system and effectively
    // provides a 'bsl::hash' specialization for 'TimeTz'.  Note that two
    // objects which represent the same UTC time but have different offsets
    // will not (necessarily) hash to the same value.

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

                             // ------------
                             // class TimeTz
                             // ------------

// CLASS METHODS
inline
bool TimeTz::isValid(const Time& localTime, int offset)
{
    return offset > k_MIN_OFFSET
        && offset < k_MAX_OFFSET
        && (bdlt::Time() != localTime || 0 == offset);
}

                                  // Aspects

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

// CREATORS
inline
TimeTz::TimeTz()
: d_localTime()
, d_offset(0)
{
}

inline
TimeTz::TimeTz(const Time& localTime, int offset)
: d_localTime(localTime)
, d_offset(offset)
{
    BSLS_REVIEW(isValid(localTime, offset));
}

inline
TimeTz::TimeTz(const TimeTz& original)
: d_localTime(original.d_localTime)
, d_offset(original.d_offset)
{
}

inline
TimeTz::~TimeTz()
{
    BSLS_REVIEW(isValid(d_localTime, d_offset));
}

// MANIPULATORS
inline
TimeTz& TimeTz::operator=(const TimeTz& rhs)
{
    d_localTime = rhs.d_localTime;
    d_offset    = rhs.d_offset;

    return *this;
}

inline
void TimeTz::setTimeTz(const Time& localTime, int offset)
{
    BSLS_REVIEW(isValid(localTime, offset));

    d_localTime = localTime;
    d_offset    = offset;
}

inline
int TimeTz::setTimeTzIfValid(const Time& localTime, int offset)
{
    if (isValid(localTime, offset)) {
        setTimeTz(localTime, offset);

        return 0;                                                     // RETURN
    }
    return -1;
}

                                  // Aspects

template <class STREAM>
STREAM& TimeTz::bdexStreamIn(STREAM& stream, int version)
{
    if (stream) {
        switch (version) { // switch on the schema version
          case 2:
            BSLS_ANNOTATION_FALLTHROUGH;
          case 1: {
            Time time;
            time.bdexStreamIn(stream, version);

            int offset;
            stream.getInt32(offset);

            if (stream && isValid(time, offset)) {
                setTimeTz(time, offset);
            }
            else {
                stream.invalidate();
            }
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

// ACCESSORS
inline
Time TimeTz::localTime() const
{
    return d_localTime;
}

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

inline
Time TimeTz::utcTime() const
{
    Time utc(d_localTime);

    if (d_offset) {
        // N.B. adding -0 minutes to a default-constructed 'Time' object (with
        // value '24:00:00.000') would convert it to a 'Time' object with value
        // '00:00:00.000'.  The branch here preserves the 'localTime' attribute
        // for a default-constructed 'TimeTz' object.

        utc.addMinutes(-d_offset);
    }

    return utc;
}

                                  // Aspects

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

#ifndef BDE_OPENSOURCE_PUBLICATION  // pending deprecation
// DEPRECATED
inline
Time TimeTz::gmtTime() const
{
    return utcTime();
}

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

inline
int TimeTz::validateAndSetTimeTz(const Time& localTime, int offset)
{
    return setTimeTzIfValid(localTime, offset);
}
#endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation

}  // close package namespace

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

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

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

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

}  // close enterprise namespace

namespace bsl {

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

}  // close namespace bsl

#endif

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