如何解析由 std::put_time("%x") 在 Windows 上创建的字符串?
How to parse a string created by std::put_time("%x") on Windows?
起点
让我们假设 windows 上的第三方组件使用以下函数创建一个表示日期的字符串:
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();
}
根据您的系统语言环境和短日期格式的设置,您会得到类似 15.09.2020
或 09/15/2020
或 15. Sept. 2020
等的字符串
这是预期的,因为 %x
在 cppreference 上被描述为 writes localized date representation (locale dependent)
。
问题
如何将 std::put_time("%x")
生成的字符串解析回 std::tm
(假设相同的语言环境和短日期格式系统设置)?
什么不起作用
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;
}
不起作用,因为 std::get_time 的实现需要硬编码格式 "%d / %m / %y
for %x
in xlocime
.
提升
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
因为 %x
似乎根本没有实现解析。
strptime
windows 似乎不可用。有一个实现 here 但编译和集成似乎并不简单。
解决方法
到目前为止我想到的最好的解决方法是使用 Win32 函数 EnumDateFormats()
读取系统 DATE_SHORTDATE
格式并将此格式转换为 std::get_time()
语法,因为它不兼容(例如,对于 std::get_time()
,dd.MM.yyyy
需要转换为 %d.%m.%Y
)。但这似乎很容易出错,而不是“正确”的方法...
似乎std::put_time()
在内部使用strftime
而std::get_time()
是“自行实现的”。我原以为 std::put_time()
生成的所有内容都应该可以由 std::get_time()
使用相同的格式字符串进行解析。但这似乎并非如此,而且似乎也没有被记录在案。或者我错过了什么?
Plauger 有一个著名的 post,他不打算解析“米迦勒节后的第 14 天”,标准委员会不能让他这样做。你可以做的是 put_time
一个已知的日期,例如71/2/1,并尝试剖析结果以重新创建一个详细的模式,您稍后可以将其用于解析。
您可以准备所有可能格式的值,例如:
const char* formats[] = {
"%Y.%M.%d",
"%Y/%M/%d",
"%Y %b %d",
...
};
然后你可以尝试依次使用格式解析get_time的第一个日期,直到解析成功。
之后您可以保存格式索引并将其用于其他日期解析。
但基本上您在这里遗漏了一些信息。例如,只需查看:
2020/02/03
如果您不知道生成日期的语言环境或格式,您无法判断是 3 月 2 日还是 2 月 3 日。
为了克服这个问题,您可以尝试对从集合中随机选择的几个日期(或所有日期)使用上述格式,让每个解析都成功,这样您就可以更加确定所选择的格式是正确的一.
但这是一个蛮力解决方案。
对于 simplest/most 正确的解决方案,有必要对 put_time 生成的日期进行一些限制 - 您必须了解其格式。
这是我迄今为止想到的最好的:
#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;
}
即使我在我的 windows 区域设置中输入相当奇怪的自定义短日期格式(例如 DD.MM.YYYY, DDDD
,它会生成 17.09.2020, Thursday
.
等日期,这仍然有效
起点
让我们假设 windows 上的第三方组件使用以下函数创建一个表示日期的字符串:
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();
}
根据您的系统语言环境和短日期格式的设置,您会得到类似 15.09.2020
或 09/15/2020
或 15. Sept. 2020
等的字符串
这是预期的,因为 %x
在 cppreference 上被描述为 writes localized date representation (locale dependent)
。
问题
如何将 std::put_time("%x")
生成的字符串解析回 std::tm
(假设相同的语言环境和短日期格式系统设置)?
什么不起作用
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;
}
不起作用,因为 std::get_time 的实现需要硬编码格式 "%d / %m / %y
for %x
in xlocime
.
提升
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
因为 %x
似乎根本没有实现解析。
strptime
windows 似乎不可用。有一个实现 here 但编译和集成似乎并不简单。
解决方法
到目前为止我想到的最好的解决方法是使用 Win32 函数 EnumDateFormats()
读取系统 DATE_SHORTDATE
格式并将此格式转换为 std::get_time()
语法,因为它不兼容(例如,对于 std::get_time()
,dd.MM.yyyy
需要转换为 %d.%m.%Y
)。但这似乎很容易出错,而不是“正确”的方法...
似乎std::put_time()
在内部使用strftime
而std::get_time()
是“自行实现的”。我原以为 std::put_time()
生成的所有内容都应该可以由 std::get_time()
使用相同的格式字符串进行解析。但这似乎并非如此,而且似乎也没有被记录在案。或者我错过了什么?
Plauger 有一个著名的 post,他不打算解析“米迦勒节后的第 14 天”,标准委员会不能让他这样做。你可以做的是 put_time
一个已知的日期,例如71/2/1,并尝试剖析结果以重新创建一个详细的模式,您稍后可以将其用于解析。
您可以准备所有可能格式的值,例如:
const char* formats[] = {
"%Y.%M.%d",
"%Y/%M/%d",
"%Y %b %d",
...
};
然后你可以尝试依次使用格式解析get_time的第一个日期,直到解析成功。
之后您可以保存格式索引并将其用于其他日期解析。
但基本上您在这里遗漏了一些信息。例如,只需查看:
2020/02/03
如果您不知道生成日期的语言环境或格式,您无法判断是 3 月 2 日还是 2 月 3 日。
为了克服这个问题,您可以尝试对从集合中随机选择的几个日期(或所有日期)使用上述格式,让每个解析都成功,这样您就可以更加确定所选择的格式是正确的一.
但这是一个蛮力解决方案。
对于 simplest/most 正确的解决方案,有必要对 put_time 生成的日期进行一些限制 - 您必须了解其格式。
这是我迄今为止想到的最好的:
#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;
}
即使我在我的 windows 区域设置中输入相当奇怪的自定义短日期格式(例如 DD.MM.YYYY, DDDD
,它会生成 17.09.2020, Thursday
.