C++ - 如何以独立于平台、线程安全的方式以用户首选的日期/时间语言环境格式格式化文件的最后修改日期和时间
Posted
技术标签:
【中文标题】C++ - 如何以独立于平台、线程安全的方式以用户首选的日期/时间语言环境格式格式化文件的最后修改日期和时间【英文标题】:C++ - How to format a file's last modified date and time in the user's preferred date/time locale format in a platform-independent, thread-safe manner 【发布时间】:2021-11-07 08:38:33 【问题描述】:我想检索给定文件的最后修改日期和时间,并将其格式化为字符串。我想以线程和内存安全的方式这样做;跨平台兼容;并且不需要使用自定义库。日期和时间应根据操作系统返回的用户首选区域设置和日期/时间格式进行格式化。
使用 C++11/14 功能很好。使用 Boost 也是如此。执行速度并不特别重要。
这是我目前拥有的:
std::string text;
text += filename;
text = "Savegame: "+text+lf+"_________________"+lf;
text += "Saved on: ";
const boost::filesystem::path file_name_pathfilename;
const boost::filesystem::path save_dir_pathgetSaveDir();
const boost::filesystem::path& full_file_path = boost::filesystem::absolute(file_name_path, save_dir_path);
std::time_t last_saved_time;
std::stringstream last_saved_string_stream;
last_saved_string_stream.exceptions(std::ios_base::failbit);
try
std::locale users_preferred_localestd::locale("");
boost::gregorian::date_facet output_facet;
last_saved_string_stream.imbue(users_preferred_locale);
last_saved_string_stream.imbue(std::locale(last_saved_string_stream.getloc(), &output_facet));
output_facet.format("%x");
last_saved_time = boost::filesystem::last_write_time(full_file_path);
auto last_saved_time_pointboost::chrono::system_clock::from_time_t(last_saved_time);
last_saved_string_stream << last_saved_time_point;
catch (boost::filesystem::filesystem_error& fse)
VS_LOG_AND_FLUSH(fatal, (boost::format("Filesystem Error determining savegame's last saved date/time: %1%") % fse.what()));
catch (std::exception& e)
VS_LOG_AND_FLUSH(fatal, (boost::format("General Error determining savegame's last saved date/time: %1%") % e.what()));
catch (...)
VS_LOG_AND_FLUSH(fatal, "Really bad error determining savegame's last saved date/time! What just happened??");
text += last_saved_string_stream.str() + lf;
VS_LOG_AND_FLUSH 是一个宏(我知道,我知道),它使用指定的日志级别和消息调用 BOOST_LOG_TRIVIAL,然后刷新所有 boost 日志接收器,以及 stdout 和 stderr。
getSaveDir()
的实现对于这个问题并不重要。让我们接受它返回一个std::string
,其中包含存储该程序的存档游戏的目录。
即使走到这一步也比原本应该的要困难得多。我发现实际使用这些库的人的例子非常少——尤其是一起使用。
此代码可以编译,但在 Windows 上运行时会静默崩溃。同时,在 Linux 上,它会运行,但将文件的时间戳显示为自 1970 年 1 月 1 日以来的纳秒数。这不是正确的格式。例如,格式应该类似于美国的“9/10/2021 5:30 PM”或某些欧洲国家/地区的“10/09/2021 17:30:00”。
我做错了什么?而我应该怎么做?
我在output_facet.format(...
行上尝试了几种不同的格式字符串。我试过"%c"
和"%x"
,结果都一样。
【问题讨论】:
【参考方案1】:首先:事实证明,getSaveDir()
毕竟并没有可靠地返回预期值。现在已修复。
第二:我的catch
子句依赖于更多格式化的字符串输出,在某些字符串输出已经失败之后。不一定是个好主意。
第三:我的catch
子句实际上并没有退出程序。 VS_LOG_AND_FLUSH
不包含此功能。
第四:我在这个 .cpp 文件顶部包含的特定头文件集很重要。甚至顺序也可能很重要。这是我发现的工作:
#include <boost/filesystem.hpp>
#include <boost/chrono/time_point.hpp>
#include <boost/chrono/io/time_point_io.hpp>
#include <boost/chrono/chrono.hpp>
#include <iostream>
#include <sstream>
#include <chrono>
#include <locale>
最后:我要找的具体输出函数是boost::chrono::time_fmt
。
这是我现在拥有的代码:
string text;
text += filename;
text = "Savegame: "+text+lf+"_________________"+lf;
try
text += "Saved on: ";
const boost::filesystem::path file_name_pathfilename;
const boost::filesystem::path save_dir_pathgetSaveDir();
const boost::filesystem::path full_file_pathboost::filesystem::absolute(file_name_path, save_dir_path);
std::time_t last_saved_timeboost::filesystem::last_write_time(full_file_path);
boost::chrono::system_clock::time_point last_saved_time_pointboost::chrono::system_clock::from_time_t(last_saved_time);
std::ostringstream last_saved_string_stream;
last_saved_string_stream << boost::chrono::time_fmt(boost::chrono::timezone::local, "%c")
<< last_saved_time_point;
text += last_saved_string_stream.str() + lf;
catch (boost::filesystem::filesystem_error& fse)
VS_LOG_AND_FLUSH(fatal, "boost::filesystem::filesystem_error encountered");
VS_LOG_AND_FLUSH(fatal, fse.what());
VSExit(-6);
catch (std::exception& e)
VS_LOG_AND_FLUSH(fatal, "std::exception encountered");
VS_LOG_AND_FLUSH(fatal, e.what());
VSExit(-6);
catch (...)
VS_LOG_AND_FLUSH(fatal, "unknown exception type encountered!");
VSExit(-6);
这似乎在 Windows 和 Linux 上都能可靠地工作。 (代码尚未在 macOS 上测试,但至少可以在 Mac 上编译。)
【讨论】:
是的,您涵盖了我的一些代码审查评论,其中一些我在回答中提到了。不过请务必查看该答案,因为您错过了Undefined Behaviour 的最重要原因【参考方案2】:你已经接近了。一些注意事项:
facet 由它们的语言环境所有,您不应该在那里获取局部变量的地址:
boost::gregorian::date_facet output_facet;
last_saved_string_stream.imbue(std::locale(last_saved_string_stream.getloc(), &output_facet));
output_facet.format("%x");
应该是
last_saved_string_stream.imbue(std::locale(last_saved_string_stream.getloc(),
new boost::gregorian::date_facet("%x")));
事实上,多余的灌输可能会被合并:
std::stringstream last_saved_string_stream;
last_saved_string_stream.exceptions(std::ios_base::failbit);
std::locale users_preferred_localestd::locale("");
last_saved_string_stream.imbue(users_preferred_locale);
last_saved_string_stream.imbue(std::locale(last_saved_string_stream.getloc(), new boost::gregorian::date_facet("%x")));
可以变成
std::stringstream ss;
ss.exceptions(std::ios_base::failbit);
ss.imbue(std::locale(std::locale(""), new boost::gregorian::date_facet("%x")));
请注意,您可以减少 stringstream 和 time_t 变量的多余范围
转换为 chrono time_point 没有帮助。这些方面仅适用于 Boost DateTime 库类型,而不适用于 Chrono。而是转换为ptime
或local_date_time
:
ss << boost::posix_time::from_time_t(
fs::last_write_time(full_file_path));
为什么要将字符串连接、std iostreams 和 boost::format 结合在一个函数中?
根据我的测试,你或许可以不用灌输:
Live On Coliru
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <boost/chrono.hpp>
#include <boost/date_time.hpp>
namespace fs = boost::filesystem;
using namespace std::string_literals;
// mocks
enum severity fatal ;
template <typename Msg>
static void VS_LOG_AND_FLUSH(severity, Msg const& msg) std::clog << msg << std::endl;
fs::path getSaveDir() return fs::current_path();
static constexpr char const* lf = "\n";
std::string DescribeSaveGame(std::string filename, bool do_imbue)
std::stringstream ss;
ss.exceptions(std::ios_base::failbit);
if (do_imbue)
ss.imbue(std::locale(std::locale(""),
new boost::gregorian::date_facet("%x")));
ss << "Savegame: " << filename << lf << "_________________" << lf;
try
ss << "Saved on: ";
ss << boost::posix_time::from_time_t(
fs::last_write_time(fs::absolute(filename, getSaveDir())))
<< lf;
return ss.str();
catch (std::exception const& e)
VS_LOG_AND_FLUSH(fatal,
"General Error determining savegame's last saved date/time: "s +
e.what());
catch (...)
VS_LOG_AND_FLUSH(fatal,
"Really bad error determining savegame's last saved "
"date/time! What just happened??");
return "error"; // TODO?
int main()
std::cout << DescribeSaveGame("test.bin", true) << std::endl;
std::cout << DescribeSaveGame("test.bin", false) << std::endl;
打印
Savegame: test.bin
_________________
Saved on: 2021-Sep-11 14:29:09
Savegame: test.bin
_________________
Saved on: 2021-Sep-11 14:29:09
最简单
以上暗示您可能可以将整个事情简化为 lexical_cast:
std::string DescribeSaveGame(std::string const& filename)
auto save_date = boost::posix_time::from_time_t(
last_write_time(absolute(filename, getSaveDir())));
return "Savegame: " + filename + lf + "_________________" + lf +
"Saved on: " + boost::lexical_cast<std::string>(save_date) + lf;
Live On Coliru
仍然打印
Savegame: test.bin
_________________
Saved on: 2021-Sep-11 14:37:10
【讨论】:
旁注:如果您要依赖构面,还可以考虑使用boost::posix_time::time_facet
来控制不仅仅是日期部分。以上是关于C++ - 如何以独立于平台、线程安全的方式以用户首选的日期/时间语言环境格式格式化文件的最后修改日期和时间的主要内容,如果未能解决你的问题,请参考以下文章