C++的filesystem的时间与Qt的QDateTime时间为什么相差204年?
最近,在一个使用了Qt6的C++项目中,发现了一个奇怪的现象。
需求
简单来说就是,我们使用C++20中增加的filesystem库,取得了某个文件的最后更改时间,输入Qt的QDateTime,最后显示在Qt开发的界面上。
奇怪的一幕就这样发生了:这个当前的时间竟然是1820年。
演示
先写一个测试程序,叫做test_filetime.cc
#include <iostream>
#include <filesystem>
#include <chrono>
#include <QDateTime>
using namespace std;
int main(int argc, char *argv[])
{
// 取程序自身的最后更改时间点
auto file_path = argv[0];
auto last_write = filesystem::last_write_time (file_path);
// cout << "last write: " << last_write << endl;
//转化为一个epoch
auto epoch = last_write.time_since_epoch();
// cout << "epoch: " << epoch << endl;
auto epoch_count = epoch.count();
cout << "epoch_count: " << epoch_count << endl;
auto duration = chrono::duration_cast<chrono::seconds>(epoch);
//cout << "duration: " << duration << endl;
auto duration_count = duration.count();
cout << "duration_count: " << duration_count << endl;
// 输入QDateTime,之后打印输出
auto datetime = QDateTime::fromSecsSinceEpoch (duration_count, Qt::UTC);
//cout << "QDateTime datetime: " << datetime;
auto datetimestr = datetime.toString("yyyy-MM-dd HH:mm:ss");
cout << "QDateTime datetime toString: " << datetimestr.toStdString() << endl;
return 0;
}
一运行,我们得到了如下输出:
./test_filetime
epoch_count: -4707228206068994024
duration_count: -4707228206
QDateTime datetime toString: 1820-11-01 04:36:34
我们的程序刚刚编译完,最后的更改时间肯定会是2024年。2024 - 1820,整整相差了204年。
怎么回事儿呢?
应该不是Windows与Linux的差异
我们上网搜索,在这篇CSDN文章 中,得到以下信息:
windows FILETIME时间从1601/01/01 零时零分零秒开始计时,windows每个时钟滴答将计数加一,每个时钟滴答的间隔是100 nanoseconds(纳秒,1秒=10的九次方纳秒),即每个时钟滴答= 10 (-7)秒。
unix、linux时间是从1970/01/01零时零分零秒开始计数,每秒计数加1. 197001/01与1601/01/01的时间间隔是11644473600秒。
但是,如果是这种原因的话,时间相差的年数应该是1970 - 1601,等于169年,不会是204年啊!
而且,我们是在同一台Linux机器上,取得一个文件的最后更改时间,涉及不到Windows系统与Linux系统之间的差异问题。
C++中的chrono
根据《C++并发编程实战(第2版)》中介绍:
C++中的每种时钟,都是一个类。而每个时钟类,都提供如下信息:
- 当前时刻;
- 时间值的类型(从该时钟取得的时间以它为表示形式);
- 该时钟的计时单元的长度(tick period);
- 计时速率是否恒定,即能否将该时钟视为恒稳时钟(steady clock)。
如果要取得当前时刻,调用时钟类的静态成员函数now()即可。
而这个时刻,对于C++的时钟类来说,是一个叫做time_point的成员类型。
而上文提到的文件最后修改时间,返回的也是一个time_point的成员类型。
所以,我们可以先测试一下,使用std::chrono::system_clock::now()得到的时间,进入QDateTime之后,时间是否一致。
原来的测试程序加入以下代码:
auto now = chrono::system_clock::now();
epoch = now.time_since_epoch();
duration = chrono::duration_cast<chrono::seconds>(epoch);
duration_count = duration.count();
datetime = QDateTime::fromSecsSinceEpoch (duration_count, Qt::UTC);
datetimestr = datetime.toString("yyyy-MM-dd HH:mm:ss");
cout << "QDateTime now toString: " << datetimestr.toStdString() << endl;
我们得到了如下输出:
QDateTime now toString: 2024-11-01 05:08:20
年份完全没有问题。
看来,不是QDateTime这块儿的事情,而是filesystem库的time_point和std::chrono::system_clock的time_point就不一样,是它们的值相差了204年。
std::format是对的
我们查一下filesystem的文档。
在last_write_time的页面里,并没有提到这个时间差异的问题,但是提到了一块儿样例代码,是使用了std::format函数来打印这个time_point。
我们把测试程序里,也加上使用std::format来打印一下看看。
cout << "format datetime: " << std::format("{}", last_write) << endl;
得到了如下输出:
./test_file_time
epoch_count: -4707225704322468259
duration_count: -4707225704
QDateTime datetime toString: 1820-11-01 05:18:16
format datetime: 2024-11-01 05:18:15.677531741
QDateTime now toString: 2024-11-01 05:18:19
使用std::format打印也是对的。
也就是说,我们使用last_write_time,返回的这个time_point,输入QDateTime的方式有问题,而std::format解析出来的才是对的。
源码
我们只有读一下STL里的源码了。
last_write_time在fs_fwd.h中。
原型为:
file_time_type last_write_time(const path&);
file_time_type last_write_time(const path&, error_code&) noexcept;
而file_time_type的定义为:
using file_time_type = __file_clock::time_point;
也就是说,filesystem库自己实现了一个__file_clock,这个file_time_type是这个clock的成员time_point。
而__file_clock这个类,在chrono.h中。
这个类的private:部分,赫然有一个成员,并且有一块儿醒目的注释:
private:
using __sys_clock = chrono::system_clock;
// This clock's (unspecified) epoch is 2174-01-01 00:00:00 UTC.
// A signed 64-bit duration with nanosecond resolution gives roughly
// +/- 292 years, which covers the 1901-2446 date range for ext4.
static constexpr chrono::seconds _S_epoch_diff{6437664000};
看到没有?也就是说,__file_clock内部的时间偏移是2174年的0点,而 2174 - 1970,等于204。
所以,我们顺藤摸瓜,也找到了解决之道。
这个时钟类的公有方法部分,提供了两个转换方法from_sys与to_sys:
template<typename _Dur>
static
chrono::file_time<common_type_t<_Dur, chrono::seconds>>
from_sys(const chrono::sys_time<_Dur>& __t) noexcept
{ return _S_from_sys(__t); }
// For internal use only
template<typename _Dur>
static
chrono::sys_time<common_type_t<_Dur, chrono::seconds>>
to_sys(const chrono::file_time<_Dur>& __t) noexcept
{ return _S_to_sys(__t); }
而_S_from_sys与_S_to_sys也在这个类里定义了:
template<typename _dur>
static
chrono::time_point<__file_clock, common_type_t<_dur, chrono::seconds>>
_s_from_sys(const chrono::time_point<__sys_clock, _dur>& __t) noexcept
{
using _cdur = common_type_t<_dur, chrono::seconds>;
using __file_time = chrono::time_point<__file_clock, _cdur>;
return __file_time{__t.time_since_epoch()} - _s_epoch_diff;
}
// for internal use only
template<typename _dur>
static
chrono::time_point<__sys_clock, common_type_t<_dur, chrono::seconds>>
_s_to_sys(const chrono::time_point<__file_clock, _dur>& __t) noexcept
{
using _cdur = common_type_t<_dur, chrono::seconds>;
using __sys_time = chrono::time_point<__sys_clock, _cdur>;
return __sys_time{__t.time_since_epoch()} + _s_epoch_diff;
}
其实就是把偏移量加上。
解决
我们在测试程序中,加入这个转换:
auto sys = filesystem::__file_clock::to_sys(last_write);
epoch = sys.time_since_epoch();
duration = chrono::duration_cast<chrono::seconds>(epoch);
duration_count = duration.count();
datetime = QDateTime::fromSecsSinceEpoch (duration_count, Qt::UTC);
datetimestr = datetime.toString("yyyy-MM-dd HH:mm:ss");
cout << "QDateTime sys toString: " << datetimestr.toStdString() << endl;
最后得到了正确的输出:
test_filetime
epoch_count: -4707223542449681988
duration_count: -4707223542
QDateTime datetime toString: 1820-11-01 05:54:18
QDateTime now toString: 2024-11-01 05:54:22
QDateTime sys toString: 2024-11-01 05:54:17
C++17
但是,还有一个问题,就是__file_clock的to_sys方法,是C++20加入的。如果我们使用的是C++17,还需要自己完成这个转换。
#if __cplusplus > 201703L // C++20
auto sys = filesystem::__file_clock::to_sys(last_write);
epoch = sys.time_since_epoch();
#else
epoch = last_write.time_since_epoch() + chrono::seconds{6437664000} ;
#endif
duration = chrono::duration_cast<chrono::seconds>(epoch);
duration_count = duration.count();
datetime = QDateTime::fromSecsSinceEpoch (duration_count, Qt::UTC);
datetimestr = datetime.toString("yyyy-MM-dd HH:mm:ss");
cout << "QDateTime sys toString: " << datetimestr.toStdString() << endl;