blob: 580efac0f7ff7264360e295ac99d79724d175dd1 [file] [log] [blame]
//
// 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 BOOST_LOCALE_SOURCE
#include <boost/config.hpp>
#ifdef BOOST_MSVC
# pragma warning(disable : 4996)
#endif
#include <locale>
#include <string>
#include <ios>
#include <boost/locale/date_time_facet.hpp>
#include <boost/locale/date_time.hpp>
#include <stdlib.h>
#include <ctime>
#include <memory>
#include <algorithm>
#include <limits>
#include "timezone.hpp"
#include "gregorian.hpp"
namespace boost {
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;
}
std::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;
std::time_t seconds_in_day = 3600 * 24;
std::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_ = std::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
std::time_t point = -1;
if(is_local_) {
point = std::mktime(&val);
if(point == static_cast<std::time_t>(-1)){
#ifndef BOOST_WINDOWS
// 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 BOOST_WINDOWS
// 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 BOOST_WINDOWS
return 1970; // Unix epoch windows can't handle negative time_t
#else
if(sizeof(std::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(std::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<std::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(std::time_t point)
{
std::time_t real_point = point + tzoff_;
std::tm *t = 0;
#ifdef BOOST_WINDOWS
// 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_;
std::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