// bdlt_iso8601utilconfiguration.h                                    -*-C++-*-
#ifndef INCLUDED_BDLT_ISO8601UTILCONFIGURATION
#define INCLUDED_BDLT_ISO8601UTILCONFIGURATION

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

//@PURPOSE: Provide an attribute class to configure ISO 8601 string generation.
//
//@CLASSES:
//  bdlt::Iso8601UtilConfiguration: configuration for ISO 8601 strings
//
//@SEE_ALSO: bdlt_iso8601util, bdlt_iso8601utilparseconfiguration
//
//@DESCRIPTION: This component provides an unconstrained (value-semantic)
// attribute class, `bdlt::Iso8601UtilConfiguration`, that may be used to
// configure various aspects of generated ISO 8601 strings.
//
///Attributes
///----------
//
// | Name                      | Type | Default |
// | ------------------------- | ---- | ------- |
// | fractionalSecondPrecision | int  | 3       |
// | omitColonInZoneDesignator | bool | false   |
// | useCommaForDecimalSign    | bool | false   |
// | useZAbbreviationForUtc    | bool | false   |
//
// * `fractionalSecondPrecision`: number of digits used to represent
//   fractional seconds; must be in the range `0 .. 6`.
// * `omitColonInZoneDesignator`: `true` if `:` should be omitted from zone
//   designators.
// * `useCommaForDecimalSign`: `true` if `,` should be used as the decimal
//   sign in fractional seconds; otherwise, `.` should be used.
// * `useZAbbreviationForUtc`: `true` if `Z` should be used for the zone
//   designator instead of `+00:00` (specific to UTC).
//
///Default Configuration
///---------------------
// This component also provides a (process-wide) default configuration that may
// be set and retrieved via the `setDefaultConfiguration` and
// `defaultConfiguration` class methods, respectively.  See Usage Example 2 for
// further details.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Configuring ISO 8601 String Generation
///- - - - - - - - - - - - - - - - - - - - - - - - -
// This example demonstrates creation of a `bdlt::Iso8601UtilConfiguration`
// object that may be used to influence the format of the output produced by a
// hypothetical utility, `my::Iso8601Util`, that generates and parses ISO 8601
// strings for `bdlt` vocabulary types (see `bdlt_iso8601util`, which provides
// just such functionality).  In particular, suppose that given a sample
// `bdlt::TimeTz` object:
// ```
// const bdlt::TimeTz timeTz(bdlt::Time(8, 59, 59, 123), 240);
// ```
// `my::Iso8601Util` produces, by default, the following (valid) ISO 8601
// string:
// ```
// 08:59:59.123+04:00
// ```
// However, we would like to produce the following (also valid) ISO 8601 string
// instead:
// ```
// 08:59:59,123+0400
// ```
// `bdlt::Iso8601UtilConfiguration` can be used to obtain the desired result
// assuming that `my::Iso8601Util` uses `bdlt::Iso8601UtilConfiguration` to
// affect the format of generated strings in this fashion (e.g., again see
// `bdlt_iso8601util`).
//
// First, we construct a `bdlt::Iso8601UtilConfiguration` object that has the
// default value:
// ```
// bdlt::Iso8601UtilConfiguration configuration;
// assert( configuration.fractionalSecondPrecision() == 3);
// assert(!configuration.omitColonInZoneDesignator());
// assert(!configuration.useCommaForDecimalSign());
// assert(!configuration.useZAbbreviationForUtc());
// ```
// Then, we modify `configuration` to indicate that we want to use `,` as the
// decimal sign (in fractional seconds):
// ```
// configuration.setUseCommaForDecimalSign(true);
// assert( configuration.fractionalSecondPrecision() == 3);
// assert(!configuration.omitColonInZoneDesignator());
// assert( configuration.useCommaForDecimalSign());
// assert(!configuration.useZAbbreviationForUtc());
// ```
// Finally, we modify `configuration` to indicate that we want to omit the `:`
// in zone designators:
// ```
// configuration.setOmitColonInZoneDesignator(true);
// assert( configuration.fractionalSecondPrecision() == 3);
// assert( configuration.omitColonInZoneDesignator());
// assert( configuration.useCommaForDecimalSign());
// assert(!configuration.useZAbbreviationForUtc());
// ```
// Our `configuration` object can now be supplied to `my::Iso8601Util` to
// produce the desired result.
//
///Example 2: Setting the Process-Wide Default Configuration
///- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// This example demonstrates how to establish the process-wide default
// configuration.
//
// First, we retrieve the default configuration in effect at process start-up
// and note that it has the default-constructed value:
// ```
// bdlt::Iso8601UtilConfiguration configuration =
//                     bdlt::Iso8601UtilConfiguration::defaultConfiguration();
// assert(bdlt::Iso8601UtilConfiguration() == configuration);
// assert( configuration.fractionalSecondPrecision() == 3);
// assert(!configuration.omitColonInZoneDesignator());
// assert(!configuration.useCommaForDecimalSign());
// assert(!configuration.useZAbbreviationForUtc());
// ```
// Next, we modify `configuration` to indicate that we want to output `Z` when
// the zone designator is UTC (i.e., instead of `+00:00`):
// ```
// configuration.setUseZAbbreviationForUtc(true);
// assert( configuration.fractionalSecondPrecision() == 3);
// assert(!configuration.omitColonInZoneDesignator());
// assert(!configuration.useCommaForDecimalSign());
// assert( configuration.useZAbbreviationForUtc());
// ```
// Then, we modify `configuration` to display milliseconds:
// ```
// configuration.setFractionalSecondPrecision(6);
// assert( configuration.fractionalSecondPrecision() == 6);
// assert(!configuration.omitColonInZoneDesignator());
// assert(!configuration.useCommaForDecimalSign());
// assert( configuration.useZAbbreviationForUtc());
// ```
// Now, we set the default configuration to the value of our `configuration`
// object:
// ```
// bdlt::Iso8601UtilConfiguration::setDefaultConfiguration(configuration);
// ```
// Finally, we verify that the default configuration was updated as expected:
// ```
// const bdlt::Iso8601UtilConfiguration newConfiguration =
//                     bdlt::Iso8601UtilConfiguration::defaultConfiguration();
// assert( newConfiguration.fractionalSecondPrecision() == 6);
// assert(!newConfiguration.omitColonInZoneDesignator());
// assert(!newConfiguration.useCommaForDecimalSign());
// assert( newConfiguration.useZAbbreviationForUtc());
// ```
// Note that the expected usage is that the process-wide configuration will be
// established *once*, early in `main`, and not changed throughout the lifetime
// of a process.

#include <bdlscm_version.h>

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

#include <bsl_iosfwd.h>

namespace BloombergLP {
namespace bdlt {

                      // ==============================
                      // class Iso8601UtilConfiguration
                      // ==============================

/// This unconstrained (value-semantic) attribute class characterizes how to
/// configure certain behavior in `Iso8601Util` functions.  Currently, only the
/// `generate` and `generateRaw` methods of that utility are affected by
/// `Iso8601UtilConfiguration` settings.  See the [](#Attributes) section for
/// information on the class attributes.
class Iso8601UtilConfiguration {

  private:
    // PRIVATE TYPES

    /// This enumeration denotes the distinct bits that define the values of
    /// each of the four configuration attributes.
    enum {

        k_FRACTIONAL_SECOND_PRECISION_MASK  = 0x07,
        k_OMIT_COLON_IN_ZONE_DESIGNATOR_BIT = 0x08,
        k_USE_COMMA_FOR_DECIMAL_SIGN_BIT    = 0x10,
        k_USE_Z_ABBREVIATION_FOR_UTC_BIT    = 0x20
    };

    // CLASS DATA
    static bsls::AtomicOperations::AtomicTypes::Int
                    s_defaultConfiguration;  // process-wide configuration

    // DATA
    int             d_configurationMask;     // bitmask defining configuration

    // FRIENDS
    friend bool operator==(const Iso8601UtilConfiguration&,
                           const Iso8601UtilConfiguration&);
    friend bool operator!=(const Iso8601UtilConfiguration&,
                           const Iso8601UtilConfiguration&);

  private:
    // PRIVATE CREATORS

    /// Create an `Iso8601UtilConfiguration` object having the value indicated
    /// by the specified `configurationMask`.  The behavior is undefined unless
    /// `configurationMask` represents a valid `Iso8601UtilConfiguration`
    /// value.
    explicit Iso8601UtilConfiguration(int configurationMask);

  public:
    // CLASS METHODS

    /// Return the value of the process-wide `Iso8601UtilConfiguration` that is
    /// currently in effect.
    static Iso8601UtilConfiguration defaultConfiguration();

    /// Set the value of the process-wide `Iso8601UtilConfiguration` to the
    /// specified `configuration`.  Note that the expected usage is that the
    /// process-wide configuration will be established *once*, early in `main`,
    /// and not changed throughout the lifetime of a process.
    static void setDefaultConfiguration(
                                const Iso8601UtilConfiguration& configuration);

    // CREATORS

    /// Create an `Iso8601UtilConfiguration` object having the (default)
    /// attribute values:
    /// ```
    /// fractionalSecondPrecision() == 3
    /// omitColonInZoneDesignator() == false
    /// useCommaForDecimalSign()    == false
    /// useZAbbreviationForUtc()    == false
    /// ```
    Iso8601UtilConfiguration();

    /// Create an `Iso8601UtilConfiguration` object having the value of the
    /// specified `original` configuration.
    Iso8601UtilConfiguration(const Iso8601UtilConfiguration& original);

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

    // MANIPULATORS

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

    /// Set the `fractionalSecondPrecision` attribute of this object to the
    /// specified `value`.  The behavior is undefined unless `0 <= value` and
    /// `6 >= value`.
    void setFractionalSecondPrecision(int value);

    /// Set the `omitColonInZoneDesignator` attribute of this object to the
    /// specified `value`.
    void setOmitColonInZoneDesignator(bool value);

    /// Set the `useCommaForDecimalSign` attribute of this object to the
    /// specified `value`.
    void setUseCommaForDecimalSign(bool value);

    /// Set the `useZAbbreviationForUtc` attribute of this object to the
    /// specified `value`.
    void setUseZAbbreviationForUtc(bool value);

    // ACCESSORS

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

    /// Return the value of the `omitColonInZoneDesignator` attribute of this
    /// object.
    bool omitColonInZoneDesignator() const;

    /// Return the value of the `useCommaForDecimalSign` attribute of this
    /// object.
    bool useCommaForDecimalSign() const;

    /// Return the value of the `useZAbbreviationForUtc` attribute of this
    /// object.
    bool useZAbbreviationForUtc() const;

                                  // Aspects

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

// FREE OPERATORS

/// Return `true` if the specified `lhs` and `rhs` objects have the same value,
/// and `false` otherwise.  Two `Iso8601UtilConfiguration` objects have the
/// same value if each of their `fractionalSecondPrecision`,
/// `omitColonInZoneDesignator`, `useCommaForDecimalSign`, and
/// `useZAbbreviationForUtc` attributes (respectively) have the same value.
bool operator==(const Iso8601UtilConfiguration& lhs,
                const Iso8601UtilConfiguration& rhs);

/// Return `true` if the specified `lhs` and `rhs` objects do not have the same
/// value, and `false` otherwise.  Two `Iso8601UtilConfiguration` objects do
/// not have the same value if any of their `fractionalSecondPrecision`,
/// `omitColonInZoneDesignator`, `useCommaForDecimalSign`, or
/// `useZAbbreviationForUtc` attributes (respectively) do not have the same
/// value.
bool operator!=(const Iso8601UtilConfiguration& lhs,
                const Iso8601UtilConfiguration& rhs);

/// Write the value of the specified `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 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 Iso8601UtilConfiguration& object);

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

                      // ------------------------------
                      // class Iso8601UtilConfiguration
                      // ------------------------------

// PRIVATE CREATORS
inline
Iso8601UtilConfiguration::Iso8601UtilConfiguration(int configurationMask)
: d_configurationMask(configurationMask)
{
    BSLS_ASSERT(0 == (configurationMask
                           & ~(k_FRACTIONAL_SECOND_PRECISION_MASK
                             | k_OMIT_COLON_IN_ZONE_DESIGNATOR_BIT
                             | k_USE_COMMA_FOR_DECIMAL_SIGN_BIT
                             | k_USE_Z_ABBREVIATION_FOR_UTC_BIT)));
}

// CLASS METHODS
inline
Iso8601UtilConfiguration Iso8601UtilConfiguration::defaultConfiguration()
{
    return Iso8601UtilConfiguration(
               bsls::AtomicOperations::getIntRelaxed(&s_defaultConfiguration));
}

inline
void Iso8601UtilConfiguration::setDefaultConfiguration(
                                 const Iso8601UtilConfiguration& configuration)
{
    bsls::AtomicOperations::setIntRelaxed(&s_defaultConfiguration,
                                          configuration.d_configurationMask);
}

// CREATORS
inline
Iso8601UtilConfiguration::Iso8601UtilConfiguration()
: d_configurationMask(3)
{
}

inline
Iso8601UtilConfiguration::Iso8601UtilConfiguration(
                                      const Iso8601UtilConfiguration& original)
: d_configurationMask(original.d_configurationMask)
{
}

inline
Iso8601UtilConfiguration::~Iso8601UtilConfiguration()
{
    BSLS_ASSERT(0 == (d_configurationMask
                           & ~(k_FRACTIONAL_SECOND_PRECISION_MASK
                             | k_OMIT_COLON_IN_ZONE_DESIGNATOR_BIT
                             | k_USE_COMMA_FOR_DECIMAL_SIGN_BIT
                             | k_USE_Z_ABBREVIATION_FOR_UTC_BIT)));
}

// MANIPULATORS
inline
Iso8601UtilConfiguration& Iso8601UtilConfiguration::operator=(
                                           const Iso8601UtilConfiguration& rhs)
{
    d_configurationMask = rhs.d_configurationMask;

    return *this;
}

// ACCESSORS
inline
int Iso8601UtilConfiguration::fractionalSecondPrecision() const
{
    return d_configurationMask & k_FRACTIONAL_SECOND_PRECISION_MASK;
}

inline
bool Iso8601UtilConfiguration::omitColonInZoneDesignator() const
{
    return d_configurationMask & k_OMIT_COLON_IN_ZONE_DESIGNATOR_BIT;
}

inline
bool Iso8601UtilConfiguration::useCommaForDecimalSign() const
{
    return d_configurationMask & k_USE_COMMA_FOR_DECIMAL_SIGN_BIT;
}

inline
bool Iso8601UtilConfiguration::useZAbbreviationForUtc() const
{
    return d_configurationMask & k_USE_Z_ABBREVIATION_FOR_UTC_BIT;
}

}  // close package namespace

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

inline
bool bdlt::operator!=(const Iso8601UtilConfiguration& lhs,
                      const Iso8601UtilConfiguration& rhs)
{
    return lhs.d_configurationMask != rhs.d_configurationMask;
}

}  // close enterprise namespace

#endif

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