Example of date_time_period boost::locale::period:

2019-07-17 16:20发布

问题:

In boost::locale document it says:

date_time_period boost::locale::period::first_day_of_week(int v) [inline]

Get date_time_period for: First day of week, constant, for example Sunday in US = 1, Monday in France = 2

Those functions which return date_time_period can be used to create custom boost::locale::date_time objects.

I tried to create one with std::locale set to "en_US.UTF-8", so the first day of week is default to Sunday, and then modified it to Monday. Here's the code:

#include <iostream>
#include <boost/locale.hpp>

int main() {
  using namespace boost::locale;
  date_time_period_set s;
  generator gen;
  std::locale locale = gen("en_US.UTF-8");
  std::locale::global(locale);
  std::cout.imbue(locale);
  s.add(period::year(2000));
  s.add(period::month(6));
  s.add(period::day(5));
  s.add(period::hour(9));
  s.add(period::minute(0));
  s.add(period::second(0));
  s.add(period::first_day_of_week(2));// set the first day of week to Monday
  date_time now(s); // should be 2000-07-05 09:00:00, week starts from Monday
  std::cout << now << std::endl;
}

However, running the program results to an error:

terminate called after throwing an instance of 'std::invalid_argument'
what():  Invalid date_time period type

The error comes from its ICU backend adapter:

  static UCalendarDateFields to_icu(period::marks::period_mark f)
  {
      using namespace period::marks;

      switch(f) {
      case era: return UCAL_ERA;
      case year: return UCAL_YEAR;
      case extended_year: return UCAL_EXTENDED_YEAR;
      case month: return UCAL_MONTH;
      case day: return UCAL_DATE;
      case day_of_year: return UCAL_DAY_OF_YEAR;
      case day_of_week: return UCAL_DAY_OF_WEEK;
      case day_of_week_in_month:  return UCAL_DAY_OF_WEEK_IN_MONTH;
      case day_of_week_local: return UCAL_DOW_LOCAL;
      case hour: return UCAL_HOUR_OF_DAY;
      case hour_12: return UCAL_HOUR;
      case am_pm: return UCAL_AM_PM;
      case minute: return UCAL_MINUTE;
      case second: return UCAL_SECOND;
      case week_of_year: return UCAL_WEEK_OF_YEAR;
      case week_of_month: return UCAL_WEEK_OF_MONTH;
      default:
          throw std::invalid_argument("Invalid date_time period type");
      }
  }

first_day_of_week is not acceptable.

So the questions are:

  1. Can a boost::locale::date_time object be created with modified "the first day of week"? If true, how to do it?
  2. How (or where) to use date_time_period boost::locale::period::first_day_of_week(int v)?

回答1:

This seems confusing indeed.

Preliminaries:

std::locale english = gen("en_US.UTF-8");
std::locale french  = gen("fr_FR.UTF-8");
std::cout.imbue(english); // this one doesn't matter
std::locale::global(french);

After some (extensive) tests, I figured out that the value for first_day_of_week on a date_time instance is:

  • set from the global locale at the time of the construction

    std::locale::global(english);
    assert(1 == period::first_day_of_week(date_time()));
    
    std::locale::global(french);
    assert(2 == period::first_day_of_week(date_time()));
    
  • or: set from the locale that is passed during construction

    assert(1 == period::first_day_of_week(date_time(english)));
    assert(2 == period::first_day_of_week(date_time(french)));
    
  • or: set from the date_time object that is passed during copy construction (ignoring locale)

    date_time edt(english), fdt(french);
    assert(1 == period::first_day_of_week(edt));
    edt = date_time(french);
    assert(2 == period::first_day_of_week(edt));
    fdt = date_time(english);
    assert(1 == period::first_day_of_week(fdt));
    
  • and most importantly: cannot be set from another source than the locale

    {
        date_time_period_set dtps;
        dtps.add(period::friday());
        dtps.add(period::week_of_year(4));
        // no effect
        dtps.add(period::first_day_of_week(1));
    
        assert(2 == period::first_day_of_week(date_time(dtps)));
    }
    
    {
        date_time dt;
        // no effect:
        dt.set(period::first_day_of_week(), 1);
        assert(2 == period::first_day_of_week(dt));
    }
    

Whether or not this is a bug, I leave up to the devs. You can maybe ask it on the boost mailing list. It sure is surprising, as the presence of a set interface suggests that this property can be changed.

In fact it appears that the first weekday property is not a property of a date_time in the first place, but rather a property of the locale, and it appears that the locale "imbued" on a date_time instance can not be changed except at construction/assignment.

Full tests: Live On Coliru

#include <boost/locale.hpp>
#include <iostream>

int main()
{
    using namespace boost::locale;
    generator gen;

    std::locale english = gen("en_US.UTF-8");
    std::locale french  = gen("fr_FR.UTF-8");
    std::cout.imbue(english); // this one doesn't matter
    std::locale::global(french);

    {
        std::locale::global(english);
        assert(1 == period::first_day_of_week(date_time()));

        std::locale::global(french);
        assert(2 == period::first_day_of_week(date_time()));
    }

    {
        assert(1 == period::first_day_of_week(date_time(english)));
        assert(2 == period::first_day_of_week(date_time(french)));
    }

    {
        date_time_period_set dtps;
        dtps.add(period::friday());
        dtps.add(period::week_of_year(4));
        // no effect
        dtps.add(period::first_day_of_week(1));

        assert(2 == period::first_day_of_week(date_time(dtps)));
    }

    {
        date_time dt;
        // no effect:
        dt.set(period::first_day_of_week(), 1);
        assert(2 == period::first_day_of_week(dt));
    }

    {
        // associated locale gets copied:
        date_time edt(english), fdt(french);
        assert(1 == period::first_day_of_week(edt));
        edt = date_time(french);
        assert(2 == period::first_day_of_week(edt));
        fdt = date_time(english);
        assert(1 == period::first_day_of_week(fdt));
    }

    std::cout << "All tests passed\n";
}