Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-30 09:35:25

0001 #ifndef DATE_TIME_TZ_DB_BASE_HPP__
0002 #define DATE_TIME_TZ_DB_BASE_HPP__
0003 
0004 /* Copyright (c) 2003-2005 CrystalClear Software, Inc.
0005  * Subject to the Boost Software License, Version 1.0. 
0006  * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
0007  * Author: Jeff Garland, Bart Garst
0008  * $Date$
0009  */
0010 
0011 #include <map>
0012 #include <vector>
0013 #include <string>
0014 #include <sstream>
0015 #include <fstream>
0016 #include <stdexcept>
0017 #include <boost/tokenizer.hpp>
0018 #include <boost/shared_ptr.hpp>
0019 #include <boost/throw_exception.hpp>
0020 #include <boost/date_time/compiler_config.hpp>
0021 #include <boost/date_time/time_zone_names.hpp>
0022 #include <boost/date_time/time_zone_base.hpp>
0023 #include <boost/date_time/time_parsing.hpp>
0024 #include <boost/algorithm/string.hpp>
0025 
0026 namespace boost {
0027   namespace date_time {
0028 
0029     //! Exception thrown when tz database cannot locate requested data file
0030     class data_not_accessible : public std::logic_error
0031     {
0032      public:
0033        data_not_accessible() : 
0034          std::logic_error(std::string("Unable to locate or access the required datafile.")) 
0035        {}
0036        data_not_accessible(const std::string& filespec) : 
0037          std::logic_error(std::string("Unable to locate or access the required datafile. Filespec: " + filespec)) 
0038        {}
0039     };
0040     
0041     //! Exception thrown when tz database locates incorrect field structure in data file
0042     class bad_field_count : public std::out_of_range
0043     {
0044      public:
0045        bad_field_count(const std::string& s) : 
0046          std::out_of_range(s) 
0047       {}
0048     };
0049 
0050     //! Creates a database of time_zones from csv datafile
0051     /*! The csv file containing the zone_specs used by the
0052      * tz_db_base is intended to be customized by the
0053      * library user. When customizing this file (or creating your own) the
0054      * file must follow a specific format.
0055      * 
0056      * This first line is expected to contain column headings and is therefore
0057      * not processed by the tz_db_base.
0058      *
0059      * Each record (line) must have eleven fields. Some of those fields can
0060      * be empty. Every field (even empty ones) must be enclosed in 
0061      * double-quotes.
0062      * Ex:
0063      * @code
0064      * "America/Phoenix" <- string enclosed in quotes
0065      * ""                <- empty field
0066      * @endcode
0067      * 
0068      * Some fields represent a length of time. The format of these fields 
0069      * must be:
0070      * @code
0071      * "{+|-}hh:mm[:ss]" <- length-of-time format
0072      * @endcode
0073      * Where the plus or minus is mandatory and the seconds are optional.
0074      * 
0075      * Since some time zones do not use daylight savings it is not always 
0076      * necessary for every field in a zone_spec to contain a value. All 
0077      * zone_specs must have at least ID and GMT offset. Zones that use 
0078      * daylight savings must have all fields filled except: 
0079      * STD ABBR, STD NAME, DST NAME. You should take note 
0080      * that DST ABBR is mandatory for zones that use daylight savings 
0081      * (see field descriptions for further details).
0082      *
0083      * ******* Fields and their description/details ********* 
0084      *     
0085      * ID: 
0086      * Contains the identifying string for the zone_spec. Any string will
0087      * do as long as it's unique. No two ID's can be the same. 
0088      *
0089      * STD ABBR:
0090      * STD NAME:
0091      * DST ABBR:
0092      * DST NAME:
0093      * These four are all the names and abbreviations used by the time 
0094      * zone being described. While any string will do in these fields, 
0095      * care should be taken. These fields hold the strings that will be 
0096      * used in the output of many of the local_time classes. 
0097      * Ex:
0098      * @code
0099      * time_zone nyc = tz_db.time_zone_from_region("America/New_York");
0100      * local_time ny_time(date(2004, Aug, 30), IS_DST, nyc);
0101      * cout << ny_time.to_long_string() << endl;
0102      * // 2004-Aug-30 00:00:00 Eastern Daylight Time
0103      * cout << ny_time.to_short_string() << endl;
0104      * // 2004-Aug-30 00:00:00 EDT
0105      * @endcode
0106      *
0107      * NOTE: The exact format/function names may vary - see local_time 
0108      * documentation for further details.
0109      *
0110      * GMT offset:
0111      * This is the number of hours added to utc to get the local time 
0112      * before any daylight savings adjustments are made. Some examples 
0113      * are: America/New_York offset -5 hours, & Africa/Cairo offset +2 hours.
0114      * The format must follow the length-of-time format described above.
0115      *
0116      * DST adjustment:
0117      * The amount of time added to gmt_offset when daylight savings is in 
0118      * effect. The format must follow the length-of-time format described
0119      * above.
0120      *
0121      * DST Start Date rule:
0122      * This is a specially formatted string that describes the day of year
0123      * in which the transition take place. It holds three fields of it's own,
0124      * separated by semicolons. 
0125      * The first field indicates the "nth" weekday of the month. The possible 
0126      * values are: 1 (first), 2 (second), 3 (third), 4 (fourth), 5 (fifth), 
0127      * and -1 (last).
0128      * The second field indicates the day-of-week from 0-6 (Sun=0).
0129      * The third field indicates the month from 1-12 (Jan=1).
0130      * 
0131      * Examples are: "-1;5;9"="Last Friday of September", 
0132      * "2;1;3"="Second Monday of March"
0133      *
0134      * Start time:
0135      * Start time is the number of hours past midnight, on the day of the
0136      * start transition, the transition takes place. More simply put, the 
0137      * time of day the transition is made (in 24 hours format). The format
0138      * must follow the length-of-time format described above with the 
0139      * exception that it must always be positive.
0140      *
0141      * DST End date rule:
0142      * See DST Start date rule. The difference here is this is the day 
0143      * daylight savings ends (transition to STD).
0144      *
0145      * End time:
0146      * Same as Start time.
0147      */
0148     template<class time_zone_type, class rule_type>
0149     class tz_db_base {
0150     public:
0151       /* Having CharT as a template parameter created problems 
0152        * with posix_time::duration_from_string. Templatizing 
0153        * duration_from_string was not possible at this time, however, 
0154        * it should be possible in the future (when poor compilers get 
0155        * fixed or stop being used). 
0156        * Since this class was designed to use CharT as a parameter it 
0157        * is simply typedef'd here to ease converting in back to a 
0158        * parameter the future */
0159       typedef char char_type;
0160 
0161       typedef typename time_zone_type::base_type time_zone_base_type;
0162       typedef typename time_zone_type::time_duration_type time_duration_type;
0163       typedef time_zone_names_base<char_type> time_zone_names;
0164       typedef boost::date_time::dst_adjustment_offsets<time_duration_type> dst_adjustment_offsets;
0165       typedef std::basic_string<char_type> string_type;
0166 
0167       //! Constructs an empty database
0168       tz_db_base() {}
0169 
0170       //! Process csv data file, may throw exceptions
0171       /*! May throw bad_field_count exceptions */
0172       void load_from_stream(std::istream &in)
0173       {
0174         std::string buff;
0175         while( std::getline(in, buff)) {
0176           boost::trim_right(buff);
0177           parse_string(buff);
0178         }
0179       }
0180 
0181       //! Process csv data file, may throw exceptions
0182       /*! May throw data_not_accessible, or bad_field_count exceptions */
0183       void load_from_file(const std::string& pathspec)
0184       {
0185         std::string  buff;
0186         
0187         std::ifstream ifs(pathspec.c_str());
0188         if(!ifs){
0189           boost::throw_exception(data_not_accessible(pathspec));
0190         }
0191         std::getline(ifs, buff); // first line is column headings
0192         this->load_from_stream(ifs);
0193       }
0194 
0195       //! returns true if record successfully added to map
0196       /*! Takes a region name in the form of "America/Phoenix", and a 
0197        * time_zone object for that region. The id string must be a unique 
0198        * name that does not already exist in the database. */
0199       bool add_record(const string_type& region, 
0200                       boost::shared_ptr<time_zone_base_type> tz)
0201       {
0202         typename map_type::value_type p(region, tz); 
0203         return (m_zone_map.insert(p)).second;
0204       }
0205 
0206       //! Returns a time_zone object built from the specs for the given region
0207       /*! Returns a time_zone object built from the specs for the given 
0208        * region. If region does not exist a local_time::record_not_found 
0209        * exception will be thrown */
0210       boost::shared_ptr<time_zone_base_type> 
0211       time_zone_from_region(const string_type& region) const 
0212       {
0213         // get the record
0214         typename map_type::const_iterator record = m_zone_map.find(region);
0215         if(record == m_zone_map.end()){
0216           return boost::shared_ptr<time_zone_base_type>(); //null pointer
0217         }
0218         return record->second;
0219       }
0220 
0221       //! Returns a vector of strings holding the time zone regions in the database
0222       std::vector<std::string> region_list() const
0223       {
0224         typedef std::vector<std::string> vector_type;
0225         vector_type regions;
0226         typename map_type::const_iterator itr = m_zone_map.begin();
0227         while(itr != m_zone_map.end()) {
0228           regions.push_back(itr->first);
0229           ++itr;
0230         }
0231         return regions;
0232       }
0233     
0234     private:
0235       typedef std::map<string_type, boost::shared_ptr<time_zone_base_type> > map_type;
0236       map_type m_zone_map;
0237 
0238       // start and end rule are of the same type
0239       typedef typename rule_type::start_rule::week_num week_num;
0240 
0241       /* TODO: mechanisms need to be put in place to handle different
0242        * types of rule specs. parse_rules() only handles nth_kday
0243        * rule types. */
0244       
0245       //! parses rule specs for transition day rules
0246       rule_type* parse_rules(const string_type& sr, const string_type& er) const
0247       {
0248         // start and end rule are of the same type, 
0249         // both are included here for readability
0250         typedef typename rule_type::start_rule start_rule;
0251         typedef typename rule_type::end_rule end_rule;
0252        
0253         // these are: [start|end] nth, day, month
0254         int s_nth = 0, s_d = 0, s_m = 0;
0255         int e_nth = 0, e_d = 0, e_m = 0;
0256         split_rule_spec(s_nth, s_d, s_m, sr);
0257         split_rule_spec(e_nth, e_d, e_m, er);
0258         
0259         typename start_rule::week_num s_wn, e_wn;
0260         s_wn = get_week_num(s_nth);
0261         e_wn = get_week_num(e_nth);
0262         
0263         
0264         return new rule_type(start_rule(s_wn,
0265                                         static_cast<unsigned short>(s_d),
0266                                         static_cast<unsigned short>(s_m)),
0267                              end_rule(e_wn,
0268                                       static_cast<unsigned short>(e_d),
0269                                       static_cast<unsigned short>(e_m)));
0270       }
0271       //! helper function for parse_rules()
0272       week_num get_week_num(int nth) const
0273       {
0274         typedef typename rule_type::start_rule start_rule;
0275         switch(nth){
0276         case 1:
0277           return start_rule::first;
0278         case 2:
0279           return start_rule::second;
0280         case 3:
0281           return start_rule::third;
0282         case 4:
0283           return start_rule::fourth;
0284         case 5:
0285         case -1:
0286           return start_rule::fifth;
0287         default:
0288           // shouldn't get here - add error handling later
0289           break;
0290         }
0291         return start_rule::fifth; // silence warnings
0292       }
0293           
0294       //! splits the [start|end]_date_rule string into 3 ints
0295       void split_rule_spec(int& nth, int& d, int& m, string_type rule) const
0296       {
0297         typedef boost::char_separator<char_type, std::char_traits<char_type> > char_separator_type;
0298         typedef boost::tokenizer<char_separator_type,
0299                                  std::basic_string<char_type>::const_iterator,
0300                                  std::basic_string<char_type> > tokenizer;
0301         typedef boost::tokenizer<char_separator_type,
0302                                  std::basic_string<char_type>::const_iterator,
0303                                  std::basic_string<char_type> >::iterator tokenizer_iterator;
0304         
0305         const char_type sep_char[] = { ';', '\0'};
0306         char_separator_type sep(sep_char);
0307         tokenizer tokens(rule, sep); // 3 fields
0308 
0309         if ( std::distance ( tokens.begin(), tokens.end ()) != 3 ) {
0310           std::ostringstream msg;
0311           msg << "Expecting 3 fields, got " 
0312               << std::distance ( tokens.begin(), tokens.end ()) 
0313               << " fields in line: " << rule;
0314           boost::throw_exception(bad_field_count(msg.str()));
0315         }
0316 
0317         tokenizer_iterator tok_iter = tokens.begin(); 
0318         nth = std::atoi(tok_iter->c_str()); ++tok_iter;
0319         d   = std::atoi(tok_iter->c_str()); ++tok_iter;
0320         m   = std::atoi(tok_iter->c_str());
0321       }
0322 
0323      
0324       //! Take a line from the csv, turn it into a time_zone_type.
0325       /*! Take a line from the csv, turn it into a time_zone_type,
0326        * and add it to the map. Zone_specs in csv file are expected to 
0327        * have eleven fields that describe the time zone. Returns true if 
0328        * zone_spec successfully added to database */
0329       bool parse_string(string_type& s)
0330       {
0331         std::vector<string_type> result;
0332         typedef boost::token_iterator_generator<boost::escaped_list_separator<char_type>, string_type::const_iterator, string_type >::type token_iter_type;
0333 
0334         token_iter_type i = boost::make_token_iterator<string_type>(s.begin(), s.end(),boost::escaped_list_separator<char_type>());
0335 
0336         token_iter_type end;
0337         while (i != end) {
0338           result.push_back(*i);
0339           i++;
0340         }
0341 
0342         enum db_fields { ID, STDABBR, STDNAME, DSTABBR, DSTNAME, GMTOFFSET,
0343                          DSTADJUST, START_DATE_RULE, START_TIME, END_DATE_RULE,
0344                          END_TIME, FIELD_COUNT };
0345 
0346         //take a shot at fixing gcc 4.x error
0347         const unsigned int expected_fields = static_cast<unsigned int>(FIELD_COUNT);
0348         if (result.size() != expected_fields) { 
0349           std::ostringstream msg;
0350           msg << "Expecting " << FIELD_COUNT << " fields, got " 
0351             << result.size() << " fields in line: " << s;
0352           boost::throw_exception(bad_field_count(msg.str()));
0353           BOOST_DATE_TIME_UNREACHABLE_EXPRESSION(return false); // should never reach
0354         }
0355 
0356         // initializations
0357         bool has_dst = true; 
0358         if(result[DSTABBR] == std::string()){
0359           has_dst = false;
0360         }
0361 
0362 
0363         // start building components of a time_zone
0364         time_zone_names names(result[STDNAME], result[STDABBR],
0365                               result[DSTNAME], result[DSTABBR]);
0366 
0367         time_duration_type utc_offset = 
0368           str_from_delimited_time_duration<time_duration_type,char_type>(result[GMTOFFSET]);
0369         
0370         dst_adjustment_offsets adjust(time_duration_type(0,0,0),
0371                                       time_duration_type(0,0,0),
0372                                       time_duration_type(0,0,0));
0373 
0374         boost::shared_ptr<rule_type> rules;
0375 
0376         if(has_dst){
0377           adjust = dst_adjustment_offsets(
0378                                           str_from_delimited_time_duration<time_duration_type,char_type>(result[DSTADJUST]),
0379                                           str_from_delimited_time_duration<time_duration_type,char_type>(result[START_TIME]),
0380                                           str_from_delimited_time_duration<time_duration_type,char_type>(result[END_TIME])
0381                                           );
0382 
0383           rules = 
0384             boost::shared_ptr<rule_type>(parse_rules(result[START_DATE_RULE],
0385                                                      result[END_DATE_RULE]));
0386         }
0387         string_type id(result[ID]);
0388         boost::shared_ptr<time_zone_base_type> zone(new time_zone_type(names, utc_offset, adjust, rules));
0389         return (add_record(id, zone));
0390         
0391       } 
0392      
0393     };
0394 
0395 } } // namespace
0396 
0397 #endif // DATE_TIME_TZ_DB_BASE_HPP__