// baltzo_zoneinfoutil.h                                              -*-C++-*-
#ifndef INCLUDED_BALTZO_ZONEINFOUTIL
#define INCLUDED_BALTZO_ZONEINFOUTIL

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

//@PURPOSE: Provide utility operations on `baltzo::Zoneinfo` objects.
//
//@CLASSES:
//  baltzo::ZoneinfoUtil: utility for operations on a `baltzo::Zoneinfo` object
//
//@SEE_ALSO: baltzo_zoneinfo, baltzo_localtimevalidity
//
//@DESCRIPTION: This component provides a suite of pure functions that operate
// on objects of type `baltzo::Zoneinfo`.  A `baltzo::Zoneinfo` is a value
// semantic-type providing information about a time zone, mirroring the
// information found in the Zoneinfo Database, a public-domain distribution of
// time zone data (see `baltzo_zoneinfo` for more information).  The primary
// functions provided by `baltzo::ZoneinfoUtil` are `convertUtcToLocalTime` and
// `loadRelevantTransitions`: `convertUtcToLocalTime` converts a UTC time into
// its corresponding local time in the time zone described by the supplied
// `baltzo::Zoneinfo` object; `loadRelevantTransitions` returns the transition,
// from the supplied `baltzo::Zoneinfo` object's list of transitions that
// describes the attributes of local time in effect at supplied local time;
// returning two possible transitions in instances where the supplied local
// time is either invalid or ambiguous (see `baltzo_localtimevalidity`).  Note
// that the time supplied as input to `convertUtcToLocalTime` is a *UTC* time,
// whereas the time supplied as input to `loadRelevantTransitions` is a *local*
// time.
//
///Determining Relevant Transitions with `loadRelevantTransitions`
///---------------------------------------------------------------
// The function `loadRelevantTransitions` is used to find the transition in a
// `baltzo::Zoneinfo` object that describes the properties of a client supplied
// local time value.  In instances where the client supplied local time is
// either ambiguous or invalid `loadRelevantTransitions` returns an alternative
// transition that refers to a second set of properties that could be used to
// describe the supplied local time.  To understand why multiple transitions
// might be needed, consider the impact of daylight-saving time transitions on
// local time in New York:
// ```
// Figure 1: Mapping between UTC Time and New York Local Time
//
//    EST - Eastern Standard Time (UTC-5:00)
//    EDT - Eastern Daylight Time (UTC-4:00)
//
//                                        ,-invalid times    ,- ambiguous times
//                                    T2 /  T3           T4 /   T5
//                                    @-----O            @------O
//
//               T1      EST          T2                 T4        EST
// New York Time @--------------------O                  @------------------>
//                                    |     T3    EDT    |      T5
//                                    |     @------------|------O
//                                   /   __/            /    __/
//                                 /  __/              /  __/
//                                /__/                /__/
//      UTC Time +---------------+-------------------+---------------------->
//               |               |                   |
//               Transition 1    Transition 2        Transition 3
//               (to EST)        (to EDT)            (to EST)
//
// ```
// In New York, clocks are set forward an hour in the spring, creating the
// discontinuity between T2 and T3 in the diagram.  A New York local time value
// in the range [T2, T3) is considered invalid, because a correctly set clock
// in New York would never display that value.  Similarly, clocks are set back
// an hour in the fall, creating the discontinuity between T4 and T5 in the
// diagram.  A New York local time value in the range [T4, T5) is considered
// ambiguous, as local times in that range occur twice.  For either invalid or
// ambiguous times, `loadRelevantTransitions` will return two distinct
// iterators referring to the adjacent transitions holding the two descriptions
// that might be applied to that local time.
//
// For example, consider the returned validity and transitions for the
// following New York local times:
// ```
// New York Local Time  Validity     1st Transition      2nd Transition
// -------------------  -----------  ------------------  ------------------
// Jan  1, 2010 2:30am  *_UNIQUE     Nov  1, 2009 (EST)  Nov  1, 2009 (EST)
// Mar 14, 2010 2:30am  *_INVALID    Nov  1, 2009 (EST)  Mar 14, 2010 (EDT)
// Nov  7, 2010 1:30am  *_AMBIGUOUS  Mar 14, 2010 (EDT)  Nov  7, 2010 (EST)
// ```
//
///Well-Formed Time Zone Information
///---------------------------------
// The primary operations provided by this component require the supplied
// Zoneinfo value meet certain constraints that are not enforced by the
// `baltzo::Zoneinfo` type itself.  A Zoneinfo meeting these constraints is
// considered *well-formed*, and `baltzo::ZoneinfoUtil::isWellFormed` will
// return `true` for such a value.  Specifically, a `baltzo::Zoneinfo` object
// is considered well-formed only if *all* of the following are true:
//
// 1. The Zoneinfo provides at least one transition.
// 2. The first transition in the Zoneinfo is at the first representable
//    `bdlt::Datetime` value, "Jan 01, 0001 00:00" -- i.e.,
//    `bdlt::Datetime(1, 1, 1)`.
// 3. There is no transition in the ordered sequence of transitions described
//    by the Zoneinfo where local clock time is adjusted (either forwards or
//    backwards) introducing a period of invalid or ambiguous local times,
//    where that range of invalid or ambiguous local times overlaps with the
//    range of invalid or ambiguous local times introduced by subsequent
//    transition.
//
// Note that `baltzo::ZoneinfoUtil::isWellFormed` has linear complexity with
// respect to the number of transitions that the Zoneinfo value defines.
//
///Overlapping Transitions
///- - - - - - - - - - - -
// In order to better understand the 3rd constraint (above) on a well-formed
// Zoneinfo object, first, notice that Figure 1 above (showing local time in
// New York) illustrates a well-formed sequence of transitions.  Both
// Transition 2 (to EDT) and Transition 3 (to EST) introduce a range of
// ambiguous or invalid times (ambiguous when clocks are adjusted backwards,
// invalid when clocks are adjusted forward), but those two ranges of ambiguous
// and invalid local times do not overlap.
//
// However, a Zoneinfo object is not well-formed if two transitions occur so
// close together that the respective ranges of invalid or ambiguous times that
// those transitions introduce, overlap with one and other, as illustrated in
// Figure 2.
// ```
// Figure 2:  Overlapping Transitions
//
// Standard Time (STD):        UTC+00:00
// Daylight-Saving Time (DST): UTC+01:00
//
// Transition 1 (to DST): At 00:00 UTC (00:00 local)
// Transition 2 (to STD): At 00:30 UTC (01:30 local)
//
//
//                                  (00:30)    STD
//                   STD   (00:00)    @--------------------------
//   Local Time @------------O        |           DST
//                           |        | (01:00) @------O (01:30)
//                           |        |      __/     _/
//                            \        \  __/     __/
//                             \      __\/     __/
//                              \ ___/   \ __/
//     UTC Time +---------------+---------+----------------------------
//                         Transition 1   Transition 2
//                          (01:00 UTC)   (01:30 UTC)
// ```
// Notice that between [ 00:30 .. 01:00 ] local time, the range of invalid
// times introduced by Transition 1, overlaps with the range of ambiguous times
// introduced by Transition 2.  The above time zone would therefore *not* be
// well-formed.
//
///Usage
///-----
// The following examples demonstrate how to use a `ZoneinfoUtil` to perform
// common operations on time values using a Zoneinfo description of a time
// zone.
//
///Prologue: Initializing a `baltzo::Zoneinfo` Object
/// - - - - - - - - - - - - - - - - - - - - - - - - -
// We start by creating a Zoneinfo time zone description for New York, which we
// will use in subsequent examples.  Note that, in practice, clients should
// obtain time zone information from a data source (see
// `baltzo_zoneinfocache`).
//
// First we create a Zoneinfo object for New York, and populate `newYork` with
// the correct time zone identifier:
// ```
// baltzo::Zoneinfo newYork;
// newYork.setIdentifier("America/New_York");
// ```
// Next we create two local-time descriptors, one for standard time and one for
// daylight-saving time:
// ```
// baltzo::LocalTimeDescriptor est(-18000, false, "EST");
// baltzo::LocalTimeDescriptor edt(-14400, true,  "EDT");
// ```
// Then we set the initial descriptor for `newYork` to Eastern Standard Time.
// Note that such an initial transition is required for a `baltzo::Zoneinfo`
// object to be considered Well-Formed (see `isWellFormed`):
// ```
// newYork.addTransition(bdlt::EpochUtil::convertToTimeT64(
//                                                   bdlt::Datetime(1, 1, 1)),
//                       est);
// ```
// Next we create a series of transitions between these local-time descriptors
// for the years 2007-2011.  Note that the United States transitions to
// daylight saving time on the second Sunday in March, at 2am local time (07:00
// UTC), and transitions back to standard time on the first Sunday in November
// at 2am local time (06:00 UTC), resulting in an even number of transitions:
// ```
// static const bdlt::Datetime TRANSITION_TIMES[] = {
//     bdlt::Datetime(2007,  3, 11, 7),
//     bdlt::Datetime(2007, 11,  4, 6),
//     bdlt::Datetime(2008,  3,  9, 7),
//     bdlt::Datetime(2008, 11,  2, 6),
//     bdlt::Datetime(2009,  3,  8, 7),
//     bdlt::Datetime(2009, 11,  1, 6),
//     bdlt::Datetime(2010,  3, 14, 7),
//     bdlt::Datetime(2010, 11,  7, 6),
//     bdlt::Datetime(2011,  3, 13, 7),
//     bdlt::Datetime(2011, 11,  6, 6),
// };
// const int NUM_TRANSITION_TIMES =
//                         sizeof TRANSITION_TIMES / sizeof *TRANSITION_TIMES;
// assert(0 == NUM_TRANSITION_TIMES % 2);
//
// for (int i = 0; i < NUM_TRANSITION_TIMES; i += 2) {
//     newYork.addTransition(bdlt::EpochUtil::convertToTimeT64(
//                                                       TRANSITION_TIMES[i]),
//                           edt);
//     newYork.addTransition(bdlt::EpochUtil::convertToTimeT64(
//                                                   TRANSITION_TIMES[i + 1]),
//                           est);
// }
// ```
// Finally we verify that the time zone information we've created is considered
// well-formed (as discussed above):
// ```
// assert(true == baltzo::ZoneinfoUtil::isWellFormed(newYork));
// ```
//
///Example 1: Converting from a UTC Time to a Local Time
///- - - - - - - - - - - - - - - - - - - - - - - - - - -
// In this example we demonstrate how to convert a UTC time to the
// corresponding local time using the `convertUtcToLocalTime` class method.
//
// We start by creating a `bdlt::Datetime` representing the UTC time "Dec 12,
// 2010 15:00":
// ```
// bdlt::Datetime utcTime(2010, 12, 12, 15, 0, 0);
// ```
// Now, we call `convertUtcToLocalTime` and supply as input both `utcTime` and
// the Zoneinfo description for `newYork` (which we initialized in the prologue
// above):
// ```
// bdlt::DatetimeTz                          localNYTime;
// baltzo::Zoneinfo::TransitionConstIterator iterator;
// baltzo::ZoneinfoUtil::convertUtcToLocalTime(&localNYTime,
//                                             &iterator,
//                                             utcTime,
//                                             newYork);
// ```
// Then we verify that `localNYTime` is "Dec 12, 2010 10:00+5:00", the time in
// New York corresponding to the UTC time "Dec 12, 2010 15:00".
// ```
// assert(utcTime                          == localNYTime.utcDatetime());
// assert(bdlt::Datetime(2010, 12, 12, 10) == localNYTime.localDatetime());
// assert(-5 * 60                          == localNYTime.offset());
// ```
// Finally, we verify that the returned `iterator` refers to the local-time
// transition immediately before `utcTime`, and that that transition refers to
// a local-time descriptor characterizing standard-time in New York:
// ```
// baltzo::Zoneinfo::TransitionConstIterator transitionIter     = iterator;
// baltzo::Zoneinfo::TransitionConstIterator nextTransitionIter = ++iterator;
//
// const bdlt::EpochUtil::TimeT64 utcTimeT =
//                                  bdlt::EpochUtil::converToTimeT64(utcTime);
// assert(utcTimeT >= transitionIter->transition());
// assert(utcTimeT <  nextTransitionIter->transition());
//
// assert(false        == transitionIter->descriptor().dstInEffectFlag());
// assert(-5 * 60 * 60 == transitionIter->descriptor().utcOffsetInSeconds());
// assert("EST"        == transitionIter->descriptor().description());
// ```
//
///Example 2: Determining the Type of a Local Time
///- - - - - - - - - - - - - - - - - - - - - - - -
// In this next example we use `loadRelevantTransitions` to determine the
// local-time descriptor (see `baltzo_localtimedescriptor`) that applies to a
// local time value, represented using a `bdlt::Datetime` object.
//
// We start by defining a `bdlt::Datetime` object for "Jan 1, 2011 12:00" in
// New York:
// ```
// bdlt::Datetime nyLocalTime(2011, 1, 1, 12);
// ```
// Then, we call `loadRelevantTransitions`, and supply, as input, both
// `nyLocalTime` and the Zoneinfo description for `newYork` (which we
// initialized in the prologue above):
// ```
// baltzo::LocalTimeValidity::Enum           validity;
// baltzo::Zoneinfo::TransitionConstIterator firstTransition;
// baltzo::Zoneinfo::TransitionConstIterator secondTransition;
// baltzo::ZoneinfoUtil::loadRelevantTransitions(&firstTransition,
//                                               &secondTransition,
//                                               &validity,
//                                               nyLocalTime,
//                                               newYork);
// ```
// "Jan 1, 2011 12:00" in New York, is not near a daylight-saving time
// transition, so it uniquely describes a valid time (in New York) which falls
// during Eastern Standard Time, and whose local time offset from UTC is -5:00.
// Because "Jan 1, 2011 12:00" is both a valid and unique local time, the
// returned validity will be `baltzo::LocalTimeValidity::e_VALID_UNIQUE` and
// the two returned transition iterators will be equal:
// ```
// assert(baltzo::LocalTimeValidity::e_VALID_UNIQUE == validity);
// assert(firstTransition == secondTransition);
//
// assert(false    == firstTransition->descriptor().dstInEffectFlag());
// assert(-5*60*60 == firstTransition->descriptor().utcOffsetInSeconds());
// assert("EST"    == firstTransition->descriptor().description());
// ```
// Next, we create a second `bdlt::Datetime` object to represent "Nov 7, 2010
// 1:30" in New York.  Note that the clock time "Nov 7, 2010 1:30" occurred
// twice in New York, as clocks were set back by an hour an instant before the
// local clock would have reached "Nov 7, 2010 02:00 EDT", and it is therefore
// ambiguous which of those two values that local time is meant to refer.
// ```
// bdlt::Datetime ambiguousLocalTime(2010, 11, 7, 1, 30);
// ```
// Now, we call `loadRelevantTransitions`, this time supplying
// `ambiguousLocalTime`:
// ```
// baltzo::ZoneinfoUtil::loadRelevantTransitions(&firstTransition,
//                                               &secondTransition,
//                                               &validity,
//                                               ambiguousLocalTime,
//                                               newYork);
// ```
// Finally we observe that the local time was ambiguous and that the returned
// transitions are distinct:
// ```
// assert(baltzo::LocalTimeValidity::e_VALID_AMBIGUOUS == validity);
// assert(firstTransition != secondTransition);
// ```
// Because `ambiguousLocalTime` may refer to either the standard or the
// daylight-saving time value "Nov 7, 2010 01:30", the returned validity will
// be `e_VALID_AMBIGUOUS`, and the `first` and `second` iterators will differ.
// `first` will refer to a description of the local time before the transition
// (daylight-saving time) and `second` will refer to a description of local
// time after the transition (standard-time):
// ```
// assert(true      == firstTransition->descriptor().dstInEffectFlag());
// assert(-4*60*60  == firstTransition->descriptor().utcOffsetInSeconds());
// assert("EDT"     == firstTransition->descriptor().description());
//
// assert(false     == secondTransition->descriptor().dstInEffectFlag());
// assert(-5*60*60  == secondTransition->descriptor().utcOffsetInSeconds());
// assert("EST"     == secondTransition->descriptor().description());
// ```
// Note that the two transitions returned are adjacent:
// ```
// ++firstTransition;
// assert(firstTransition == secondTransition);
// ```

#include <balscm_version.h>

#include <baltzo_localtimevalidity.h>
#include <baltzo_zoneinfo.h>

#include <bdlt_datetime.h>
#include <bdlt_datetimetz.h>

#include <bsl_iosfwd.h>

namespace BloombergLP {
namespace baltzo {
                             // ==================
                             // class ZoneinfoUtil
                             // ==================

/// This `struct` provides a namespace for utility operations using a
/// `Zoneinfo` object.
struct ZoneinfoUtil {

    // CLASS METHODS

    /// Load, into the specified `resultTime`, the local date-time value, in
    /// the specified `timeZone`, corresponding to the specified `utcTime`,
    /// and load, into the specified `resultTransition`, an iterator
    /// referring to the first transition in `timeZone` whose
    /// transition-time is before `utcTime`.  Return 0 on success, and
    /// `baltzo::ErrorCode::k_OUT_OF_RANGE` if `resultTime` would be outside
    /// of the legal range that a `bdlt::DatetimeTz` can represent.  The
    /// behavior is undefined unless `isWellFormed(timeZone)` is `true`.
    static int convertUtcToLocalTime(
                           bdlt::DatetimeTz                  *resultTime,
                           Zoneinfo::TransitionConstIterator *resultTransition,
                           const bdlt::Datetime&              utcTime,
                           const Zoneinfo&                    timeZone);

    /// Load, into the specified `firstResultTransition`, an iterator
    /// referring to the transition describing the characteristics of local
    /// time in effect at the specified `localTime` in the specified
    /// `timeZone`; in instances where `localTime` is not both a unique and
    /// valid local time in `timeZone`, load, into the specified
    /// `secondResultTransition`, an iterator referring to a subsequent
    /// transition describing the alternative characteristics of local time
    /// that could also apply to `localTime`; finally load, into the
    /// specified `resultValidity`, the validity of `localTime` as being
    /// unique, ambiguous but valid, or invalid.  If `resultValidity` is
    /// `e_VALID_UNIQUE`, then `firstResultTransition` and
    /// `secondResultTransition` will be loaded with the same transition,
    /// otherwise the returned transitions will be distinct with
    /// `secondResultTransition` referring to the transition immediately
    /// after `firstResultTransition`.  The behavior is undefined unless
    /// `isWellFormed(timeZone)` is `true`, and
    /// `firstResultTransition != secondResultTransition`.
    static void loadRelevantTransitions(
                     Zoneinfo::TransitionConstIterator *firstResultTransition,
                     Zoneinfo::TransitionConstIterator *secondResultTransition,
                     LocalTimeValidity::Enum           *resultValidity,
                     const bdlt::Datetime&              localTime,
                     const Zoneinfo&                    timeZone);

    /// Return `true` if the specified `timeZone` is a well-formed Zoneinfo
    /// object (which can be used by other methods on this utility), and
    /// `false` otherwise.  For a Zoneinfo to be considered well-formed
    /// *all* of the following must be true:
    ///
    /// 1. `timeZone.numTransitions() > 0`
    /// 2. The first transition in `timeZone` is at the first representable
    ///    `bdlt::Datetime` value, "Jan 01, 0001 00 00.000" -- i.e.,
    ///    `bdlt::Datetime(1, 1, 1)`.
    /// 3. There is no transition in the ordered sequence of transitions
    ///    described by `timeZone` where the local clock time is adjusted
    ///    (either forwards or backwards) introducing a period of invalid or
    ///    ambiguous local times, where that range of invalid or ambiguous
    ///    local times overlaps with a range of invalid or or ambiguous
    ///    local times introduced by the subsequent transition (see
    ///    component documentation for an illustration).
    ///
    /// Note that this method has linear worst-case time complexity with
    /// respect to `timeZone.numTransitions()`.
    static bool isWellFormed(const Zoneinfo& timeZone);
};

}  // close package namespace
}  // close enterprise namespace

#endif

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