Chris@16: #ifndef DATE_TIME_TZ_DB_BASE_HPP__ Chris@16: #define DATE_TIME_TZ_DB_BASE_HPP__ Chris@16: Chris@16: /* Copyright (c) 2003-2005 CrystalClear Software, Inc. Chris@16: * Subject to the Boost Software License, Version 1.0. Chris@16: * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) Chris@16: * Author: Jeff Garland, Bart Garst Chris@101: * $Date$ Chris@16: */ Chris@16: Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: Chris@16: namespace boost { Chris@16: namespace date_time { Chris@16: Chris@16: //! Exception thrown when tz database cannot locate requested data file Chris@16: class data_not_accessible : public std::logic_error Chris@16: { Chris@16: public: Chris@16: data_not_accessible() : Chris@16: std::logic_error(std::string("Unable to locate or access the required datafile.")) Chris@16: {} Chris@16: data_not_accessible(const std::string& filespec) : Chris@16: std::logic_error(std::string("Unable to locate or access the required datafile. Filespec: " + filespec)) Chris@16: {} Chris@16: }; Chris@16: Chris@16: //! Exception thrown when tz database locates incorrect field structure in data file Chris@16: class bad_field_count : public std::out_of_range Chris@16: { Chris@16: public: Chris@16: bad_field_count(const std::string& s) : Chris@16: std::out_of_range(s) Chris@16: {} Chris@16: }; Chris@16: Chris@16: //! Creates a database of time_zones from csv datafile Chris@16: /*! The csv file containing the zone_specs used by the Chris@16: * tz_db_base is intended to be customized by the Chris@16: * library user. When customizing this file (or creating your own) the Chris@16: * file must follow a specific format. Chris@16: * Chris@16: * This first line is expected to contain column headings and is therefore Chris@16: * not processed by the tz_db_base. Chris@16: * Chris@16: * Each record (line) must have eleven fields. Some of those fields can Chris@16: * be empty. Every field (even empty ones) must be enclosed in Chris@16: * double-quotes. Chris@16: * Ex: Chris@16: * @code Chris@16: * "America/Phoenix" <- string enclosed in quotes Chris@16: * "" <- empty field Chris@16: * @endcode Chris@16: * Chris@16: * Some fields represent a length of time. The format of these fields Chris@16: * must be: Chris@16: * @code Chris@16: * "{+|-}hh:mm[:ss]" <- length-of-time format Chris@16: * @endcode Chris@16: * Where the plus or minus is mandatory and the seconds are optional. Chris@16: * Chris@16: * Since some time zones do not use daylight savings it is not always Chris@16: * necessary for every field in a zone_spec to contain a value. All Chris@16: * zone_specs must have at least ID and GMT offset. Zones that use Chris@16: * daylight savings must have all fields filled except: Chris@16: * STD ABBR, STD NAME, DST NAME. You should take note Chris@16: * that DST ABBR is mandatory for zones that use daylight savings Chris@16: * (see field descriptions for further details). Chris@16: * Chris@16: * ******* Fields and their description/details ********* Chris@16: * Chris@16: * ID: Chris@16: * Contains the identifying string for the zone_spec. Any string will Chris@16: * do as long as it's unique. No two ID's can be the same. Chris@16: * Chris@16: * STD ABBR: Chris@16: * STD NAME: Chris@16: * DST ABBR: Chris@16: * DST NAME: Chris@16: * These four are all the names and abbreviations used by the time Chris@16: * zone being described. While any string will do in these fields, Chris@16: * care should be taken. These fields hold the strings that will be Chris@16: * used in the output of many of the local_time classes. Chris@16: * Ex: Chris@16: * @code Chris@16: * time_zone nyc = tz_db.time_zone_from_region("America/New_York"); Chris@16: * local_time ny_time(date(2004, Aug, 30), IS_DST, nyc); Chris@16: * cout << ny_time.to_long_string() << endl; Chris@16: * // 2004-Aug-30 00:00:00 Eastern Daylight Time Chris@16: * cout << ny_time.to_short_string() << endl; Chris@16: * // 2004-Aug-30 00:00:00 EDT Chris@16: * @endcode Chris@16: * Chris@16: * NOTE: The exact format/function names may vary - see local_time Chris@16: * documentation for further details. Chris@16: * Chris@16: * GMT offset: Chris@16: * This is the number of hours added to utc to get the local time Chris@16: * before any daylight savings adjustments are made. Some examples Chris@16: * are: America/New_York offset -5 hours, & Africa/Cairo offset +2 hours. Chris@16: * The format must follow the length-of-time format described above. Chris@16: * Chris@16: * DST adjustment: Chris@16: * The amount of time added to gmt_offset when daylight savings is in Chris@16: * effect. The format must follow the length-of-time format described Chris@16: * above. Chris@16: * Chris@16: * DST Start Date rule: Chris@16: * This is a specially formatted string that describes the day of year Chris@16: * in which the transition take place. It holds three fields of it's own, Chris@16: * separated by semicolons. Chris@16: * The first field indicates the "nth" weekday of the month. The possible Chris@16: * values are: 1 (first), 2 (second), 3 (third), 4 (fourth), 5 (fifth), Chris@16: * and -1 (last). Chris@16: * The second field indicates the day-of-week from 0-6 (Sun=0). Chris@16: * The third field indicates the month from 1-12 (Jan=1). Chris@16: * Chris@16: * Examples are: "-1;5;9"="Last Friday of September", Chris@16: * "2;1;3"="Second Monday of March" Chris@16: * Chris@16: * Start time: Chris@16: * Start time is the number of hours past midnight, on the day of the Chris@16: * start transition, the transition takes place. More simply put, the Chris@16: * time of day the transition is made (in 24 hours format). The format Chris@16: * must follow the length-of-time format described above with the Chris@16: * exception that it must always be positive. Chris@16: * Chris@16: * DST End date rule: Chris@16: * See DST Start date rule. The difference here is this is the day Chris@16: * daylight savings ends (transition to STD). Chris@16: * Chris@16: * End time: Chris@16: * Same as Start time. Chris@16: */ Chris@16: template Chris@16: class tz_db_base { Chris@16: public: Chris@16: /* Having CharT as a template parameter created problems Chris@16: * with posix_time::duration_from_string. Templatizing Chris@16: * duration_from_string was not possible at this time, however, Chris@16: * it should be possible in the future (when poor compilers get Chris@16: * fixed or stop being used). Chris@16: * Since this class was designed to use CharT as a parameter it Chris@16: * is simply typedef'd here to ease converting in back to a Chris@16: * parameter the future */ Chris@16: typedef char char_type; Chris@16: Chris@16: typedef typename time_zone_type::base_type time_zone_base_type; Chris@16: typedef typename time_zone_type::time_duration_type time_duration_type; Chris@16: typedef time_zone_names_base time_zone_names; Chris@16: typedef boost::date_time::dst_adjustment_offsets dst_adjustment_offsets; Chris@16: typedef std::basic_string string_type; Chris@16: Chris@16: //! Constructs an empty database Chris@16: tz_db_base() {} Chris@16: Chris@16: //! Process csv data file, may throw exceptions Chris@16: /*! May throw bad_field_count exceptions */ Chris@16: void load_from_stream(std::istream &in) Chris@16: { Chris@16: std::string buff; Chris@16: while( std::getline(in, buff)) { Chris@16: parse_string(buff); Chris@16: } Chris@16: } Chris@16: Chris@16: //! Process csv data file, may throw exceptions Chris@16: /*! May throw data_not_accessible, or bad_field_count exceptions */ Chris@16: void load_from_file(const std::string& pathspec) Chris@16: { Chris@16: std::string buff; Chris@16: Chris@16: std::ifstream ifs(pathspec.c_str()); Chris@16: if(!ifs){ Chris@16: boost::throw_exception(data_not_accessible(pathspec)); Chris@16: } Chris@16: std::getline(ifs, buff); // first line is column headings Chris@16: this->load_from_stream(ifs); Chris@16: } Chris@16: Chris@16: //! returns true if record successfully added to map Chris@16: /*! Takes a region name in the form of "America/Phoenix", and a Chris@16: * time_zone object for that region. The id string must be a unique Chris@16: * name that does not already exist in the database. */ Chris@16: bool add_record(const string_type& region, Chris@16: boost::shared_ptr tz) Chris@16: { Chris@16: typename map_type::value_type p(region, tz); Chris@16: return (m_zone_map.insert(p)).second; Chris@16: } Chris@16: Chris@16: //! Returns a time_zone object built from the specs for the given region Chris@16: /*! Returns a time_zone object built from the specs for the given Chris@16: * region. If region does not exist a local_time::record_not_found Chris@16: * exception will be thrown */ Chris@16: boost::shared_ptr Chris@16: time_zone_from_region(const string_type& region) const Chris@16: { Chris@16: // get the record Chris@16: typename map_type::const_iterator record = m_zone_map.find(region); Chris@16: if(record == m_zone_map.end()){ Chris@16: return boost::shared_ptr(); //null pointer Chris@16: } Chris@16: return record->second; Chris@16: } Chris@16: Chris@16: //! Returns a vector of strings holding the time zone regions in the database Chris@16: std::vector region_list() const Chris@16: { Chris@16: typedef std::vector vector_type; Chris@16: vector_type regions; Chris@16: typename map_type::const_iterator itr = m_zone_map.begin(); Chris@16: while(itr != m_zone_map.end()) { Chris@16: regions.push_back(itr->first); Chris@16: ++itr; Chris@16: } Chris@16: return regions; Chris@16: } Chris@16: Chris@16: private: Chris@16: typedef std::map > map_type; Chris@16: map_type m_zone_map; Chris@16: Chris@16: // start and end rule are of the same type Chris@16: typedef typename rule_type::start_rule::week_num week_num; Chris@16: Chris@16: /* TODO: mechanisms need to be put in place to handle different Chris@16: * types of rule specs. parse_rules() only handles nth_kday Chris@16: * rule types. */ Chris@16: Chris@16: //! parses rule specs for transition day rules Chris@16: rule_type* parse_rules(const string_type& sr, const string_type& er) const Chris@16: { Chris@16: using namespace gregorian; Chris@16: // start and end rule are of the same type, Chris@16: // both are included here for readability Chris@16: typedef typename rule_type::start_rule start_rule; Chris@16: typedef typename rule_type::end_rule end_rule; Chris@16: Chris@16: // these are: [start|end] nth, day, month Chris@16: int s_nth = 0, s_d = 0, s_m = 0; Chris@16: int e_nth = 0, e_d = 0, e_m = 0; Chris@16: split_rule_spec(s_nth, s_d, s_m, sr); Chris@16: split_rule_spec(e_nth, e_d, e_m, er); Chris@16: Chris@16: typename start_rule::week_num s_wn, e_wn; Chris@16: s_wn = get_week_num(s_nth); Chris@16: e_wn = get_week_num(e_nth); Chris@16: Chris@16: Chris@101: return new rule_type(start_rule(s_wn, Chris@101: static_cast(s_d), Chris@101: static_cast(s_m)), Chris@101: end_rule(e_wn, Chris@101: static_cast(e_d), Chris@101: static_cast(e_m))); Chris@16: } Chris@16: //! helper function for parse_rules() Chris@16: week_num get_week_num(int nth) const Chris@16: { Chris@16: typedef typename rule_type::start_rule start_rule; Chris@16: switch(nth){ Chris@16: case 1: Chris@16: return start_rule::first; Chris@16: case 2: Chris@16: return start_rule::second; Chris@16: case 3: Chris@16: return start_rule::third; Chris@16: case 4: Chris@16: return start_rule::fourth; Chris@16: case 5: Chris@16: case -1: Chris@16: return start_rule::fifth; Chris@16: default: Chris@16: // shouldn't get here - add error handling later Chris@16: break; Chris@16: } Chris@16: return start_rule::fifth; // silence warnings Chris@16: } Chris@16: Chris@16: //! splits the [start|end]_date_rule string into 3 ints Chris@16: void split_rule_spec(int& nth, int& d, int& m, string_type rule) const Chris@16: { Chris@16: typedef boost::char_separator > char_separator_type; Chris@16: typedef boost::tokenizer::const_iterator, Chris@16: std::basic_string > tokenizer; Chris@16: typedef boost::tokenizer::const_iterator, Chris@16: std::basic_string >::iterator tokenizer_iterator; Chris@16: Chris@16: const char_type sep_char[] = { ';', '\0'}; Chris@16: char_separator_type sep(sep_char); Chris@16: tokenizer tokens(rule, sep); // 3 fields Chris@16: Chris@16: if ( std::distance ( tokens.begin(), tokens.end ()) != 3 ) { Chris@16: std::ostringstream msg; Chris@16: msg << "Expecting 3 fields, got " Chris@16: << std::distance ( tokens.begin(), tokens.end ()) Chris@16: << " fields in line: " << rule; Chris@16: boost::throw_exception(bad_field_count(msg.str())); Chris@16: } Chris@16: Chris@16: tokenizer_iterator tok_iter = tokens.begin(); Chris@16: nth = std::atoi(tok_iter->c_str()); ++tok_iter; Chris@16: d = std::atoi(tok_iter->c_str()); ++tok_iter; Chris@16: m = std::atoi(tok_iter->c_str()); Chris@16: } Chris@16: Chris@16: Chris@16: //! Take a line from the csv, turn it into a time_zone_type. Chris@16: /*! Take a line from the csv, turn it into a time_zone_type, Chris@16: * and add it to the map. Zone_specs in csv file are expected to Chris@16: * have eleven fields that describe the time zone. Returns true if Chris@16: * zone_spec successfully added to database */ Chris@16: bool parse_string(string_type& s) Chris@16: { Chris@16: std::vector result; Chris@16: typedef boost::token_iterator_generator, string_type::const_iterator, string_type >::type token_iter_type; Chris@16: Chris@16: token_iter_type i = boost::make_token_iterator(s.begin(), s.end(),boost::escaped_list_separator()); Chris@16: Chris@16: token_iter_type end; Chris@16: while (i != end) { Chris@16: result.push_back(*i); Chris@16: i++; Chris@16: } Chris@16: Chris@16: enum db_fields { ID, STDABBR, STDNAME, DSTABBR, DSTNAME, GMTOFFSET, Chris@16: DSTADJUST, START_DATE_RULE, START_TIME, END_DATE_RULE, Chris@16: END_TIME, FIELD_COUNT }; Chris@16: Chris@16: //take a shot at fixing gcc 4.x error Chris@16: const unsigned int expected_fields = static_cast(FIELD_COUNT); Chris@16: if (result.size() != expected_fields) { Chris@16: std::ostringstream msg; Chris@16: msg << "Expecting " << FIELD_COUNT << " fields, got " Chris@16: << result.size() << " fields in line: " << s; Chris@16: boost::throw_exception(bad_field_count(msg.str())); Chris@16: BOOST_DATE_TIME_UNREACHABLE_EXPRESSION(return false); // should never reach Chris@16: } Chris@16: Chris@16: // initializations Chris@16: bool has_dst = true; Chris@16: if(result[DSTABBR] == std::string()){ Chris@16: has_dst = false; Chris@16: } Chris@16: Chris@16: Chris@16: // start building components of a time_zone Chris@16: time_zone_names names(result[STDNAME], result[STDABBR], Chris@16: result[DSTNAME], result[DSTABBR]); Chris@16: Chris@16: time_duration_type utc_offset = Chris@16: str_from_delimited_time_duration(result[GMTOFFSET]); Chris@16: Chris@16: dst_adjustment_offsets adjust(time_duration_type(0,0,0), Chris@16: time_duration_type(0,0,0), Chris@16: time_duration_type(0,0,0)); Chris@16: Chris@16: boost::shared_ptr rules; Chris@16: Chris@16: if(has_dst){ Chris@16: adjust = dst_adjustment_offsets( Chris@16: str_from_delimited_time_duration(result[DSTADJUST]), Chris@16: str_from_delimited_time_duration(result[START_TIME]), Chris@16: str_from_delimited_time_duration(result[END_TIME]) Chris@16: ); Chris@16: Chris@16: rules = Chris@16: boost::shared_ptr(parse_rules(result[START_DATE_RULE], Chris@16: result[END_DATE_RULE])); Chris@16: } Chris@16: string_type id(result[ID]); Chris@16: boost::shared_ptr zone(new time_zone_type(names, utc_offset, adjust, rules)); Chris@16: return (add_record(id, zone)); Chris@16: Chris@16: } Chris@16: Chris@16: }; Chris@16: Chris@16: } } // namespace Chris@16: Chris@16: #endif // DATE_TIME_TZ_DB_BASE_HPP__