views:

252

answers:

3

Consider this somewhat of a follow up to this question. Essentially, the C++ date/time formatting facilities seem to be hopelessly broken - so much so that in order to do something as simple as convert a date/time string into an object, you really have to resort to either Boost.Datetime or good old C strftime/strptime facilities.

The problem is that neither of these solutions work directly with the C++ locale settings imbued onto a particular iostream object. The C facilities use the global C/POSIX locale settings, whereas the I/O facilities in Boost.Datetime seem to bypass iostream locale settings entirely by allowing the user to directly set the names of months, weekdays, etc., regardless of locale.

So, I wanted something that would respect the locale settings imbued onto a particular I/O stream that would allow me to convert a string into a struct tm. It seemed easy enough, but I ran into obstacles around every corner. At first, I noticed that some implementations of the STL provide a non-standard std::time_get::get function, so I decided to implement something similar. Basically, I would simply iterate over the format string and whenever I hit a format flag, I would use one of the time_get facilities (like get_monthname, get_weekday, get_year, etc.) to convert the input string into a struct tm. This seems easy enough, except each one of these functions requires an exact iterator range. You can't convert "Monday,", it has to be "Monday" exactly, or the conversion fails. Since the iterators have to be istreambuf_iterator, you can't simply scan ahead, because each increment changes the get position in the stream buffer. So, basically you have to first iterate over the stream, copying each character into another streambuffer, and then when you hit a delimiter (like a space or a comma), use the second streambuffer with the time_get facilities. It's literally as if the C++ designers went out of their way to make this as annoying as possible.

So, is there an easier solution? What do most C++ programmers do when they need to convert a date/time string to an object? Do we just have to use the C facilities, and lose the advantages that come along with different locale settings imbued on different iostream objects?

A: 

Why not use the C library? It's almost certainly available in your implementation, and it's well debugged and tested.

If it's lacking some feature, surely it's easier to make wrapper function which does the timezone work you desire.

wallyk
If the program is designed around C++ locales, I suppose it might be inconvenient to translate back to C locales… although I don't see how it could be more than getting the name from locale::name() and passing to setlocale()/newlocale()… that might be rather slow, though.
Potatoswatter
It depends on how he's using them. Calling `std::locale::global()` will also set the C locale if the given locale object has a name. If he's imbuing individual streams with different locales, he'll have to manually set and unset the C locale each time he uses one of them (or at least, each time he uses one for date processing). It'd be easy to write a sentinel class to help with that, though.
Steve M
A: 

I always try to use locale independent strings to serialize data. Makes life a lot easier.

Dialecticus
+1  A: 

Boost uses the standard locale(s) by default; you don't have to bypass anything:

#include "boost/date_time/gregorian/gregorian.hpp"
#include <iostream>
#include <sstream>
#include <ctime>

int main(){
  using namespace boost::gregorian;

  std::locale::global(std::locale(""));
  std::locale german("German_Germany");
  std::locale french("French_France");

  date d1(day_clock::local_day());
  date d2;
  std::stringstream ss("2002-May-01");

  std::cout << "Mine: " << d1 << " | ";
  ss >> d2;
  std::cout << d2 << '\n';

  std::cout.imbue(german);
  std::cout << "Germany: " << d1 << " | ";
  ss.imbue(german);
  ss << "2002-Mai-01";
  ss >> d2;
  std::cout << d2 << '\n';

  std::cout.imbue(french);
  std::cout << "France: " << d1 << " | " << d2 << '\n';

  std::tm t = to_tm(d1);
  std::cout << "tm: " << asctime(&t);
}

(Those locale names are specific to Windows, of course.) Output:

Mine: 2010-Oct-28 | 2002-May-01
Germany: 2010-Okt-28 | 2002-Mai-01
France: 2010-oct.-28 | 2002-mai-01
tm: Thu Oct 28 00:00:00 2010
Steve M