posix_time_zone.hpp 18 KB

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