• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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