Let's assume a third party component on windows creates a string representing a date using the following function:
std::string getDate() {
std::tm t = {};
std::time_t now = std::time(nullptr);
localtime_s(&t, &now);
std::stringstream s;
s.imbue(std::locale(""));
s << std::put_time(&t, "%x");
return s.str();
}
Depending on your system locale and settings for the short date format you get strings like 15.09.2020
or 09/15/2020
or 15. Sept. 2020
etc.
This is expected as %x
is described as writes localized date representation (locale dependent)
on cppreference.
How to parse a string generated by std::put_time("%x")
back to a std::tm
(assuming identical locale and short date format system settings)?
STL
std::tm parseDate1(const std::string& date) {
std::tm t = {};
std::istringstream ss(date);
ss.exceptions(std::ifstream::failbit);
ss.imbue(std::locale(""));
ss >> std::get_time(&t, "%x");
return t;
}
Doesn't work because the implementation of std::get_time expects hard-coded format "%d / %m / %y
for %x
in xlocime
.
Boost
std::tm parseDate2(const std::string& date) {
boost::gregorian::date d;
auto* input_facet = new boost::gregorian::date_input_facet();
input_facet->format("%x");
std::istringstream ss(date);
ss.exceptions(std::ifstream::failbit);
ss.imbue(std::locale(std::locale(""), input_facet));
ss >> d;
return boost::gregorian::to_tm(d);
}
Boost always returns 1400-Jan-01
because %x
doesn't seem to be implemented for parsing at all.
strptime
Doesn't seem to be available on windows. There exists an implementation here but it doesn't seem to be straightforward to compile and integrate.
The best workaround I have came up with so far is using Win32 function EnumDateFormats()
to read the system DATE_SHORTDATE
format and convert this format to std::get_time()
syntax because it is not compatible (e.g. dd.MM.yyyy
needs to be converted to %d.%m.%Y
for std::get_time()
). But this seems to be error prone and not the "right" way to do it...
It also seems that std::put_time()
uses strftime
internally and std::get_time()
is "self-implemented". I would have expected that everything produced by std::put_time()
should be parsable by std::get_time()
using the same format string. But this doesn't seem to be the case and it also doesn't seem to be documented somehwere. Or am I missing something?
Here is the best I came up with so far:
#include <iostream>
#include <sstream>
#include <locale>
#include <iomanip>
#include <windows.h>
#include <boost/algorithm/string.hpp>
static std::string g_shortDateFormat;
BOOL CALLBACK EnumDateFormatsProc(_In_ LPSTR formatString) {
if (g_shortDateFormat.empty())
g_shortDateFormat = formatString;
return TRUE;
}
std::string getShortDatePattern() {
if (g_shortDateFormat.empty()) {
EnumDateFormatsA(EnumDateFormatsProc, LOCALE_USER_DEFAULT, DATE_SHORTDATE);
boost::algorithm::replace_all(g_shortDateFormat, "yyyy", "%Y");
boost::algorithm::replace_all(g_shortDateFormat, "yy", "%y");
boost::algorithm::replace_all(g_shortDateFormat, "MMMM", "%b");
boost::algorithm::replace_all(g_shortDateFormat, "MMM", "%b");
boost::algorithm::replace_all(g_shortDateFormat, "MM", "%m");
boost::algorithm::replace_all(g_shortDateFormat, "M", "%m");
boost::algorithm::replace_all(g_shortDateFormat, "dddd", "%a");
boost::algorithm::replace_all(g_shortDateFormat, "ddd", "%a");
boost::algorithm::replace_all(g_shortDateFormat, "dd", "d"); // intended to avoid %%d
boost::algorithm::replace_all(g_shortDateFormat, "d", "%d");
}
return g_shortDateFormat;
}
std::string getLocalDate(const std::tm& t) {
std::stringstream s;
s.imbue(std::locale(""));
s << std::put_time(&t, "%x");
return s.str();
}
std::tm parseLocalDate(const std::string& localDate) {
auto format = getShortDatePattern();
std::istringstream is(localDate);
is.imbue(std::locale(""));
is.exceptions(std::istream::failbit);
std::tm t = {};
is >> std::get_time(&t, format.c_str());
return t;
}
std::tm now() {
auto now = std::time(nullptr);
std::tm t = {};
localtime_s(&t, &now);
return t;
}
int main() {
auto t = now();
auto localDate = getLocalDate(t);
auto parsedDate = parseLocalDate(localDate);
std::cout << localDate << " - " << getLocalDate(parsedDate) << std::endl;
return 0;
}
This works even if I enter rather strange custom short dateformats like DD.MM.YYYY, DDDD
in my windows regional settings which generates dates like 17.09.2020, Thursday
.