// 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       | ""                                 |
//
// * `datetimeTz`: date, time, and offset from UTC of the local time.
// * `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_bslallocator.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>

namespace BloombergLP {
namespace baltzo {

                            // ===================
                            // 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 for information on the class attributes.
class LocalDatetime {

    // 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
    /// Return the most current BDEX streaming version number supported by
    /// this class.
    ///
    /// @DEPRECATED: Use `maxSupportedBdexVersion(int)` instead.
    static int maxSupportedBdexVersion();

#endif // BDE_OMIT_INTERNAL_DEPRECATED -- pending deprecation

    /// 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 `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();
    explicit LocalDatetime(const allocator_type& allocator);

    /// 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 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 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(const LocalDatetime&  original,
                  const allocator_type& allocator = allocator_type());

    /// 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)
                                                         BSLS_KEYWORD_NOEXCEPT;

    /// 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(bslmf::MovableRef<LocalDatetime> original,
                  const allocator_type&            allocator);

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

    // MANIPULATORS

    /// Assign to this object the value of the specified `rhs` object, and
    /// return a reference providing modifiable access to this object.
    LocalDatetime& operator=(const 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).
    LocalDatetime& operator=(bslmf::MovableRef<LocalDatetime> rhs);

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

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

                        // 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);

    /// 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`.
    void swap(LocalDatetime& other);

    // ACCESSORS

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

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

                                // Aspects

                                // Allocator

    /// Return `get_allocator().mechanism()`.
    ///
    /// @DEPRECATED: Use `get_allocator()` instead.
    bslma::Allocator *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.
    allocator_type get_allocator() const;

                                // Output

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

// FREE OPERATORS

/// 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.
bool operator!=(const LocalDatetime& lhs, const LocalDatetime& rhs);

/// 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.
bsl::ostream& operator<<(bsl::ostream& stream, const LocalDatetime& object);

// FREE FUNCTIONS

/// 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.
void swap(LocalDatetime& a, LocalDatetime& b);

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