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

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

    // CLASS METHODS
    static int convertUtcToLocalTime(
                           bdlt::DatetimeTz                  *resultTime,
                           Zoneinfo::TransitionConstIterator *resultTransition,
                           const bdlt::Datetime&              utcTime,
                           const Zoneinfo&                    timeZone);
        // 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 void loadRelevantTransitions(
                     Zoneinfo::TransitionConstIterator *firstResultTransition,
                     Zoneinfo::TransitionConstIterator *secondResultTransition,
                     LocalTimeValidity::Enum           *resultValidity,
                     const bdlt::Datetime&              localTime,
                     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 bool isWellFormed(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()'.
};

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