1 #ifndef LOCAL_TIME_LOCAL_DATE_TIME_HPP__ 2 #define LOCAL_TIME_LOCAL_DATE_TIME_HPP__ 3 4 /* Copyright (c) 2003-2005 CrystalClear Software, Inc. 5 * Subject to the Boost Software License, Version 1.0. 6 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) 7 * Author: Jeff Garland, Bart Garst 8 * $Date$ 9 */ 10 11 #include <string> 12 #include <iomanip> 13 #include <sstream> 14 #include <stdexcept> 15 #include <boost/shared_ptr.hpp> 16 #include <boost/throw_exception.hpp> 17 #include <boost/date_time/time.hpp> 18 #include <boost/date_time/posix_time/posix_time.hpp> //todo remove? 19 #include <boost/date_time/compiler_config.hpp> 20 #include <boost/date_time/dst_rules.hpp> 21 #include <boost/date_time/time_zone_base.hpp> 22 #include <boost/date_time/special_defs.hpp> 23 #include <boost/date_time/time_resolution_traits.hpp> // absolute_value 24 25 namespace boost { 26 namespace local_time { 27 28 //! simple exception for reporting when STD or DST cannot be determined 29 struct BOOST_SYMBOL_VISIBLE ambiguous_result : public std::logic_error 30 { ambiguous_resultboost::local_time::ambiguous_result31 ambiguous_result (std::string const& msg = std::string()) : 32 std::logic_error(std::string("Daylight Savings Results are ambiguous: " + msg)) {} 33 }; 34 //! simple exception for when time label given cannot exist 35 struct BOOST_SYMBOL_VISIBLE time_label_invalid : public std::logic_error 36 { time_label_invalidboost::local_time::time_label_invalid37 time_label_invalid (std::string const& msg = std::string()) : 38 std::logic_error(std::string("Time label given is invalid: " + msg)) {} 39 }; 40 struct BOOST_SYMBOL_VISIBLE dst_not_valid: public std::logic_error 41 { dst_not_validboost::local_time::dst_not_valid42 dst_not_valid(std::string const& msg = std::string()) : 43 std::logic_error(std::string("is_dst flag does not match resulting dst for time label given: " + msg)) {} 44 }; 45 46 //TODO: I think these should be in local_date_time_base and not 47 // necessarily brought into the namespace 48 using date_time::time_is_dst_result; 49 using date_time::is_in_dst; 50 using date_time::is_not_in_dst; 51 using date_time::ambiguous; 52 using date_time::invalid_time_label; 53 54 //! Representation of "wall-clock" time in a particular time zone 55 /*! Representation of "wall-clock" time in a particular time zone 56 * Local_date_time_base holds a time value (date and time offset from 00:00) 57 * along with a time zone. The time value is stored as UTC and conversions 58 * to wall clock time are made as needed. This approach allows for 59 * operations between wall-clock times in different time zones, and 60 * daylight savings time considerations, to be made. Time zones are 61 * required to be in the form of a boost::shared_ptr<time_zone_base>. 62 */ 63 template<class utc_time_=posix_time::ptime, 64 class tz_type=date_time::time_zone_base<utc_time_,char> > 65 class BOOST_SYMBOL_VISIBLE local_date_time_base : public date_time::base_time<utc_time_, 66 boost::posix_time::posix_time_system> { 67 public: 68 typedef utc_time_ utc_time_type; 69 typedef typename utc_time_type::time_duration_type time_duration_type; 70 typedef typename utc_time_type::date_type date_type; 71 typedef typename date_type::duration_type date_duration_type; 72 typedef typename utc_time_type::time_system_type time_system_type; 73 /*! This constructor interprets the passed time as a UTC time. 74 * So, for example, if the passed timezone is UTC-5 then the 75 * time will be adjusted back 5 hours. The time zone allows for 76 * automatic calculation of whether the particular time is adjusted for 77 * daylight savings, etc. 78 * If the time zone shared pointer is null then time stays unadjusted. 79 *@param t A UTC time 80 *@param tz Timezone for to adjust the UTC time to. 81 */ local_date_time_base(utc_time_type t,boost::shared_ptr<tz_type> tz)82 local_date_time_base(utc_time_type t, 83 boost::shared_ptr<tz_type> tz) : 84 date_time::base_time<utc_time_type, time_system_type>(t), 85 zone_(tz) 86 { 87 // param was already utc so nothing more to do 88 } 89 90 /*! This constructs a local time -- the passed time information 91 * understood to be in the passed tz. The DST flag must be passed 92 * to indicate whether the time is in daylight savings or not. 93 * @throws -- time_label_invalid if the time passed does not exist in 94 * the given locale. The non-existent case occurs typically 95 * during the shift-back from daylight savings time. When 96 * the clock is shifted forward a range of times 97 * (2 am to 3 am in the US) is skipped and hence is invalid. 98 * @throws -- dst_not_valid if the DST flag is passed for a period 99 * where DST is not active. 100 */ local_date_time_base(date_type d,time_duration_type td,boost::shared_ptr<tz_type> tz,bool dst_flag)101 local_date_time_base(date_type d, 102 time_duration_type td, 103 boost::shared_ptr<tz_type> tz, 104 bool dst_flag) : //necessary for constr_adj() 105 date_time::base_time<utc_time_type,time_system_type>(construction_adjustment(utc_time_type(d, td), tz, dst_flag)), 106 zone_(tz) 107 { 108 if(tz != boost::shared_ptr<tz_type>() && tz->has_dst()){ 109 110 // d & td are already local so we use them 111 time_is_dst_result result = check_dst(d, td, tz); 112 bool in_dst = (result == is_in_dst); // less processing than is_dst() 113 114 // ambig occurs at end, invalid at start 115 if(result == invalid_time_label){ 116 // Ex: 2:15am local on trans-in day in nyc, dst_flag irrelevant 117 std::ostringstream ss; 118 ss << "time given: " << d << ' ' << td; 119 boost::throw_exception(time_label_invalid(ss.str())); 120 } 121 else if(result != ambiguous && in_dst != dst_flag){ 122 // is dst_flag accurate? 123 // Ex: false flag in NYC in June 124 std::ostringstream ss; 125 ss.setf(std::ios_base::boolalpha); 126 ss << "flag given: dst=" << dst_flag << ", dst calculated: dst=" << in_dst; 127 boost::throw_exception(dst_not_valid(ss.str())); 128 } 129 130 // everything checks out and conversion to utc already done 131 } 132 } 133 134 //TODO maybe not the right set...Ignore the last 2 for now... 135 enum DST_CALC_OPTIONS { EXCEPTION_ON_ERROR, NOT_DATE_TIME_ON_ERROR }; 136 //ASSUME_DST_ON_ERROR, ASSUME_NOT_DST_ON_ERROR }; 137 138 /*! This constructs a local time -- the passed time information 139 * understood to be in the passed tz. The DST flag is calculated 140 * according to the specified rule. 141 */ local_date_time_base(date_type d,time_duration_type td,boost::shared_ptr<tz_type> tz,DST_CALC_OPTIONS calc_option)142 local_date_time_base(date_type d, 143 time_duration_type td, 144 boost::shared_ptr<tz_type> tz, 145 DST_CALC_OPTIONS calc_option) : 146 // dummy value - time_ is set in constructor code 147 date_time::base_time<utc_time_type,time_system_type>(utc_time_type(d,td)), 148 zone_(tz) 149 { 150 time_is_dst_result result = check_dst(d, td, tz); 151 if(result == ambiguous) { 152 if(calc_option == EXCEPTION_ON_ERROR){ 153 std::ostringstream ss; 154 ss << "time given: " << d << ' ' << td; 155 boost::throw_exception(ambiguous_result(ss.str())); 156 } 157 else{ // NADT on error 158 this->time_ = posix_time::posix_time_system::get_time_rep(date_type(date_time::not_a_date_time), time_duration_type(date_time::not_a_date_time)); 159 } 160 } 161 else if(result == invalid_time_label){ 162 if(calc_option == EXCEPTION_ON_ERROR){ 163 std::ostringstream ss; 164 ss << "time given: " << d << ' ' << td; 165 boost::throw_exception(time_label_invalid(ss.str())); 166 } 167 else{ // NADT on error 168 this->time_ = posix_time::posix_time_system::get_time_rep(date_type(date_time::not_a_date_time), time_duration_type(date_time::not_a_date_time)); 169 } 170 } 171 else if(result == is_in_dst){ 172 utc_time_type t = 173 construction_adjustment(utc_time_type(d, td), tz, true); 174 this->time_ = posix_time::posix_time_system::get_time_rep(t.date(), 175 t.time_of_day()); 176 } 177 else{ 178 utc_time_type t = 179 construction_adjustment(utc_time_type(d, td), tz, false); 180 this->time_ = posix_time::posix_time_system::get_time_rep(t.date(), 181 t.time_of_day()); 182 } 183 } 184 185 186 //! Determines if given time label is in daylight savings for given zone 187 /*! Determines if given time label is in daylight savings for given zone. 188 * Takes a date and time_duration representing a local time, along 189 * with time zone, and returns a time_is_dst_result object as result. 190 */ check_dst(date_type d,time_duration_type td,boost::shared_ptr<tz_type> tz)191 static time_is_dst_result check_dst(date_type d, 192 time_duration_type td, 193 boost::shared_ptr<tz_type> tz) 194 { 195 if(tz != boost::shared_ptr<tz_type>() && tz->has_dst()) { 196 typedef typename date_time::dst_calculator<date_type, time_duration_type> dst_calculator; 197 return dst_calculator::local_is_dst( 198 d, td, 199 tz->dst_local_start_time(d.year()).date(), 200 tz->dst_local_start_time(d.year()).time_of_day(), 201 tz->dst_local_end_time(d.year()).date(), 202 tz->dst_local_end_time(d.year()).time_of_day(), 203 tz->dst_offset() 204 ); 205 } 206 else{ 207 return is_not_in_dst; 208 } 209 } 210 211 //! Simple destructor, releases time zone if last referrer ~local_date_time_base()212 ~local_date_time_base() {} 213 214 //! Copy constructor local_date_time_base(const local_date_time_base & rhs)215 local_date_time_base(const local_date_time_base& rhs) : 216 date_time::base_time<utc_time_type, time_system_type>(rhs), 217 zone_(rhs.zone_) 218 {} 219 220 //! Special values constructor local_date_time_base(const boost::date_time::special_values sv,boost::shared_ptr<tz_type> tz=boost::shared_ptr<tz_type> ())221 explicit local_date_time_base(const boost::date_time::special_values sv, 222 boost::shared_ptr<tz_type> tz = boost::shared_ptr<tz_type>()) : 223 date_time::base_time<utc_time_type, time_system_type>(utc_time_type(sv)), 224 zone_(tz) 225 {} 226 227 //! returns time zone associated with calling instance zone() const228 boost::shared_ptr<tz_type> zone() const 229 { 230 return zone_; 231 } 232 //! returns false is time_zone is NULL and if time value is a special_value is_dst() const233 bool is_dst() const 234 { 235 if(zone_ != boost::shared_ptr<tz_type>() && zone_->has_dst() && !this->is_special()) { 236 // check_dst takes a local time, *this is utc 237 utc_time_type lt(this->time_); 238 lt += zone_->base_utc_offset(); 239 // dst_offset only needs to be considered with ambiguous time labels 240 // make that adjustment there 241 242 switch(check_dst(lt.date(), lt.time_of_day(), zone_)){ 243 case is_not_in_dst: 244 return false; 245 case is_in_dst: 246 return true; 247 case ambiguous: 248 if(lt + zone_->dst_offset() < zone_->dst_local_end_time(lt.date().year())) { 249 return true; 250 } 251 break; 252 case invalid_time_label: 253 if(lt >= zone_->dst_local_start_time(lt.date().year())) { 254 return true; 255 } 256 break; 257 } 258 } 259 return false; 260 } 261 //! Returns object's time value as a utc representation utc_time() const262 utc_time_type utc_time() const 263 { 264 return utc_time_type(this->time_); 265 } 266 //! Returns object's time value as a local representation local_time() const267 utc_time_type local_time() const 268 { 269 if(zone_ != boost::shared_ptr<tz_type>()){ 270 utc_time_type lt = this->utc_time() + zone_->base_utc_offset(); 271 if (is_dst()) { 272 lt += zone_->dst_offset(); 273 } 274 return lt; 275 } 276 return utc_time_type(this->time_); 277 } 278 //! Returns string in the form "2003-Aug-20 05:00:00 EDT" 279 /*! Returns string in the form "2003-Aug-20 05:00:00 EDT". If 280 * time_zone is NULL the time zone abbreviation will be "UTC". The time 281 * zone abbrev will not be included if calling object is a special_value*/ to_string() const282 std::string to_string() const 283 { 284 //TODO is this a temporary function ??? 285 std::ostringstream ss; 286 if(this->is_special()){ 287 ss << utc_time(); 288 return ss.str(); 289 } 290 if(zone_ == boost::shared_ptr<tz_type>()) { 291 ss << utc_time() << " UTC"; 292 return ss.str(); 293 } 294 bool is_dst_ = is_dst(); 295 utc_time_type lt = this->utc_time() + zone_->base_utc_offset(); 296 if (is_dst_) { 297 lt += zone_->dst_offset(); 298 } 299 ss << local_time() << " "; 300 if (is_dst()) { 301 ss << zone_->dst_zone_abbrev(); 302 } 303 else { 304 ss << zone_->std_zone_abbrev(); 305 } 306 return ss.str(); 307 } 308 /*! returns a local_date_time_base in the given time zone with the 309 * optional time_duration added. */ local_time_in(boost::shared_ptr<tz_type> new_tz,time_duration_type td=time_duration_type (0,0,0)) const310 local_date_time_base local_time_in(boost::shared_ptr<tz_type> new_tz, 311 time_duration_type td=time_duration_type(0,0,0)) const 312 { 313 return local_date_time_base(utc_time_type(this->time_) + td, new_tz); 314 } 315 316 //! Returns name of associated time zone or "Coordinated Universal Time". 317 /*! Optional bool parameter will return time zone as an offset 318 * (ie "+07:00" extended iso format). Empty string is returned for 319 * classes that do not use a time_zone */ zone_name(bool as_offset=false) const320 std::string zone_name(bool as_offset=false) const 321 { 322 if(zone_ == boost::shared_ptr<tz_type>()) { 323 if(as_offset) { 324 return std::string("Z"); 325 } 326 else { 327 return std::string("Coordinated Universal Time"); 328 } 329 } 330 if (is_dst()) { 331 if(as_offset) { 332 time_duration_type td = zone_->base_utc_offset(); 333 td += zone_->dst_offset(); 334 return zone_as_offset(td, ":"); 335 } 336 else { 337 return zone_->dst_zone_name(); 338 } 339 } 340 else { 341 if(as_offset) { 342 time_duration_type td = zone_->base_utc_offset(); 343 return zone_as_offset(td, ":"); 344 } 345 else { 346 return zone_->std_zone_name(); 347 } 348 } 349 } 350 //! Returns abbreviation of associated time zone or "UTC". 351 /*! Optional bool parameter will return time zone as an offset 352 * (ie "+0700" iso format). Empty string is returned for classes 353 * that do not use a time_zone */ zone_abbrev(bool as_offset=false) const354 std::string zone_abbrev(bool as_offset=false) const 355 { 356 if(zone_ == boost::shared_ptr<tz_type>()) { 357 if(as_offset) { 358 return std::string("Z"); 359 } 360 else { 361 return std::string("UTC"); 362 } 363 } 364 if (is_dst()) { 365 if(as_offset) { 366 time_duration_type td = zone_->base_utc_offset(); 367 td += zone_->dst_offset(); 368 return zone_as_offset(td, ""); 369 } 370 else { 371 return zone_->dst_zone_abbrev(); 372 } 373 } 374 else { 375 if(as_offset) { 376 time_duration_type td = zone_->base_utc_offset(); 377 return zone_as_offset(td, ""); 378 } 379 else { 380 return zone_->std_zone_abbrev(); 381 } 382 } 383 } 384 385 //! returns a posix_time_zone string for the associated time_zone. If no time_zone, "UTC+00" is returned. zone_as_posix_string() const386 std::string zone_as_posix_string() const 387 { 388 if(zone_ == shared_ptr<tz_type>()) { 389 return std::string("UTC+00"); 390 } 391 return zone_->to_posix_string(); 392 } 393 394 //! Equality comparison operator 395 /*bool operator==(const date_time::base_time<boost::posix_time::ptime,boost::posix_time::posix_time_system>& rhs) const 396 { // fails due to rhs.time_ being protected 397 return date_time::base_time<boost::posix_time::ptime,boost::posix_time::posix_time_system>::operator==(rhs); 398 //return this->time_ == rhs.time_; 399 }*/ 400 //! Equality comparison operator operator ==(const local_date_time_base & rhs) const401 bool operator==(const local_date_time_base& rhs) const 402 { 403 return time_system_type::is_equal(this->time_, rhs.time_); 404 } 405 //! Non-Equality comparison operator operator !=(const local_date_time_base & rhs) const406 bool operator!=(const local_date_time_base& rhs) const 407 { 408 return !(*this == rhs); 409 } 410 //! Less than comparison operator operator <(const local_date_time_base & rhs) const411 bool operator<(const local_date_time_base& rhs) const 412 { 413 return time_system_type::is_less(this->time_, rhs.time_); 414 } 415 //! Less than or equal to comparison operator operator <=(const local_date_time_base & rhs) const416 bool operator<=(const local_date_time_base& rhs) const 417 { 418 return (*this < rhs || *this == rhs); 419 } 420 //! Greater than comparison operator operator >(const local_date_time_base & rhs) const421 bool operator>(const local_date_time_base& rhs) const 422 { 423 return !(*this <= rhs); 424 } 425 //! Greater than or equal to comparison operator operator >=(const local_date_time_base & rhs) const426 bool operator>=(const local_date_time_base& rhs) const 427 { 428 return (*this > rhs || *this == rhs); 429 } 430 431 //! Local_date_time + date_duration operator +(const date_duration_type & dd) const432 local_date_time_base operator+(const date_duration_type& dd) const 433 { 434 return local_date_time_base(time_system_type::add_days(this->time_,dd), zone_); 435 } 436 //! Local_date_time += date_duration operator +=(const date_duration_type & dd)437 local_date_time_base operator+=(const date_duration_type& dd) 438 { 439 this->time_ = time_system_type::add_days(this->time_,dd); 440 return *this; 441 } 442 //! Local_date_time - date_duration operator -(const date_duration_type & dd) const443 local_date_time_base operator-(const date_duration_type& dd) const 444 { 445 return local_date_time_base(time_system_type::subtract_days(this->time_,dd), zone_); 446 } 447 //! Local_date_time -= date_duration operator -=(const date_duration_type & dd)448 local_date_time_base operator-=(const date_duration_type& dd) 449 { 450 this->time_ = time_system_type::subtract_days(this->time_,dd); 451 return *this; 452 } 453 //! Local_date_time + time_duration operator +(const time_duration_type & td) const454 local_date_time_base operator+(const time_duration_type& td) const 455 { 456 return local_date_time_base(time_system_type::add_time_duration(this->time_,td), zone_); 457 } 458 //! Local_date_time += time_duration operator +=(const time_duration_type & td)459 local_date_time_base operator+=(const time_duration_type& td) 460 { 461 this->time_ = time_system_type::add_time_duration(this->time_,td); 462 return *this; 463 } 464 //! Local_date_time - time_duration operator -(const time_duration_type & td) const465 local_date_time_base operator-(const time_duration_type& td) const 466 { 467 return local_date_time_base(time_system_type::subtract_time_duration(this->time_,td), zone_); 468 } 469 //! Local_date_time -= time_duration operator -=(const time_duration_type & td)470 local_date_time_base operator-=(const time_duration_type& td) 471 { 472 this->time_ = time_system_type::subtract_time_duration(this->time_,td); 473 return *this; 474 } 475 //! local_date_time -= local_date_time --> time_duration_type operator -(const local_date_time_base & rhs) const476 time_duration_type operator-(const local_date_time_base& rhs) const 477 { 478 return utc_time_type(this->time_) - utc_time_type(rhs.time_); 479 } 480 private: 481 boost::shared_ptr<tz_type> zone_; 482 //bool is_dst_; 483 484 /*! Adjust the passed in time to UTC? 485 */ construction_adjustment(utc_time_type t,boost::shared_ptr<tz_type> z,bool dst_flag)486 utc_time_type construction_adjustment(utc_time_type t, 487 boost::shared_ptr<tz_type> z, 488 bool dst_flag) 489 { 490 if(z != boost::shared_ptr<tz_type>()) { 491 if(dst_flag && z->has_dst()) { 492 t -= z->dst_offset(); 493 } // else no adjust 494 t -= z->base_utc_offset(); 495 } 496 return t; 497 } 498 499 /*! Simple formatting code -- todo remove this? 500 */ zone_as_offset(const time_duration_type & td,const std::string & separator) const501 std::string zone_as_offset(const time_duration_type& td, 502 const std::string& separator) const 503 { 504 std::ostringstream ss; 505 if(td.is_negative()) { 506 // a negative duration is represented as "-[h]h:mm" 507 // we require two digits for the hour. A positive duration 508 // with the %H flag will always give two digits 509 ss << "-"; 510 } 511 else { 512 ss << "+"; 513 } 514 ss << std::setw(2) << std::setfill('0') 515 << date_time::absolute_value(td.hours()) 516 << separator 517 << std::setw(2) << std::setfill('0') 518 << date_time::absolute_value(td.minutes()); 519 return ss.str(); 520 } 521 }; 522 523 //!Use the default parameters to define local_date_time 524 typedef local_date_time_base<> local_date_time; 525 526 } } 527 528 529 #endif 530