// bdlt_datetimetz.h -*-C++-*- #ifndef INCLUDED_BDLT_DATETIMETZ #define INCLUDED_BDLT_DATETIMETZ #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a representation of a date and time with time zone offset. // //@CLASSES: // bdlt::DatetimeTz: local-datetime value with time zone offset from UTC // //@SEE_ALSO: bdlt_datetime // //@DESCRIPTION: This component provides a single value-semantic class, // 'bdlt::DatetimeTz', that represents a datetime value in a particular time // zone. Each 'bdlt::DatetimeTz' object contains a time zone offset from UTC // (in minutes) and a 'bdlt::Datetime' value in that time zone. For logical // consistency, the datetime value and offset should correspond to a // geographically valid time zone, but such consistency is the user's // responsibility. This component does not enforce logical constraints on any // values. // ///Caveats on Time Zone Support ///---------------------------- // A 'bdlt::DatetimeTz' value is intended to be interpreted as a value in a // local time zone, along with the offset of that value from UTC. However, // there are some problems with this simple interpretation. First of all, the // offset value may not correspond to any time zone that has ever existed. For // example, the offset value could be set to one minute, or to 1,234 minutes. // The meaning of the resulting "local datetime" value is always clear, but the // local datetime might not correspond to any geographical or historical time // zone. // // The second problem is more subtle. A given offset from UTC might be "valid" // in that it corresponds to a real time zone, but the actual datetime value // might not exist in that time zone. To make matters worse, a "valid" offset // may not (indeed, rarely will) specify one time zone uniquely. Moreover, the // datetime value might be valid in one time zone corresponding to a given // offset, and not in another time zone. // // For these reasons (and others), this component cannot and does not perform // any validation relating to time zones or offsets. The user must take care // to honor the "local datetime" contract of this component. // ///ISO Standard Text Representation ///-------------------------------- // A common standard text representation of a date and time value is described // by ISO 8601. BDE provides the 'bdlt_iso8601util' component for conversion // to and from the standard ISO8601 format. // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: Basic 'bdlt::DatetimeTz' Usage ///- - - - - - - - - - - - - - - - - - - - - // This example demonstrates how to create and use a 'bdlt::DatetimeTz' object. // // First, create an object 'dt1' having the default value, and then verify that // it contains an offset of 0, implying that the object represents a date and // time in the UTC time zone, and the value of the datetime is the same as that // of a default constructed 'bdlt::Datetime' object: //.. // bdlt::DatetimeTz dt1; // assert(0 == dt1.offset()); // assert(bdlt::Datetime() == dt1.localDatetime()); //.. // Then, set 'dt1' to the value 12:00 noon (12:00:00.000) on 12/31/2005 in the // EST time zone (UTC-5): //.. // bdlt::Datetime datetime1(2005, 12, 31, 12, 0, 0, 0); // bdlt::Datetime datetime2(datetime1); // int offset1 = -5 * 60; // // dt1.setDatetimeTz(datetime1, offset1); // assert(offset1 == dt1.offset()); // assert(dt1.localDatetime() != dt1.utcDatetime()); // assert(datetime1 == dt1.localDatetime()); // assert(datetime2 != dt1.utcDatetime()); // // datetime2.addMinutes(-offset1); // assert(datetime2 == dt1.utcDatetime()); //.. // Next, create 'dt2' as a copy of 'dt1': //.. // bdlt::DatetimeTz dt2(dt1); // assert(offset1 == dt2.offset()); // assert(datetime1 == dt2.localDatetime()); // assert(datetime2 == dt2.utcDatetime()); //.. // Now, create a third object, 'dt3', representing the time 10:33:25.000 on // 01/01/2001 in the PST time zone (UTC-8): //.. // bdlt::Datetime datetime3(2001, 1, 1, 10, 33, 25, 0); // bdlt::Datetime datetime4(datetime3); // int offset2 = -8 * 60; // // bdlt::DatetimeTz dt3(datetime3, offset2); // assert(offset2 == dt3.offset()); // assert(dt3.localDatetime() != dt3.utcDatetime()); // assert(datetime3 == dt3.localDatetime()); // assert(datetime4 != dt3.utcDatetime()); // // datetime4.addMinutes(-offset2); // assert(datetime4 == dt3.utcDatetime()); //.. // Finally, stream the values of 'dt1', 'dt2', and 'dt3' to 'stdout': //.. // bsl::cout << dt1 << bsl::endl // << dt2 << bsl::endl // << dt3 << bsl::endl; //.. // The streaming operator produces the following output on 'stdout': //.. // 31DEC2005_12:00:00.000-0500 // 31DEC2005_12:00:00.000-0500 // 01JAN2001_10:33:25.000-0800 //.. // ///Example 2: Delivery Estimation System ///- - - - - - - - - - - - - - - - - - - // Let us suppose that we are implementing a delivery estimation system for a // shipping company. The system provides estimated delivery dates and times of // client shipments. This information is provided in the local time zone and // is represented as a 'bdlt::DatetimeTz' object. Below is the definition for // a struct that returns the estimated delivery date. //.. // // ===================== // // struct DeliverySystem // // ===================== // // struct DeliverySystem { // // This struct provides a function that returns the estimated delivery // // date and time for a particular shipment. // // // PRIVATE CLASS METHODS // static bdlt::Datetime getCurrentUTCDatetime(); // // Return the current UTC date and time. // // public: // // TYPES // enum City { // // This enumeration provides an identifier for the various cities. // // e_CHICAGO = 0, // e_DUBAI, // e_NEW_YORK, // e_LONDON, // e_LOS_ANGELES // }; // // // CLASS METHODS // static bdlt::DatetimeTz getEstimatedDeliveryDatetime(City city); // // Return the estimated delivery date and time, in local time, for // // a shipment being sent to the specified 'city'. // }; //.. // All the relevant data used for delivery estimation is stored in a lookup // table as shown below: //.. // const int k_MINUTES_PER_HOUR = 60; // // static const struct { // int d_offset; // time zone offset from UTC (in minutes) // int d_deliveryTime; // delivery time (in minutes) // } DATA[] = { // // Offset DeliveryTime // // ======================= ======================= // { -6 * k_MINUTES_PER_HOUR, 10 * k_MINUTES_PER_HOUR }, // Chicago // { 3 * k_MINUTES_PER_HOUR, 72 * k_MINUTES_PER_HOUR }, // Dubai // { -5 * k_MINUTES_PER_HOUR, k_MINUTES_PER_HOUR }, // New York // { k_MINUTES_PER_HOUR, 36 * k_MINUTES_PER_HOUR }, // London // { -8 * k_MINUTES_PER_HOUR, 24 * k_MINUTES_PER_HOUR }, // Los Angeles // }; //.. // And here are the function definitions: //.. // // --------------------- // // struct DeliverySystem // // --------------------- // // // PRIVATE CLASS METHODS // bdlt::Datetime DeliverySystem::getCurrentUTCDatetime() // { // // Return a fixed datetime so that output is known a priori. // return bdlt::Datetime(2014, 10, 17, 14, 48, 56); // } // // // CLASS METHODS // bdlt::DatetimeTz DeliverySystem::getEstimatedDeliveryDatetime(City city) // { // bdlt::Datetime localDatetime(getCurrentUTCDatetime()); // localDatetime.addMinutes(DATA[city].d_offset // + DATA[city].d_deliveryTime); // return bdlt::DatetimeTz(localDatetime, DATA[city].d_offset); // } //.. // When we print out the delivery times: //.. // bsl::cout << "Estimated Delivery Time in Chicago: " // << DeliverySystem::getEstimatedDeliveryDatetime( // DeliverySystem::e_CHICAGO) // << bsl::endl; // bsl::cout << "Estimated Delivery Time in Dubai: " // << DeliverySystem::getEstimatedDeliveryDatetime( // DeliverySystem::e_DUBAI) // << bsl::endl; // bsl::cout << "Estimated Delivery Time in New York: " // << DeliverySystem::getEstimatedDeliveryDatetime( // DeliverySystem::e_NEW_YORK) // << bsl::endl; // bsl::cout << "Estimated Delivery Time in London: " // << DeliverySystem::getEstimatedDeliveryDatetime( // DeliverySystem::e_LONDON) // << bsl::endl; // bsl::cout << "Estimated Delivery Time in Los Angeles: " // << DeliverySystem::getEstimatedDeliveryDatetime( // DeliverySystem::e_LOS_ANGELES) // << bsl::endl; //.. // We get the following results: //.. // Estimated Delivery Time in Chicago: 17OCT2014_18:48:56.000-0600 // Estimated Delivery Time in Dubai: 20OCT2014_17:48:56.000+0300 // Estimated Delivery Time in New York: 17OCT2014_10:48:56.000-0500 // Estimated Delivery Time in London: 19OCT2014_03:48:56.000+0100 // Estimated Delivery Time in Los Angeles: 18OCT2014_06:48:56.000-0800 //.. #include <bdlscm_version.h> #include <bdlt_datetime.h> #include <bdlt_datetz.h> #include <bdlt_time.h> #include <bdlt_timetz.h> #include <bslh_hash.h> #include <bslmf_integralconstant.h> #include <bslmf_istriviallycopyable.h> #include <bsls_annotation.h> #include <bsls_assert.h> #include <bsl_iosfwd.h> namespace BloombergLP { namespace bdlt { // ================ // class DatetimeTz // ================ class DatetimeTz { // This value-semantic class describes a datetime value in a particular // time zone, which is indicated using an offset from UTC (in minutes). // PRIVATE TYPES enum ValidOffsetRange { // This enumeration specifies the minimum and maximum time zone offset // values. k_MAX_OFFSET = 1440, k_MIN_OFFSET = -1440 }; // DATA Datetime d_localDatetime; // datetime value in timezone specified by // 'd_offset' int d_offset; // offset from UTC (in minutes) public: // CLASS METHODS static bool isValid(const Datetime& localDatetime, int offset); // Return 'true' if the specified 'localDatetime' and the specified // time zone 'offset' represent a valid 'DatetimeTz' value, and 'false' // otherwise. A 'localDatetime' and 'offset' represent a valid // 'DatetimeTz' value if either 'bdlt::Time() == localDatetime.time()' // and '0 == offset', or 'bdlt::Time() != localDatetime.time()' and // 'offset' is in the range '( -1440 .. 1440 )'. Note that a 'true' // result from this function does not guarantee that 'offset' // corresponds to any geographical or historical time zone. Also note // that a 'true' result from this function does not guarantee that // 'localDatetime' itself is a valid 'Datetime' object. // Aspects 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 DatetimeTz(); // Create a 'DatetimeTz' object having the (default) attribute values: //.. // localDatetime() == bdlt::Datetime() // offset() == 0 //.. DatetimeTz(const Datetime& localDatetime, int offset); // Create a 'DatetimeTz' object having a local datetime value equal to // the specified 'localDatetime' and a time zone offset value from UTC // equal to the specified 'offset' (in minutes). The behavior is // undefined unless all of the specified values are within their valid // ranges (see 'isValid'). Note that this method provides no // validation, and it is the user's responsibility to ensure that // 'offset' represents a valid time zone and that 'localDatetime' // represents a valid datetime in that time zone. DatetimeTz(const DatetimeTz& original); // Create a 'DatetimeTz' object having the same value as the specified // 'original' object. ~DatetimeTz(); // Destroy this object. // MANIPULATORS DatetimeTz& operator=(const DatetimeTz& rhs); // Assign to this object the value of the specified 'rhs' object, and // return a reference providing modifiable access to this object. void setDatetimeTz(const Datetime& localDatetime, int offset); // Set the local datetime and the time zone offset of this object to // the specified 'localDatetime' and 'offset' values respectively. The // behavior is undefined unless all of the specified values are within // their valid ranges (see 'isValid'). Note that this method provides // no validation, and it is the user's responsibility to assure the // consistency of the resulting value. int setDatetimeTzIfValid(const Datetime& localDatetime, int offset); // If the specified 'localDatetime' and 'offset' represent a valid // 'DatetimeTz' value (see 'isValid'), set the local datetime and the // time zone offset of this object to the 'localDatetime' and 'offset' // values respectively and return 0, leave this object unmodified and // return a non-zero value otherwise. // 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. // ACCESSORS DateTz dateTz() const; // Return a 'DateTz' object having the value of the local date and // offset represented by this object. Datetime localDatetime() const; // Return a 'Datetime' object having the value of the local datetime // represented by this object. Note that the 'Datetime' value returned // is the current value stored in this object and may be different from // the local datetime of the system. int offset() const; // Return the time zone offset of this 'DatetimeTz' object. Note that // the offset is in minutes from UTC. TimeTz timeTz() const; // Return a 'TimeTz' object having the value of the local time and // offset represented by this object. Datetime utcDatetime() const; // Return a 'Datetime' object having the value of the UTC datetime // represented by this object. Note that if '0 != offset()', the // returned value is equal to 'localDatetime()' minus 'offset()' // minutes, and 'localDatetime()' otherwise. // Aspects 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 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. #ifndef BDE_OPENSOURCE_PUBLICATION // pending deprecation // DEPRECATED METHODS Datetime gmtDatetime() const; // !DEPRECATED!: replaced by 'utcDatetime.' // // Return a 'Datetime' object having the value of the UTC datetime // represented by this object. Note that if '0 != offset()', the // returned value is equal to 'localDatetime()' minus 'offset()' // minutes, and 'localDatetime()' otherwise. static int maxSupportedBdexVersion(); // !DEPRECATED!: Use 'maxSupportedBdexVersion(int)' instead. // // Return the most current BDEX streaming version number supported by // this class. int validateAndSetDatetimeTz(const Datetime& localDatetime, int offset); // !DEPRECATED!: replaced by 'setDatetimeTzIfValid'. // // If the specified 'localDatetime' and 'offset' represent a valid // 'DatetimeTz' value (see 'isValid'), set the local datetime and the // time zone offset of this object to the 'localDatetime' and 'offset' // values respectively and return 0, leave this object unmodified and // return a non-zero value otherwise. #endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation }; // FREE OPERATORS bool operator==(const DatetimeTz& lhs, const DatetimeTz& rhs); // Return 'true' if the specified 'lhs' and 'rhs' 'DatetimeTz' objects have // the same value, and 'false' otherwise. Two 'DatetimeTz' objects have // the same value if they have the same local datetime value and the same // time zone offset value. bool operator!=(const DatetimeTz& lhs, const DatetimeTz& rhs); // Return 'true' if the specified 'lhs' and 'rhs' 'DatetimeTz' objects do // not have the same value, and 'false' otherwise. Two 'DatetimeTz' // objects do not have the same value if they do not have the same local // datetime value or the same time zone offset value. bsl::ostream& operator<<(bsl::ostream& stream, const DatetimeTz& rhs); // Write the value of the specified 'rhs' 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)', but with // the attribute names elided. // FREE FUNCTIONS template <class HASHALG> void hashAppend(HASHALG& hashAlg, const DatetimeTz& object); // Pass the specified 'object' to the specified 'hashAlg'. This function // integrates with the 'bslh' modular hashing system and effectively // provides a 'bsl::hash' specialization for 'DatetimeTz'. Note that two // objects which represent the same UTC time but have different offsets // will not (necessarily) hash to the same value. // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // ---------------- // class DatetimeTz // ---------------- // CLASS METHODS inline bool DatetimeTz::isValid(const Datetime& localDatetime, int offset) { return k_MIN_OFFSET < offset && k_MAX_OFFSET > offset && (bdlt::Time() != localDatetime.time() || 0 == offset); } // Aspects inline int DatetimeTz::maxSupportedBdexVersion(int versionSelector) { if (versionSelector >= 20170401) { return 2; // RETURN } return 1; } // CREATORS inline DatetimeTz::DatetimeTz() : d_localDatetime() , d_offset(0) { } inline DatetimeTz::DatetimeTz(const Datetime& localDatetime, int offset) : d_localDatetime(localDatetime) , d_offset(offset) { BSLS_ASSERT_SAFE(isValid(localDatetime, offset)); } inline DatetimeTz::DatetimeTz(const DatetimeTz& original) : d_localDatetime(original.d_localDatetime) , d_offset(original.d_offset) { } inline DatetimeTz::~DatetimeTz() { BSLS_ASSERT_SAFE(isValid(d_localDatetime, d_offset)); } // MANIPULATORS inline DatetimeTz& DatetimeTz::operator=(const DatetimeTz& rhs) { d_localDatetime = rhs.d_localDatetime; d_offset = rhs.d_offset; return *this; } inline void DatetimeTz::setDatetimeTz(const Datetime& localDatetime, int offset) { BSLS_ASSERT_SAFE(isValid(localDatetime, offset)); d_localDatetime = localDatetime; d_offset = offset; } inline int DatetimeTz::setDatetimeTzIfValid(const Datetime& localDatetime, int offset) { if (isValid(localDatetime, offset)) { setDatetimeTz(localDatetime, offset); return 0; // RETURN } return -1; } // Aspects template <class STREAM> STREAM& DatetimeTz::bdexStreamIn(STREAM& stream, int version) { if (stream) { switch (version) { // switch on the schema version case 2: BSLS_ANNOTATION_FALLTHROUGH; case 1: { Datetime localDatetime; localDatetime.bdexStreamIn(stream, version); int offset; stream.getInt32(offset); if (stream && isValid(localDatetime, offset)) { d_localDatetime = localDatetime; d_offset = offset; } else { stream.invalidate(); } } break; default: { stream.invalidate(); // unrecognized version number } } } return stream; } // ACCESSORS inline DateTz DatetimeTz::dateTz() const { return DateTz(d_localDatetime.date(), d_offset); } inline Datetime DatetimeTz::localDatetime() const { return d_localDatetime; } inline int DatetimeTz::offset() const { return d_offset; } inline TimeTz DatetimeTz::timeTz() const { return TimeTz(d_localDatetime.time(), d_offset); } inline Datetime DatetimeTz::utcDatetime() const { Datetime utc(d_localDatetime); if (d_offset) { utc.addMinutes(-d_offset); } return utc; } // Aspects template <class STREAM> STREAM& DatetimeTz::bdexStreamOut(STREAM& stream, int version) const { if (stream) { switch (version) { // switch on the schema version case 2: BSLS_ANNOTATION_FALLTHROUGH; case 1: { d_localDatetime.bdexStreamOut(stream, version); stream.putInt32(d_offset); } break; default: { stream.invalidate(); // unrecognized version number } } } return stream; } #ifndef BDE_OPENSOURCE_PUBLICATION // pending deprecation // DEPRECATED METHODS inline Datetime DatetimeTz::gmtDatetime() const { return utcDatetime(); } inline int DatetimeTz::maxSupportedBdexVersion() { return maxSupportedBdexVersion(0); } inline int DatetimeTz::validateAndSetDatetimeTz(const Datetime& localDatetime, int offset) { return setDatetimeTzIfValid(localDatetime, offset); } #endif // BDE_OPENSOURCE_PUBLICATION -- pending deprecation } // close package namespace // FREE OPERATORS inline bool bdlt::operator==(const DatetimeTz& lhs, const DatetimeTz& rhs) { return lhs.localDatetime() == rhs.localDatetime() && lhs.offset() == rhs.offset(); } inline bool bdlt::operator!=(const DatetimeTz& lhs, const DatetimeTz& rhs) { return !(lhs == rhs); } inline bsl::ostream& bdlt::operator<<(bsl::ostream& stream, const DatetimeTz& rhs) { return rhs.print(stream, 0, -1); } // FREE FUNCTIONS template <class HASHALG> inline void bdlt::hashAppend(HASHALG& hashAlg, const DatetimeTz& object) { using ::BloombergLP::bslh::hashAppend; hashAppend(hashAlg, object.localDatetime()); hashAppend(hashAlg, object.offset()); } } // close enterprise namespace namespace bsl { // TRAITS template <> struct is_trivially_copyable<BloombergLP::bdlt::DatetimeTz> : bsl::true_type { // This template specialization for 'is_trivially_copyable' indicates that // 'bdlt::DatetimeTz' is a trivially copyable type. }; } // close namespace bsl #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 ----------------------------------