source: NonGTP/Boost/boost/date_time/local_time/posix_time_zone.hpp @ 857

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