1 #ifndef _DATE_TIME_POSIX_TIME_ZONE__ 2 #define _DATE_TIME_POSIX_TIME_ZONE__ 3 4 /* Copyright (c) 2003-2005 CrystalClear Software, Inc. 5 * Subject to the Boost Software License, Version 1.0. (See accompanying 6 * 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 <sstream> 13 #include <stdexcept> 14 #include <boost/tokenizer.hpp> 15 #include <boost/throw_exception.hpp> 16 #include <boost/date_time/compiler_config.hpp> 17 #include <boost/date_time/gregorian/gregorian.hpp> 18 #include <boost/date_time/time_zone_names.hpp> 19 #include <boost/date_time/time_zone_base.hpp> 20 #include <boost/date_time/local_time/dst_transition_day_rules.hpp> 21 #include <boost/date_time/posix_time/posix_time.hpp> 22 #include <boost/date_time/string_convert.hpp> 23 #include <boost/date_time/time_parsing.hpp> 24 25 namespace boost{ 26 namespace local_time{ 27 28 //! simple exception for UTC and Daylight savings start/end offsets 29 struct BOOST_SYMBOL_VISIBLE bad_offset : public std::out_of_range 30 { bad_offsetboost::local_time::bad_offset31 bad_offset(std::string const& msg = std::string()) : 32 std::out_of_range(std::string("Offset out of range: " + msg)) {} 33 }; 34 //! simple exception for UTC daylight savings adjustment 35 struct BOOST_SYMBOL_VISIBLE bad_adjustment : public std::out_of_range 36 { bad_adjustmentboost::local_time::bad_adjustment37 bad_adjustment(std::string const& msg = std::string()) : 38 std::out_of_range(std::string("Adjustment out of range: " + msg)) {} 39 }; 40 41 typedef boost::date_time::dst_adjustment_offsets<boost::posix_time::time_duration> dst_adjustment_offsets; 42 43 //! A time zone class constructed from a POSIX time zone string 44 /*! A POSIX time zone string takes the form of:<br> 45 * "std offset dst [offset],start[/time],end[/time]" (w/no spaces) 46 * 'std' specifies the abbrev of the time zone.<br> 47 * 'offset' is the offset from UTC.<br> 48 * 'dst' specifies the abbrev of the time zone during daylight savings time.<br> 49 * The second offset is how many hours changed during DST. Default=1<br> 50 * 'start' and'end' are the dates when DST goes into (and out of) effect.<br> 51 * 'offset' takes the form of: [+|-]hh[:mm[:ss]] {h=0-23, m/s=0-59}<br> 52 * 'time' and 'offset' take the same form. Time defaults=02:00:00<br> 53 * 'start' and 'end' can be one of three forms:<br> 54 * Mm.w.d {month=1-12, week=1-5 (5 is always last), day=0-6}<br> 55 * Jn {n=1-365 Feb29 is never counted}<br> 56 * n {n=0-365 Feb29 is counted in leap years}<br> 57 * Example "PST-5PDT01:00:00,M4.1.0/02:00:00,M10.1.0/02:00:00" 58 * <br> 59 * Exceptions will be thrown under these conditions:<br> 60 * An invalid date spec (see date class)<br> 61 * A boost::local_time::bad_offset exception will be thrown for:<br> 62 * A DST start or end offset that is negative or more than 24 hours<br> 63 * A UTC zone that is greater than +14 or less than -12 hours<br> 64 * A boost::local_time::bad_adjustment exception will be thrown for:<br> 65 * A DST adjustment that is 24 hours or more (positive or negative)<br> 66 * 67 * Note that UTC zone offsets can be greater than +12: 68 * http://www.worldtimezone.com/utc/utc+1200.html 69 */ 70 template<class CharT> 71 class BOOST_SYMBOL_VISIBLE posix_time_zone_base : public date_time::time_zone_base<posix_time::ptime,CharT> { 72 public: 73 typedef boost::posix_time::time_duration time_duration_type; 74 typedef date_time::time_zone_names_base<CharT> time_zone_names; 75 typedef date_time::time_zone_base<posix_time::ptime,CharT> base_type; 76 typedef typename base_type::string_type string_type; 77 typedef CharT char_type; 78 typedef typename base_type::stringstream_type stringstream_type; 79 typedef boost::char_separator<char_type, std::char_traits<char_type> > char_separator_type; 80 typedef boost::tokenizer<char_separator_type, 81 typename string_type::const_iterator, 82 string_type> tokenizer_type; 83 typedef typename tokenizer_type::iterator tokenizer_iterator_type; 84 85 //! Construct from a POSIX time zone string posix_time_zone_base(const string_type & s)86 posix_time_zone_base(const string_type& s) : 87 //zone_names_("std_name","std_abbrev","no-dst","no-dst"), 88 zone_names_(), 89 has_dst_(false), 90 base_utc_offset_(posix_time::hours(0)), 91 dst_offsets_(posix_time::hours(0),posix_time::hours(0),posix_time::hours(0)), 92 dst_calc_rules_() 93 { 94 #ifdef __HP_aCC 95 // Work around bug in aC++ compiler: see QXCR1000880488 in the 96 // HP bug tracking system 97 const char_type sep_chars[2] = {',',0}; 98 #else 99 const char_type sep_chars[2] = {','}; 100 #endif 101 char_separator_type sep(sep_chars); 102 tokenizer_type tokens(s, sep); 103 tokenizer_iterator_type it = tokens.begin(), end = tokens.end(); 104 if (it == end) 105 BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse time zone name")); 106 calc_zone(*it++); 107 if(has_dst_) 108 { 109 if (it == end) 110 BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse DST begin time")); 111 string_type dst_begin = *it++; 112 113 if (it == end) 114 BOOST_THROW_EXCEPTION(std::invalid_argument("Could not parse DST end time")); 115 string_type dst_end = *it; 116 calc_rules(dst_begin, dst_end); 117 } 118 } ~posix_time_zone_base()119 virtual ~posix_time_zone_base() {} 120 //!String for the zone when not in daylight savings (eg: EST) std_zone_abbrev() const121 virtual string_type std_zone_abbrev()const 122 { 123 return zone_names_.std_zone_abbrev(); 124 } 125 //!String for the timezone when in daylight savings (eg: EDT) 126 /*! For those time zones that have no DST, an empty string is used */ dst_zone_abbrev() const127 virtual string_type dst_zone_abbrev() const 128 { 129 return zone_names_.dst_zone_abbrev(); 130 } 131 //!String for the zone when not in daylight savings (eg: Eastern Standard Time) 132 /*! The full STD name is not extracted from the posix time zone string. 133 * Therefore, the STD abbreviation is used in it's place */ std_zone_name() const134 virtual string_type std_zone_name()const 135 { 136 return zone_names_.std_zone_name(); 137 } 138 //!String for the timezone when in daylight savings (eg: Eastern Daylight Time) 139 /*! The full DST name is not extracted from the posix time zone string. 140 * Therefore, the STD abbreviation is used in it's place. For time zones 141 * that have no DST, an empty string is used */ dst_zone_name() const142 virtual string_type dst_zone_name()const 143 { 144 return zone_names_.dst_zone_name(); 145 } 146 //! True if zone uses daylight savings adjustments otherwise false has_dst() const147 virtual bool has_dst()const 148 { 149 return has_dst_; 150 } 151 //! Local time that DST starts -- NADT if has_dst is false dst_local_start_time(gregorian::greg_year y) const152 virtual posix_time::ptime dst_local_start_time(gregorian::greg_year y)const 153 { 154 gregorian::date d(gregorian::not_a_date_time); 155 if(has_dst_) 156 { 157 d = dst_calc_rules_->start_day(y); 158 } 159 return posix_time::ptime(d, dst_offsets_.dst_start_offset_); 160 } 161 //! Local time that DST ends -- NADT if has_dst is false dst_local_end_time(gregorian::greg_year y) const162 virtual posix_time::ptime dst_local_end_time(gregorian::greg_year y)const 163 { 164 gregorian::date d(gregorian::not_a_date_time); 165 if(has_dst_) 166 { 167 d = dst_calc_rules_->end_day(y); 168 } 169 return posix_time::ptime(d, dst_offsets_.dst_end_offset_); 170 } 171 //! Base offset from UTC for zone (eg: -07:30:00) base_utc_offset() const172 virtual time_duration_type base_utc_offset()const 173 { 174 return base_utc_offset_; 175 } 176 //! Adjustment forward or back made while DST is in effect dst_offset() const177 virtual time_duration_type dst_offset()const 178 { 179 return dst_offsets_.dst_adjust_; 180 } 181 182 //! Returns a POSIX time_zone string for this object to_posix_string() const183 virtual string_type to_posix_string() const 184 { 185 // std offset dst [offset],start[/time],end[/time] - w/o spaces 186 stringstream_type ss; 187 ss.fill('0'); 188 boost::shared_ptr<dst_calc_rule> no_rules; 189 // std 190 ss << std_zone_abbrev(); 191 // offset 192 if(base_utc_offset().is_negative()) { 193 // inverting the sign guarantees we get two digits 194 ss << '-' << std::setw(2) << base_utc_offset().invert_sign().hours(); 195 } 196 else { 197 ss << '+' << std::setw(2) << base_utc_offset().hours(); 198 } 199 if(base_utc_offset().minutes() != 0 || base_utc_offset().seconds() != 0) { 200 ss << ':' << std::setw(2) << base_utc_offset().minutes(); 201 if(base_utc_offset().seconds() != 0) { 202 ss << ':' << std::setw(2) << base_utc_offset().seconds(); 203 } 204 } 205 if(dst_calc_rules_ != no_rules) { 206 // dst 207 ss << dst_zone_abbrev(); 208 // dst offset 209 if(dst_offset().is_negative()) { 210 // inverting the sign guarantees we get two digits 211 ss << '-' << std::setw(2) << dst_offset().invert_sign().hours(); 212 } 213 else { 214 ss << '+' << std::setw(2) << dst_offset().hours(); 215 } 216 if(dst_offset().minutes() != 0 || dst_offset().seconds() != 0) { 217 ss << ':' << std::setw(2) << dst_offset().minutes(); 218 if(dst_offset().seconds() != 0) { 219 ss << ':' << std::setw(2) << dst_offset().seconds(); 220 } 221 } 222 // start/time 223 ss << ',' << date_time::convert_string_type<char, char_type>(dst_calc_rules_->start_rule_as_string()) << '/' 224 << std::setw(2) << dst_offsets_.dst_start_offset_.hours() << ':' 225 << std::setw(2) << dst_offsets_.dst_start_offset_.minutes(); 226 if(dst_offsets_.dst_start_offset_.seconds() != 0) { 227 ss << ':' << std::setw(2) << dst_offsets_.dst_start_offset_.seconds(); 228 } 229 // end/time 230 ss << ',' << date_time::convert_string_type<char, char_type>(dst_calc_rules_->end_rule_as_string()) << '/' 231 << std::setw(2) << dst_offsets_.dst_end_offset_.hours() << ':' 232 << std::setw(2) << dst_offsets_.dst_end_offset_.minutes(); 233 if(dst_offsets_.dst_end_offset_.seconds() != 0) { 234 ss << ':' << std::setw(2) << dst_offsets_.dst_end_offset_.seconds(); 235 } 236 } 237 238 return ss.str(); 239 } 240 private: 241 time_zone_names zone_names_; 242 bool has_dst_; 243 time_duration_type base_utc_offset_; 244 dst_adjustment_offsets dst_offsets_; 245 boost::shared_ptr<dst_calc_rule> dst_calc_rules_; 246 247 /*! Extract time zone abbreviations for STD & DST as well 248 * as the offsets for the time shift that occurs and how 249 * much of a shift. At this time full time zone names are 250 * NOT extracted so the abbreviations are used in their place */ calc_zone(const string_type & obj)251 void calc_zone(const string_type& obj){ 252 const char_type empty_string[2] = {'\0'}; 253 stringstream_type ss(empty_string); 254 typename string_type::const_pointer sit = obj.c_str(), obj_end = sit + obj.size(); 255 string_type l_std_zone_abbrev, l_dst_zone_abbrev; 256 257 // get 'std' name/abbrev 258 while(std::isalpha(*sit)){ 259 ss << *sit++; 260 } 261 l_std_zone_abbrev = ss.str(); 262 ss.str(empty_string); 263 264 // get UTC offset 265 if(sit != obj_end){ 266 // get duration 267 while(sit != obj_end && !std::isalpha(*sit)){ 268 ss << *sit++; 269 } 270 base_utc_offset_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(ss.str()); 271 ss.str(empty_string); 272 273 // base offset must be within range of -12 hours to +14 hours 274 if(base_utc_offset_ < time_duration_type(-12,0,0) || 275 base_utc_offset_ > time_duration_type(14,0,0)) 276 { 277 boost::throw_exception(bad_offset(posix_time::to_simple_string(base_utc_offset_))); 278 } 279 } 280 281 // get DST data if given 282 if(sit != obj_end){ 283 has_dst_ = true; 284 285 // get 'dst' name/abbrev 286 while(sit != obj_end && std::isalpha(*sit)){ 287 ss << *sit++; 288 } 289 l_dst_zone_abbrev = ss.str(); 290 ss.str(empty_string); 291 292 // get DST offset if given 293 if(sit != obj_end){ 294 // get duration 295 while(sit != obj_end && !std::isalpha(*sit)){ 296 ss << *sit++; 297 } 298 dst_offsets_.dst_adjust_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(ss.str()); 299 ss.str(empty_string); 300 } 301 else{ // default DST offset 302 dst_offsets_.dst_adjust_ = posix_time::hours(1); 303 } 304 305 // adjustment must be within +|- 1 day 306 if(dst_offsets_.dst_adjust_ <= time_duration_type(-24,0,0) || 307 dst_offsets_.dst_adjust_ >= time_duration_type(24,0,0)) 308 { 309 boost::throw_exception(bad_adjustment(posix_time::to_simple_string(dst_offsets_.dst_adjust_))); 310 } 311 } 312 // full names not extracted so abbrevs used in their place 313 zone_names_ = time_zone_names(l_std_zone_abbrev, l_std_zone_abbrev, l_dst_zone_abbrev, l_dst_zone_abbrev); 314 } 315 calc_rules(const string_type & start,const string_type & end)316 void calc_rules(const string_type& start, const string_type& end){ 317 #ifdef __HP_aCC 318 // Work around bug in aC++ compiler: see QXCR1000880488 in the 319 // HP bug tracking system 320 const char_type sep_chars[2] = {'/',0}; 321 #else 322 const char_type sep_chars[2] = {'/'}; 323 #endif 324 char_separator_type sep(sep_chars); 325 tokenizer_type st_tok(start, sep); 326 tokenizer_type et_tok(end, sep); 327 tokenizer_iterator_type sit = st_tok.begin(); 328 tokenizer_iterator_type eit = et_tok.begin(); 329 330 // generate date spec 331 char_type x = string_type(*sit).at(0); 332 if(x == 'M'){ 333 M_func(*sit, *eit); 334 } 335 else if(x == 'J'){ 336 julian_no_leap(*sit, *eit); 337 } 338 else{ 339 julian_day(*sit, *eit); 340 } 341 342 ++sit; 343 ++eit; 344 // generate durations 345 // starting offset 346 if(sit != st_tok.end()){ 347 dst_offsets_.dst_start_offset_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(*sit); 348 } 349 else{ 350 // default 351 dst_offsets_.dst_start_offset_ = posix_time::hours(2); 352 } 353 // start/end offsets must fall on given date 354 if(dst_offsets_.dst_start_offset_ < time_duration_type(0,0,0) || 355 dst_offsets_.dst_start_offset_ >= time_duration_type(24,0,0)) 356 { 357 boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_.dst_start_offset_))); 358 } 359 360 // ending offset 361 if(eit != et_tok.end()){ 362 dst_offsets_.dst_end_offset_ = date_time::str_from_delimited_time_duration<time_duration_type,char_type>(*eit); 363 } 364 else{ 365 // default 366 dst_offsets_.dst_end_offset_ = posix_time::hours(2); 367 } 368 // start/end offsets must fall on given date 369 if(dst_offsets_.dst_end_offset_ < time_duration_type(0,0,0) || 370 dst_offsets_.dst_end_offset_ >= time_duration_type(24,0,0)) 371 { 372 boost::throw_exception(bad_offset(posix_time::to_simple_string(dst_offsets_.dst_end_offset_))); 373 } 374 } 375 376 /* Parses out a start/end date spec from a posix time zone string. 377 * Date specs come in three possible formats, this function handles 378 * the 'M' spec. Ex "M2.2.4" => 2nd month, 2nd week, 4th day . 379 */ M_func(const string_type & s,const string_type & e)380 void M_func(const string_type& s, const string_type& e){ 381 typedef gregorian::nth_kday_of_month nkday; 382 unsigned short sm=0,sw=0,sd=0,em=0,ew=0,ed=0; // start/end month,week,day 383 #ifdef __HP_aCC 384 // Work around bug in aC++ compiler: see QXCR1000880488 in the 385 // HP bug tracking system 386 const char_type sep_chars[3] = {'M','.',0}; 387 #else 388 const char_type sep_chars[3] = {'M','.'}; 389 #endif 390 char_separator_type sep(sep_chars); 391 tokenizer_type stok(s, sep), etok(e, sep); 392 393 tokenizer_iterator_type it = stok.begin(); 394 sm = lexical_cast<unsigned short>(*it++); 395 sw = lexical_cast<unsigned short>(*it++); 396 sd = lexical_cast<unsigned short>(*it); 397 398 it = etok.begin(); 399 em = lexical_cast<unsigned short>(*it++); 400 ew = lexical_cast<unsigned short>(*it++); 401 ed = lexical_cast<unsigned short>(*it); 402 403 dst_calc_rules_ = shared_ptr<dst_calc_rule>( 404 new nth_kday_dst_rule( 405 nth_last_dst_rule::start_rule( 406 static_cast<nkday::week_num>(sw),sd,sm), 407 nth_last_dst_rule::start_rule( 408 static_cast<nkday::week_num>(ew),ed,em) 409 ) 410 ); 411 } 412 413 //! Julian day. Feb29 is never counted, even in leap years 414 // expects range of 1-365 julian_no_leap(const string_type & s,const string_type & e)415 void julian_no_leap(const string_type& s, const string_type& e){ 416 typedef gregorian::gregorian_calendar calendar; 417 const unsigned short year = 2001; // Non-leap year 418 unsigned short sm=1; 419 int sd=0; 420 sd = lexical_cast<int>(s.substr(1)); // skip 'J' 421 while(sd >= calendar::end_of_month_day(year,sm)){ 422 sd -= calendar::end_of_month_day(year,sm++); 423 } 424 unsigned short em=1; 425 int ed=0; 426 ed = lexical_cast<int>(e.substr(1)); // skip 'J' 427 while(ed > calendar::end_of_month_day(year,em)){ 428 ed -= calendar::end_of_month_day(year,em++); 429 } 430 431 dst_calc_rules_ = shared_ptr<dst_calc_rule>( 432 new partial_date_dst_rule( 433 partial_date_dst_rule::start_rule( 434 static_cast<unsigned short>(sd), static_cast<date_time::months_of_year>(sm)), 435 partial_date_dst_rule::end_rule( 436 static_cast<unsigned short>(ed), static_cast<date_time::months_of_year>(em)) 437 ) 438 ); 439 } 440 441 //! Julian day. Feb29 is always counted, but exception thrown in non-leap years 442 // expects range of 0-365 julian_day(const string_type & s,const string_type & e)443 void julian_day(const string_type& s, const string_type& e){ 444 int sd=0, ed=0; 445 sd = lexical_cast<int>(s); 446 ed = lexical_cast<int>(e); 447 dst_calc_rules_ = shared_ptr<dst_calc_rule>( 448 new partial_date_dst_rule( 449 partial_date_dst_rule::start_rule(++sd),// args are 0-365 450 partial_date_dst_rule::end_rule(++ed) // pd expects 1-366 451 ) 452 ); 453 } 454 455 //! helper function used when throwing exceptions td_as_string(const time_duration_type & td)456 static std::string td_as_string(const time_duration_type& td) 457 { 458 std::string s; 459 #if defined(USE_DATE_TIME_PRE_1_33_FACET_IO) 460 s = posix_time::to_simple_string(td); 461 #else 462 std::stringstream ss; 463 ss << td; 464 s = ss.str(); 465 #endif 466 return s; 467 } 468 }; 469 470 typedef posix_time_zone_base<char> posix_time_zone; 471 472 } } // namespace boost::local_time 473 474 475 #endif // _DATE_TIME_POSIX_TIME_ZONE__ 476