当前位置: 首页 > article >正文

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;


http://www.kler.cn/a/375205.html

相关文章:

  • Redis高频面试题
  • 【OpenGL】知识点
  • centos7配置keepalive+lvs
  • 电阻电容电感为什么通常是10、22、47这些数
  • redis详细教程(3.hash和set类型)
  • c语言水仙花,超简单讲解
  • git入门教程15:git扩展
  • Vue全栈开发旅游网项目(3)-Vue路由配置
  • 基于SpringBoot和PostGIS的世界各国邻国可视化实践
  • 一文了解 | 软件测试类型与用途
  • 前端请求后端接口报错(blocked:mixed-content),以及解决办法
  • Element Plus在Vue3的安装
  • 使用web.dev提供的工具实现浏览器消息推送服务
  • Python 的 Pygame 库来开发一个游戏
  • 关于使用雷池社区版需要知道,什么是 IPv4 地址?
  • Linux Kernel Programming (个人读书笔记)
  • WSGI、uwsgi、uWSGI与Nginx
  • 练习LabVIEW第三十二题
  • 在Python中实现一个简单的社交媒体应用
  • Spring Boot 与 EasyExcel 携手:复杂 Excel 表格高效导入导出实战
  • 基于 SM3 的密钥派生函数 (KDF):国密合规的安全密钥生成方案
  • 低代码用户中心:简化开发,提升效率的新时代
  • es(1)(仅供自己参考)
  • 前端安全:构建坚不可摧的Web应用防线
  • redis的set如何实现的
  • 【WPF】BackgroundWorker类