// baltzo_localdatetime.h                                             -*-C++-*-
#ifndef INCLUDED_BALTZO_LOCALDATETIME
#define INCLUDED_BALTZO_LOCALDATETIME

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

//@PURPOSE: Provide an attribute class for time-zone-aware datetime values.
//
//@CLASSES:
//  baltzo::LocalDatetime: time-zone-aware datetime type
//
//@SEE_ALSO: bdlt_datetimetz
//
//@DESCRIPTION: This component provides a single, unconstrained
// (value-semantic) attribute class, 'baltzo::LocalDatetime', that is used to
// encapsulate a date, time, offset from UTC (Coordinated Universal Time), and
// a time zone (string) identifier.  The date, time, and offset from UTC are
// contained within a 'bdlt::DatetimeTz' object, which together represents a
// wall-clock time in a given time zone.  This component differs from
// 'bdlt_datetimetz' in that it provides, as part of the value, a string
// identifier for the corresponding time zone.
//
///Attributes
///----------
//..
//  Name          Type               Default
//  ----------    ---------------    ----------------------------------
//  datetimeTz    bdlt::DatetimeTz   January 1, 0001, 24:00:00.000+0000
//  timeZoneId    bsl::string        ""
//..
//: o 'datetimeTz': date, time, and offset from UTC of the local time.
//:
//: o 'timeZoneId': unique identifier representing the local time zone.
//
// For example, in New York on January 1, 2011, at 10 a.m. the local offset
// from UTC is -5 hours, and a standard time zone identifier for New York is
// "America/New_York".  We can represent this information using a
// 'baltzo::LocalDatetime' object whose 'datetimeTz' attribute is
// "01JAN2011_10:00:00.000-0005" and whose 'timeZoneId' attribute is
// "America/New_York".
//
// Note that it is up to the user to ensure that the 'datetimeTz' and
// 'timeZoneId' attributes are consistent as the 'baltzo::LocalDatetime' object
// itself does not maintain any invariants with respect to their values.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Creation and Use of a 'baltzo::LocalDatetime' Object
///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// First, we default-construct a 'baltzo::LocalDatetime' object:
//..
//  baltzo::LocalDatetime localDatetime;
//..
// Next, we update the time referred to by 'localDatetime' to the New York
// time "December 25, 2009, 11:00" with the time-zone identifier set to
// "America/New_York":
//..
//  bdlt::Datetime   datetime(2009, 12, 25, 11, 00, 00);
//  bdlt::DatetimeTz datetimeTz(datetime, -5 * 60);  // offset is specified
//                                                   // in minutes from UTC
//  bsl::string      timeZoneId("America/New_York");
//  localDatetime.setDatetimeTz(datetimeTz);
//  localDatetime.setTimeZoneId(timeZoneId);
//
//  assert(datetimeTz == localDatetime.datetimeTz());
//  assert(timeZoneId == localDatetime.timeZoneId());
//..
// Now, we change the time-zone identifier to another string, for example
// "Europe/Berlin":
//..
//  bsl::string anotherTimeZoneId("Europe/Berlin");
//  localDatetime.setTimeZoneId(anotherTimeZoneId);
//
//  assert(datetimeTz        == localDatetime.datetimeTz());
//  assert(anotherTimeZoneId == localDatetime.timeZoneId());
//..
// Finally, we stream 'localDatetime' to 'bsl::cout':
//..
//  bsl::cout << localDatetime << bsl::endl;
//..
// This statement produces the following output on 'stdout':
//..
//  [ 25DEC2009_11:00:00.000-0500 "Europe/Berlin" ]
//..

#include <balscm_version.h>

#include <bdlt_datetimetz.h>

#include <bslalg_swaputil.h>

#include <bslma_allocator.h>
#include <bslma_stdallocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_isbitwisemoveable.h>
#include <bslmf_movableref.h>
#include <bslmf_nestedtraitdeclaration.h>

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

#include <bslx_instreamfunctions.h>
#include <bslx_outstreamfunctions.h>

#include <bsl_iosfwd.h>
#include <bsl_string.h>

#ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES
#include <bslalg_typetraits.h>

#include <bsl_algorithm.h>
#endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES

namespace BloombergLP {
namespace baltzo {

                            // ===================
                            // class LocalDatetime
                            // ===================

class LocalDatetime {
    // This unconstrained (value-semantic) attribute class characterizes a date
    // time, offset from UTC, and a time zone identifier represented by a
    // string.  See the Attributes section under @DESCRIPTION in the
    // component-level documentation.

    // DATA
    bdlt::DatetimeTz d_datetimeTz;  // local date-time and offset from UTC
    bsl::string      d_timeZoneId;  // local time-zone identifier

    // FRIENDS
    friend void swap(LocalDatetime&, LocalDatetime&);

  public:
    // TYPES
    typedef bsl::allocator<char> allocator_type;

    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(LocalDatetime, bslma::UsesBslmaAllocator);
    BSLMF_NESTED_TRAIT_DECLARATION(LocalDatetime, bslmf::IsBitwiseMoveable);

    // CLASS METHODS

                        // Aspects

#ifndef BDE_OMIT_INTERNAL_DEPRECATED  // pending deprecation

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

#endif // BDE_OMIT_INTERNAL_DEPRECATED -- pending deprecation

    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
    LocalDatetime();
    explicit LocalDatetime(const allocator_type& allocator);
        // Create a 'LocalDatetime' object having the (default) attribute
        // values:
        //..
        //  datetimeTz() == bdlt::DatetimeTz()
        //  timeZoneId() == ""
        //..
        // Optionally specify an 'allocator' (e.g., the address of a
        // 'bslma::Allocator' object) to supply memory; otherwise, the default
        // allocator is used.

    LocalDatetime(const bdlt::DatetimeTz&   datetimeTz,
                  const bsl::string_view&   timeZoneId,
                  const allocator_type&     allocator = allocator_type());
    LocalDatetime(const bdlt::DatetimeTz&   datetimeTz,
                  const char               *timeZoneId,
                  const allocator_type&     allocator = allocator_type());
        // Create a 'LocalDatetime' object with attribute values set from the
        // specified 'datetimeTz' and 'timeZoneId'.  Optionally specify an
        // 'allocator' (e.g., the address of a 'bslma::Allocator' object) to
        // supply memory; otherwise, the default allocator is used.  If
        // 'timeZoneId' is passed as a null pointer, it is treated as an empty
        // string.

    LocalDatetime(const LocalDatetime&  original,
                  const allocator_type& allocator = allocator_type());
        // Create a 'LocalDatetime' object having the same value as the
        // specified 'original' object.  Optionally specify an 'allocator'
        // (e.g., the address of a 'bslma::Allocator' object) to supply memory;
        // otherwise, the default allocator is used.

    LocalDatetime(bslmf::MovableRef<LocalDatetime> original)
                                                         BSLS_KEYWORD_NOEXCEPT;
        // Create a 'LocalDatetime' object having the same value and the same
        // allocator as the specified 'original' object.  The value of
        // 'original' becomes unspecified but valid, and its allocator remains
        // unchanged.

    LocalDatetime(bslmf::MovableRef<LocalDatetime> original,
                  const allocator_type&            allocator);
        // Create a 'LocalDatetime' object having the same value as the
        // specified 'original' object, using the specified 'allocator' (e.g.,
        // the address of a 'bslma::Allocator' object) to supply memory.  The
        // allocator of 'original' remains unchanged.  If 'original' and the
        // newly created object have the same allocator then the value of
        // 'original' becomes unspecified but valid, and no exceptions will be
        // thrown; otherwise 'original' is unchanged and an exception may be
        // thrown.

    ~LocalDatetime();
        // Destroy this object.

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

    LocalDatetime& operator=(bslmf::MovableRef<LocalDatetime> rhs);
        // Assign to this object the value of the specified 'rhs' object, and
        // return a non-'const' reference to this object.  The allocators of
        // this object and 'rhs' both remain unchanged.  If 'rhs' and this
        // object have the same allocator then the value of 'rhs' becomes
        // unspecified but valid, and no exceptions will be thrown; otherwise
        // 'rhs' is unchanged (and an exception may be thrown).

    void setDatetimeTz(const bdlt::DatetimeTz& value);
        // Set the 'datetimeTz' attribute of this object to the specified
        // 'value'.

    void setTimeZoneId(const bsl::string_view&  value);
    void setTimeZoneId(const char              *value);
        // Set the 'timeZoneId' attribute of this object to the specified
        // 'value'.  If 'value' is null, it is treated as an empty string.

                        // 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.

    void swap(LocalDatetime& other);
        // Efficiently exchange the value of this object with the value of the
        // specified 'other' object.  This method provides the no-throw
        // exception-safety guarantee.  The behavior is undefined unless this
        // object was created with the same allocator as was 'other'.

    // ACCESSORS
    const bdlt::DatetimeTz& datetimeTz() const;
        // Return a reference providing non-modifiable access to the
        // 'datetimeTz' attribute of this object.

    const bsl::string& timeZoneId() const;
        // Return a reference providing non-modifiable access to the
        // 'timeZoneId' attribute of this object.

                                // Aspects

                                // Allocator

    bslma::Allocator *allocator() const;
        // !DEPRECATED!: Use 'get_allocator()' instead.
        //
        // Return 'get_allocator().mechanism()'.

    allocator_type get_allocator() const;
        // Return the allocator used by this object to supply memory.  Note
        // that if no allocator was supplied at construction the default
        // allocator in effect at construction is used.

                                // Output

    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 providing modifiable
        // access 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.
};

// FREE OPERATORS
bool operator==(const LocalDatetime& lhs, const LocalDatetime& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' objects have the same
    // value, and 'false' otherwise.  Two 'LocalDatetime' objects have the
    // same value if all of the corresponding values of their 'datetimeTz' and
    // 'timeZoneId' attributes are the same.

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

bsl::ostream& operator<<(bsl::ostream& stream, const LocalDatetime& 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)' with the
    // attribute names elided.

// FREE FUNCTIONS
void swap(LocalDatetime& a, LocalDatetime& b);
    // Exchange the values of the specified 'a' and 'b' objects.  This function
    // provides the no-throw exception-safety guarantee if the two objects were
    // created with the same allocator and the basic guarantee otherwise.

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

                            // -------------------
                            // class LocalDatetime
                            // -------------------

// CLASS METHODS

                        // Aspects

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

// CREATORS
inline
LocalDatetime::LocalDatetime()
: d_datetimeTz()
, d_timeZoneId()
{
}

inline
LocalDatetime::LocalDatetime(const allocator_type& allocator)
: d_datetimeTz()
, d_timeZoneId(allocator)
{
}

inline
LocalDatetime::LocalDatetime(const bdlt::DatetimeTz& datetimeTz,
                             const bsl::string_view& timeZoneId,
                             const allocator_type&   allocator)
: d_datetimeTz(datetimeTz)
, d_timeZoneId(timeZoneId.begin(), timeZoneId.end(), allocator)
{
}

inline
LocalDatetime::LocalDatetime(const bdlt::DatetimeTz&  datetimeTz,
                             const char              *timeZoneId,
                             const allocator_type&    allocator)
: d_datetimeTz(datetimeTz)
, d_timeZoneId(allocator)
{
    if (timeZoneId) {
        bsl::string(timeZoneId, allocator).swap(d_timeZoneId);
    }
}

inline
LocalDatetime::LocalDatetime(const LocalDatetime&  original,
                             const allocator_type& allocator)
: d_datetimeTz(original.d_datetimeTz)
, d_timeZoneId(original.d_timeZoneId, allocator)
{
}

inline
LocalDatetime::LocalDatetime(bslmf::MovableRef<LocalDatetime> original)
                                                          BSLS_KEYWORD_NOEXCEPT
: d_datetimeTz(bslmf::MovableRefUtil::move(
      bslmf::MovableRefUtil::access(original).d_datetimeTz))
, d_timeZoneId(bslmf::MovableRefUtil::move(
      bslmf::MovableRefUtil::access(original).d_timeZoneId))
{
}

inline
LocalDatetime::LocalDatetime(bslmf::MovableRef<LocalDatetime> original,
                             const allocator_type&            allocator)
: d_datetimeTz(bslmf::MovableRefUtil::move(
      bslmf::MovableRefUtil::access(original).d_datetimeTz))
, d_timeZoneId(bslmf::MovableRefUtil::move(
      bslmf::MovableRefUtil::access(original).d_timeZoneId), allocator)
{
}

inline
LocalDatetime::~LocalDatetime()
{
}

// MANIPULATORS
inline
LocalDatetime& LocalDatetime::operator=(const LocalDatetime& rhs)
{
    d_timeZoneId = rhs.d_timeZoneId;  // first to allow strong guarantee
    d_datetimeTz = rhs.d_datetimeTz;
    return *this;
}

inline
LocalDatetime& LocalDatetime::operator=(bslmf::MovableRef<LocalDatetime> rhs)
{
    // Move 'd_timeZoneId' first for strong exception guarantee.

    d_timeZoneId = bslmf::MovableRefUtil::move(
        bslmf::MovableRefUtil::access(rhs).d_timeZoneId);

    d_datetimeTz = bslmf::MovableRefUtil::move(
        bslmf::MovableRefUtil::access(rhs).d_datetimeTz);

    return *this;
}

inline
void LocalDatetime::setDatetimeTz(const bdlt::DatetimeTz& value)
{
    d_datetimeTz = value;
}

inline
void LocalDatetime::setTimeZoneId(const bsl::string_view& value)
{
    // The swap ensures that the if the old value of 'd_timeZoneId' was longer
    // than the new one, space won't be wasted.

    bsl::string(value, d_timeZoneId.get_allocator().mechanism()).swap(
                                                                 d_timeZoneId);
}

inline
void LocalDatetime::setTimeZoneId(const char *value)
{
    if (value) {
        d_timeZoneId.assign(value);
    }
    else {
        d_timeZoneId.clear();
    }
}

                        // Aspects

template <class STREAM>
STREAM& LocalDatetime::bdexStreamIn(STREAM& stream, int version)
{
    if (stream) {
        switch (version) {
          case 1: {
            bslx::InStreamFunctions::bdexStreamIn(stream, d_timeZoneId, 1);
            bslx::InStreamFunctions::bdexStreamIn(stream, d_datetimeTz, 1);
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

inline
void LocalDatetime::swap(LocalDatetime& other)
{
    BSLS_ASSERT(get_allocator() == other.get_allocator());

    bslalg::SwapUtil::swap(&d_datetimeTz, &other.d_datetimeTz);
    bslalg::SwapUtil::swap(&d_timeZoneId, &other.d_timeZoneId);
}

// ACCESSORS
inline
const bdlt::DatetimeTz& LocalDatetime::datetimeTz() const
{
    return d_datetimeTz;
}

inline
const bsl::string& LocalDatetime::timeZoneId() const
{
    return d_timeZoneId;
}

                                // Aspects

                                // Allocator

inline
bslma::Allocator *LocalDatetime::allocator() const
{
    return get_allocator().mechanism();
}

inline
LocalDatetime::allocator_type LocalDatetime::get_allocator() const
{
    return d_timeZoneId.get_allocator();
}

                                // Output

template <class STREAM>
STREAM& LocalDatetime::bdexStreamOut(STREAM& stream, int version) const
{
    if (stream) {
        switch (version) {
          case 1: {
            bslx::OutStreamFunctions::bdexStreamOut(stream, d_timeZoneId, 1);
            bslx::OutStreamFunctions::bdexStreamOut(stream, d_datetimeTz, 1);
          } break;
          default: {
            stream.invalidate();  // unrecognized version number
          }
        }
    }
    return stream;
}

#ifndef BDE_OMIT_INTERNAL_DEPRECATED  // pending deprecation

// DEPRECATED METHODS
inline
int LocalDatetime::maxSupportedBdexVersion()
{
    return maxSupportedBdexVersion(0);
}

#endif // BDE_OMIT_INTERNAL_DEPRECATED -- pending deprecation

}  // close package namespace

// FREE OPERATORS
inline
bool baltzo::operator==(const LocalDatetime& lhs, const LocalDatetime& rhs)
{
    return lhs.datetimeTz() == rhs.datetimeTz()
        && lhs.timeZoneId() == rhs.timeZoneId();
}

inline
bool baltzo::operator!=(const LocalDatetime& lhs, const LocalDatetime& rhs)
{
    return lhs.datetimeTz() != rhs.datetimeTz()
        || lhs.timeZoneId() != rhs.timeZoneId();
}

// FREE FUNCTIONS
inline
void baltzo::swap(LocalDatetime& a, LocalDatetime& b)
{
    bslalg::SwapUtil::swap(&a.d_datetimeTz, &b.d_datetimeTz);
    bslalg::SwapUtil::swap(&a.d_timeZoneId, &b.d_timeZoneId);
}

}  // close enterprise namespace

#endif

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