前段时间在项目里碰到一个挺常见、但又特别容易写烂的需求:
统计时间。
听起来很简单,但一旦加上这几个条件,事情立刻变复杂:
- 有一个总时间
- 总时间由多个分段组成
- 每个分段过程中可以暂停、恢复,而且可能不止一次
- 暂停期间不能算有效时间
如果你随手用定时器或者不断累加,很快就会发现:
时间开始不对劲了。
要么多算了暂停时间,要么暂停几次之后就彻底乱套。
后来我索性停下来,重新想了一下:
时间这东西,真的有必要一直“加”吗?
换个思路:能算出来的,就别累加
最后我用的方案其实很简单:
- 所有时间都用时间戳
- 只在关键节点结算
- 其余时候全部“现算”
于是就有了下面这个TimeManager。
先上代码(核心就这些)
先写一个获取当前秒时间戳的小工具,用的是 C++ 自带的std::chrono:
#include<chrono>staticuint64_tgetNowSeconds(){returnstd::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();}然后是整个时间管理类:
classTimeManager{public:TimeManager():totalTime(0),segmentStartTime(0),segmentPauseTotal(0),totalPauseTotal(0),pauseStartTime(0),isPaused(false){}voidstartSegment(){segmentStartTime=getNowSeconds();segmentPauseTotal=0;isPaused=false;pauseStartTime=0;}voidpause(){if(isPaused||segmentStartTime==0)return;isPaused=true;pauseStartTime=getNowSeconds();}voidresume(){if(!isPaused||pauseStartTime==0)return;uint64_tnow=getNowSeconds();uint64_tpauseDuration=now-pauseStartTime;segmentPauseTotal+=pauseDuration;totalPauseTotal+=pauseDuration;isPaused=false;pauseStartTime=0;}voidstopSegment(){if(segmentStartTime==0)return;totalTime+=getSegmentTime();segmentStartTime=0;segmentPauseTotal=0;isPaused=false;pauseStartTime=0;}uint64_tgetSegmentTime()const{if(segmentStartTime==0)return0;uint64_tendTime=isPaused?pauseStartTime:getNowSeconds();returnendTime-segmentStartTime-segmentPauseTotal;}uint64_tgetTotalTime()const{returntotalTime+getSegmentTime();}private:uint64_ttotalTime;uint64_tsegmentStartTime;uint64_tsegmentPauseTotal;uint64_ttotalPauseTotal;uint64_tpauseStartTime;boolisPaused;};代码不长,但逻辑我挺满意的。
这个类到底在干什么?
一句话概括就是:
总时间 = 已结束分段的时间 + 当前分段实时算出来的时间
这里有几个我刻意分开的概念。
1️⃣ 总时间是“已经记账的时间”
uint64_ttotalTime;这个值只在一个地方改:stopSegment()
也就是说,只有当我明确“这个分段结束了”,
它的有效时间才会被加到总时间里。
中途暂停、恢复、怎么折腾,都不碰它。
2️⃣ 分段时间永远不累加,全部现算
分段有效时间的公式其实很直白:
当前时间 - 分段开始时间 - 分段暂停时间对应的代码就是:
uint64_tendTime=isPaused?pauseStartTime:getNowSeconds();returnendTime-segmentStartTime-segmentPauseTotal;有一个细节我当时特意处理了:
如果正在暂停,时间直接冻结。
所以暂停多久,分段时间就真的一秒都不涨。
3️⃣ 暂停时间只在恢复那一刻结算
我见过不少代码,是在 pause 的时候就开始各种计算,
最后状态一多就完全兜不住。
我这里的逻辑很简单:
- pause:只记一个时间点
- resume:统一算这次暂停了多久
uint64_tpauseDuration=now-pauseStartTime;segmentPauseTotal+=pauseDuration;这样不管你暂停多少次,都不会乱。
实际用起来是什么感觉?
比如这样一段流程:
TimeManager tm;tm.startSegment();// 干活// 10 秒tm.pause();// 停一下// 5 秒tm.resume();// 接着干// 8 秒tm.stopSegment();// 收工最后得到的结果是:
- 有效时间:18 秒
- 暂停时间:5 秒
- 总时间只加了 18 秒
暂停没有“偷”走任何有效时间。
写完之后的一个感受
时间统计这东西,最怕的不是代码写得少,而是状态太多。
后来我发现,只要记住一句话,问题就好解决很多:
能用时间戳算出来的,就别用变量去维护。
这个TimeManager本质上不是计时器,
而是一个时间状态管理器。
如果你做的是:
- 任务耗时统计
- 工单 / 流程时间分析
- 设备运行时间管理
这种思路基本都能直接套。
如果你对那些功能更感兴趣,也可以留言告诉我。