|
- //
- // Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
- //
- // Distributed under the Boost Software License, Version 1.0. (See
- // accompanying file LICENSE_1_0.txt or copy at
- // http://www.boost.org/LICENSE_1_0.txt)
- //
- #define BOOSTER_SOURCE
- #include <booster/config.h>
- #ifdef BOOSTER_MSVC
- # pragma warning(disable : 4996)
- #endif
- #include <locale>
- #include <string>
- #include <ios>
- #include <booster/locale/date_time_facet.h>
- #include <booster/locale/date_time.h>
- #include <stdlib.h>
- #include <ctime>
- #include <booster/auto_ptr_inc.h>
- #include <algorithm>
- #include <limits>
-
- #include "timezone.h"
- #include "gregorian.h"
-
- namespace booster {
- namespace locale {
- namespace util {
- namespace {
-
- int is_leap(int year)
- {
- if(year % 400 == 0)
- return 1;
- if(year % 100 == 0)
- return 0;
- if(year % 4 == 0)
- return 1;
- return 0;
- }
-
- int days_in_month(int year,int month)
- {
- static const int tbl[2][12] = {
- { 31,28,31,30,31,30,31,31,30,31,30,31 },
- { 31,29,31,30,31,30,31,31,30,31,30,31 }
- };
- return tbl[is_leap(year)][month - 1];
- }
-
- inline int days_from_0(int year)
- {
- year--;
- return 365 * year + (year / 400) - (year/100) + (year / 4);
- }
-
- int days_from_1970(int year)
- {
- static const int days_from_0_to_1970 = days_from_0(1970);
- return days_from_0(year) - days_from_0_to_1970;
- }
-
- int days_from_1jan(int year,int month,int day)
- {
- static const int days[2][12] = {
- { 0,31,59,90,120,151,181,212,243,273,304,334 },
- { 0,31,60,91,121,152,182,213,244,274,305,335 }
- };
- return days[is_leap(year)][month-1] + day - 1;
- }
-
- time_t internal_timegm(std::tm const *t)
- {
- int year = t->tm_year + 1900;
- int month = t->tm_mon;
- if(month > 11) {
- year += month/12;
- month %= 12;
- }
- else if(month < 0) {
- int years_diff = (-month + 11)/12;
- year -= years_diff;
- month+=12 * years_diff;
- }
- month++;
- int day = t->tm_mday;
- int day_of_year = days_from_1jan(year,month,day);
- int days_since_epoch = days_from_1970(year) + day_of_year;
-
- time_t seconds_in_day = 3600 * 24;
- time_t result = seconds_in_day * days_since_epoch + 3600 * t->tm_hour + 60 * t->tm_min + t->tm_sec;
-
- return result;
- }
-
- } // anon
-
-
-
-
- namespace {
-
- // Locale dependent data
-
- bool comparator(char const *left,char const *right)
- {
- return strcmp(left,right) < 0;
- }
-
- //
- // Ref: CLDR 1.9 common/supplemental/supplementalData.xml
- //
- // monday - default
- // fri - MV
- // sat - AE AF BH DJ DZ EG ER ET IQ IR JO KE KW LY MA OM QA SA SD SO SY TN YE
- // sun - AR AS AZ BW CA CN FO GE GL GU HK IL IN JM JP KG KR LA MH MN MO MP MT NZ PH PK SG TH TT TW UM US UZ VI ZW
- //
-
- int first_day_of_week(char const *terr) {
- static char const * const sat[] = {
- "AE","AF","BH","DJ","DZ","EG","ER","ET","IQ","IR",
- "JO","KE","KW","LY","MA","OM","QA","SA","SD","SO",
- "SY","TN","YE"
- };
- // workaround for Sun Solaris !@#%@#$%@#$%234
- #ifdef sun
- #undef sun
- #endif
- static char const * const sun[] = {
- "AR","AS","AZ","BW","CA","CN","FO","GE","GL","GU",
- "HK","IL","IN","JM","JP","KG","KR","LA","MH","MN",
- "MO","MP","MT","NZ","PH","PK","SG","TH","TT","TW",
- "UM","US","UZ","VI","ZW"
- };
- if(strcmp(terr,"MV") == 0)
- return 5; // fri
- if(std::binary_search<char const * const *>(sat,sat+sizeof(sat)/(sizeof(sat[0])),terr,comparator))
- return 6; // sat
- if(std::binary_search<char const * const *>(sun,sun+sizeof(sun)/(sizeof(sun[0])),terr,comparator))
- return 0; // sun
- // default
- return 1; // mon
- }
- }
-
- class gregorian_calendar : public abstract_calendar {
- public:
-
- gregorian_calendar(std::string const &terr)
- {
- first_day_of_week_ = first_day_of_week(terr.c_str());
- time_ = time(0);
- is_local_ = true;
- tzoff_ = 0;
- from_time(time_);
- }
-
- ///
- /// Make a polymorphic copy of the calendar
- ///
- virtual gregorian_calendar *clone() const
- {
- return new gregorian_calendar(*this);
- }
-
- ///
- /// Set specific \a value for period \a p, note not all values are settable.
- ///
- virtual void set_value(period::marks::period_mark p,int value)
- {
- using namespace period::marks;
- switch(p) {
- case era: ///< Era i.e. AC, BC in Gregorian and Julian calendar, range [0,1]
- return;
- case year: ///< Year, it is calendar specific
- case extended_year: ///< Extended year for Gregorian/Julian calendars, where 1 BC == 0, 2 BC == -1.
- tm_updated_.tm_year = value - 1900;
- break;
- case month:
- tm_updated_.tm_mon = value;
- break;
- case day:
- tm_updated_.tm_mday = value;
- break;
- case hour: ///< 24 clock hour [0..23]
- tm_updated_.tm_hour = value;
- break;
- case hour_12: ///< 12 clock hour [0..11]
- tm_updated_.tm_hour = tm_updated_.tm_hour / 12 * 12 + value;
- break;
- case am_pm: ///< am or pm marker, [0..1]
- tm_updated_.tm_hour = 12 * value + tm_updated_.tm_hour % 12;
- break;
- case minute: ///< minute [0..59]
- tm_updated_.tm_min = value;
- break;
- case second:
- tm_updated_.tm_sec = value;
- break;
- case day_of_year:
- normalize();
- tm_updated_.tm_mday += (value - (tm_updated_.tm_yday + 1));
- break;
- case day_of_week: ///< Day of week, starting from Sunday, [1..7]
- if(value < 1) // make sure it is positive
- value += (-value / 7) * 7 + 7;
- // convert to local DOW
- value = (value - 1 - first_day_of_week_ + 14) % 7 + 1;
- // fall throght
- case day_of_week_local: ///< Local day of week, for example in France Monday is 1, in US Sunday is 1, [1..7]
- normalize();
- tm_updated_.tm_mday += (value - 1) - (tm_updated_.tm_wday - first_day_of_week_ + 7) % 7;
- break;
- case day_of_week_in_month: ///< Original number of the day of the week in month. (1st sunday, 2nd sunday etc)
- case week_of_year: ///< The week number in the year, 4 is the minimal number of days to be in month
- case week_of_month: ///< The week number withing current month
- {
- normalize();
- int current_week = get_value(p,current);
- int diff = 7 * (value - current_week);
- tm_updated_.tm_mday += diff;
- }
- break;
- case period::marks::first_day_of_week: ///< For example Sunday in US, Monday in France
- default:
- return;
- }
- normalized_ = false;
- }
-
- void normalize()
- {
- if(!normalized_) {
- std::tm val = tm_updated_;
- val.tm_isdst = -1;
- val.tm_wday = -1; // indecator of error
- time_t point = -1;
- if(is_local_) {
- point = mktime(&val);
- if(point == static_cast<time_t>(-1)){
- #ifndef BOOSTER_WIN_NATIVE
- // windows does not handle negative time_t, under other plaforms
- // it may be actually valid value in 1969-12-31 23:59:59
- // so we check that a filed was updated - does not happen in case of error
- if(val.tm_wday == -1)
- #endif
- {
- throw date_time_error("boost::locale::gregorian_calendar: invalid time");
- }
- }
- }
- else {
- point = internal_timegm(&val);
- #ifdef BOOSTER_WIN_NATIVE
- // Windows uses TLS, thread safe
- std::tm *revert_point = 0;
- if(point < 0 || (revert_point = gmtime(&point)) == 0)
- throw date_time_error("boost::locale::gregorian_calendar time is out of range");
- val = *revert_point;
- #else
- if(!gmtime_r(&point,&val))
- throw date_time_error("boost::locale::gregorian_calendar invalid time");
- #endif
-
- }
-
- time_ = point - tzoff_;
- tm_ = val;
- tm_updated_ = val;
- normalized_ = true;
- }
- }
-
- int get_week_number(int day,int wday) const
- {
- ///
- /// This is the number of days that are considered withing
- /// period such that the week belongs there
- ///
- static const int days_in_full_week = 4;
-
-
- // Alaways use local week start
- int current_dow = (wday - first_day_of_week_ + 7) % 7;
- // Calculate local week day of Jan 1st.
- int first_week_day = (current_dow + 700 - day) % 7;
- // adding something big devidable by 7
-
- int start_of_period_in_weeks;
- if(first_week_day < days_in_full_week) {
- start_of_period_in_weeks = - first_week_day;
- }
- else {
- start_of_period_in_weeks = 7 - first_week_day;
- }
- int week_number_in_days = day - start_of_period_in_weeks;
- if(week_number_in_days < 0)
- return -1;
- return week_number_in_days / 7 + 1;
- }
-
- ///
- /// Get specific value for period \a p according to a value_type \a v
- ///
- virtual int get_value(period::marks::period_mark p,value_type v) const
- {
- using namespace period::marks;
- switch(p) {
- case era:
- return 1;
- case year:
- case extended_year:
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- #ifdef BOOSTER_WIN_NATIVE
- return 1970; // Unix epoch windows can't handle negative time_t
- #else
- if(sizeof(time_t) == 4)
- return 1901; // minimal year with 32 bit time_t
- else
- return 1;
- #endif
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- if(sizeof(time_t) == 4)
- return 2038; // Y2K38 - maximal with 32 bit time_t
- else
- return std::numeric_limits<int>::max();
- case current:
- return tm_.tm_year + 1900;
- };
- break;
- case month:
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 0;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 11;
- case current:
- return tm_.tm_mon;
- };
- break;
- case day:
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 1;
- case absolute_maximum:
- return 31;
- case least_maximum:
- return 28;
- case actual_maximum:
- return days_in_month(tm_.tm_year + 1900,tm_.tm_mon + 1);
- case current:
- return tm_.tm_mday;
- };
- break;
- case day_of_year: ///< The number of day in year, starting from 1
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 1;
- case absolute_maximum:
- return 366;
- case least_maximum:
- return 365;
- case actual_maximum:
- return is_leap(tm_.tm_year + 1900) ? 366 : 365;
- case current:
- return tm_.tm_yday + 1;
- }
- break;
- case day_of_week: ///< Day of week, starting from Sunday, [1..7]
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 1;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 7;
- case current:
- return tm_.tm_wday + 1;
- }
- break;
- case day_of_week_local: ///< Local day of week, for example in France Monday is 1, in US Sunday is 1, [1..7]
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 1;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 7;
- case current:
- return (tm_.tm_wday - first_day_of_week_ + 7) % 7 + 1;
- }
- break;
- case hour: ///< 24 clock hour [0..23]
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 0;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 23;
- case current:
- return tm_.tm_hour;
- }
- break;
- case hour_12: ///< 12 clock hour [0..11]
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 0;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 11;
- case current:
- return tm_.tm_hour % 12;
- }
- break;
- case am_pm: ///< am or pm marker, [0..1]
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 0;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 1;
- case current:
- return tm_.tm_hour >= 12 ? 1 : 0;
- }
- break;
- case minute: ///< minute [0..59]
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 0;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 59;
- case current:
- return tm_.tm_min;
- }
- break;
- case second: ///< second [0..59]
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 0;
- case absolute_maximum:
- case least_maximum:
- case actual_maximum:
- return 59;
- case current:
- return tm_.tm_sec;
- }
- break;
- case period::marks::first_day_of_week: ///< For example Sunday in US, Monday in France
- return first_day_of_week_ + 1;
-
- case week_of_year: ///< The week number in the year
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 1;
- case absolute_maximum:
- return 53;
- case least_maximum:
- return 52;
- case actual_maximum:
- {
- int year = tm_.tm_year + 1900;
- int end_of_year_days = (is_leap(year) ? 366 : 365) - 1;
- int dow_of_end_of_year = (end_of_year_days - tm_.tm_yday + tm_.tm_wday) % 7;
- return get_week_number(end_of_year_days,dow_of_end_of_year);
- }
- case current:
- {
- int val = get_week_number(tm_.tm_yday,tm_.tm_wday);
- if(val < 0)
- return 53;
- return val;
- }
- }
- case week_of_month: ///< The week number withing current month
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 1;
- case absolute_maximum:
- return 5;
- case least_maximum:
- return 4;
- case actual_maximum:
- {
- int end_of_month_days = days_in_month(tm_.tm_year + 1900,tm_.tm_mon + 1);
- int dow_of_end_of_month = (end_of_month_days - tm_.tm_mday + tm_.tm_wday) % 7;
- return get_week_number(end_of_month_days,dow_of_end_of_month);
- }
- case current:
- {
- int val = get_week_number(tm_.tm_mday,tm_.tm_wday);
- if(val < 0)
- return 5;
- return val;
- }
- }
-
- case day_of_week_in_month: ///< Original number of the day of the week in month.
- switch(v) {
- case absolute_minimum:
- case greatest_minimum:
- case actual_minimum:
- return 1;
- case absolute_maximum:
- return 5;
- case least_maximum:
- return 4;
- case actual_maximum:
- if(tm_.tm_mon == 1 && !is_leap(tm_.tm_year + 1900)) {
- // only in february in non leap year is 28 days, the rest
- // conver more then 4 weeks
- return 4;
- }
- return 5;
- case current:
- return (tm_.tm_mday - 1) / 7 + 1;
- default:
- ;
- }
- default:
- ;
- }
- return 0;
-
- }
-
- ///
- /// Set current time point
- ///
- virtual void set_time(posix_time const &p)
- {
- from_time(static_cast<time_t>(p.seconds));
- }
- virtual posix_time get_time() const
- {
- posix_time pt = { time_, 0};
- return pt;
- }
-
- ///
- /// Set option for calendar, for future use
- ///
- virtual void set_option(calendar_option_type opt,int /*v*/)
- {
- switch(opt) {
- case is_gregorian:
- throw date_time_error("is_gregorian is not settable options for calendar");
- case is_dst:
- throw date_time_error("is_dst is not settable options for calendar");
- default:
- ;
- }
- }
- ///
- /// Get option for calendar, currently only check if it is Gregorian calendar
- ///
- virtual int get_option(calendar_option_type opt) const
- {
- switch(opt) {
- case is_gregorian:
- return 1;
- case is_dst:
- return tm_.tm_isdst == 1;
- default:
- return 0;
- };
- }
-
- ///
- /// Adjust period's \a p value by \a difference items using a update_type \a u.
- /// Note: not all values are adjustable
- ///
- virtual void adjust_value(period::marks::period_mark p,update_type u,int difference)
- {
- switch(u) {
- case move:
- {
- using namespace period::marks;
- switch(p) {
- case year: ///< Year, it is calendar specific
- case extended_year: ///< Extended year for Gregorian/Julian calendars, where 1 BC == 0, 2 BC == -1.
- tm_updated_.tm_year +=difference;
- break;
- case month:
- tm_updated_.tm_mon +=difference;
- break;
- case day:
- case day_of_year:
- case day_of_week: ///< Day of week, starting from Sunday, [1..7]
- case day_of_week_local: ///< Local day of week, for example in France Monday is 1, in US Sunday is 1, [1..7]
- tm_updated_.tm_mday +=difference;
- break;
- case hour: ///< 24 clock hour [0..23]
- case hour_12: ///< 12 clock hour [0..11]
- tm_updated_.tm_hour += difference;
- break;
- case am_pm: ///< am or pm marker, [0..1]
- tm_updated_.tm_hour += 12 * difference;
- break;
- case minute: ///< minute [0..59]
- tm_updated_.tm_min += difference;
- break;
- case second:
- tm_updated_.tm_sec += difference;
- break;
- case week_of_year: ///< The week number in the year
- case week_of_month: ///< The week number withing current month
- case day_of_week_in_month: ///< Original number of the day of the week in month.
- tm_updated_.tm_mday +=difference * 7;
- break;
- default:
- ; // Not all values are adjustable
- }
- normalized_ = false;
- normalize();
- }
- break;
- case roll:
- { // roll
- int cur_min = get_value(p,actual_minimum);
- int cur_max = get_value(p,actual_maximum);
- int max_diff = cur_max - cur_min + 1;
- if(max_diff > 0) {
- int value = get_value(p,current);
- int addon = 0;
- if(difference < 0)
- addon = ((-difference/max_diff) + 1) * max_diff;
- value = (value - cur_min + difference + addon) % max_diff + cur_min;
- set_value(p,value);
- normalize();
- }
- }
- default:
- ;
- }
- }
-
- int get_diff(period::marks::period_mark p,int diff,gregorian_calendar const *other) const
- {
- if(diff == 0)
- return 0;
- std::auto_ptr<gregorian_calendar> self(clone());
- self->adjust_value(p,move,diff);
- if(diff > 0){
- if(self->time_ > other->time_)
- return diff - 1;
- else
- return diff;
- }
- else {
- if(self->time_ < other->time_)
- return diff + 1;
- else
- return diff;
- }
- }
-
- ///
- /// Calculate the difference between this calendar and \a other in \a p units
- ///
- virtual int difference(abstract_calendar const *other_cal,period::marks::period_mark p) const
- {
- std::auto_ptr<gregorian_calendar> keeper;
- gregorian_calendar const *other = dynamic_cast<gregorian_calendar const *>(other_cal);
- if(!other) {
- keeper.reset(clone());
- keeper->set_time(other_cal->get_time());
- other = keeper.get();
- }
-
- int factor = 1; // for weeks vs days handling
-
- using namespace period::marks;
- switch(p) {
- case era:
- return 0;
- case year:
- case extended_year:
- {
- int diff = other->tm_.tm_year - tm_.tm_year;
- return get_diff(period::marks::year,diff,other);
- }
- case month:
- {
- int diff = 12 * (other->tm_.tm_year - tm_.tm_year)
- + other->tm_.tm_mon - tm_.tm_mon;
- return get_diff(period::marks::month,diff,other);
- }
- case day_of_week_in_month:
- case week_of_month:
- case week_of_year:
- factor = 7;
- // fall
- case day:
- case day_of_year:
- case day_of_week:
- case day_of_week_local:
- {
- int diff = other->tm_.tm_yday - tm_.tm_yday;
- if(other->tm_.tm_year != tm_.tm_year) {
- diff += days_from_0(other->tm_.tm_year + 1900) -
- days_from_0(tm_.tm_year + 1900);
- }
- return get_diff(period::marks::day,diff,other) / factor;
- }
- case am_pm:
- return static_cast<int>( (other->time_ - time_) / (3600*12) );
- case hour:
- case hour_12:
- return static_cast<int>( (other->time_ - time_) / 3600 );
- case minute:
- return static_cast<int>( (other->time_ - time_) / 60 );
- case second:
- return static_cast<int>( other->time_ - time_ );
- default:
- return 0;
- };
- }
-
- ///
- /// Set time zone, empty - use system
- ///
- virtual void set_timezone(std::string const &tz)
- {
- if(tz.empty()) {
- is_local_ = true;
- tzoff_ = 0;
- }
- else {
- is_local_ = false;
- tzoff_ = parse_tz(tz);
- }
- from_time(time_);
- time_zone_name_ = tz;
- }
- virtual std::string get_timezone() const
- {
- return time_zone_name_;
- }
-
- virtual bool same(abstract_calendar const *other) const
- {
- gregorian_calendar const *gcal = dynamic_cast<gregorian_calendar const *>(other);
- if(!gcal)
- return false;
- return
- gcal->tzoff_ == tzoff_
- && gcal->is_local_ == is_local_
- && gcal->first_day_of_week_ == first_day_of_week_;
- }
-
- virtual ~gregorian_calendar()
- {
- }
-
- private:
-
- void from_time(time_t point)
- {
- time_t real_point = point + tzoff_;
- std::tm *t = 0;
- #ifdef BOOSTER_WIN_NATIVE
- // Windows uses TLS, thread safe
- t = is_local_ ? localtime(&real_point) : gmtime(&real_point);
- #else
- std::tm tmp_tm;
- t = is_local_ ? localtime_r(&real_point,&tmp_tm) : gmtime_r(&real_point,&tmp_tm);
- #endif
- if(!t) {
- throw date_time_error("boost::locale::gregorian_calendar: invalid time point");
- }
- tm_ = *t;
- tm_updated_ = *t;
- normalized_ = true;
- time_ = point;
- }
- int first_day_of_week_;
- time_t time_;
- std::tm tm_;
- std::tm tm_updated_;
- bool normalized_;
- bool is_local_;
- int tzoff_;
- std::string time_zone_name_;
-
- };
-
- abstract_calendar *create_gregorian_calendar(std::string const &terr)
- {
- return new gregorian_calendar(terr);
- }
-
- class gregorian_facet : public calendar_facet {
- public:
- gregorian_facet(std::string const &terr,size_t refs = 0) :
- calendar_facet(refs),
- terr_(terr)
- {
- }
- virtual abstract_calendar *create_calendar() const
- {
- return create_gregorian_calendar(terr_);
- }
- private:
- std::string terr_;
- };
-
- std::locale install_gregorian_calendar(std::locale const &in,std::string const &terr)
- {
- return std::locale(in,new gregorian_facet(terr));
- }
-
-
- } // util
- } // locale
- } //boost
-
-
- // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
|