muduo库-时间戳类Timestamp
如何度量程序在某一时刻的时间?
通常,我们用时刻来表示,比如"2023-06-26
23:43:00.000000",这种方式便于人查看,但不便于程序中的比较和计算。比如有2个时刻A和B,计算哪个时刻在前,哪个在后,或者要计算时刻A和B的时间差时,这种字符串表示方式就很麻烦。
我们想到将字符串形式的时刻,用自纪元时间(Epoch时间,1970-01-01
00:00:00 +0000 (UTC))以来的时间戳来表示,精度为1us(微秒)。
Linux中,如何获取这个时间呢?
使用gettimeofday
,分辨率1us,其实现也能达到毫秒级(当然分辨率不等于精度),再加上Linux是非实时任务系统,也能满足日常计时功能。
前面讲过,time
只能精确到1s,ftime
已被废弃,clock_gettime
精度高,但系统调用开销比gettimeofday
大,网络编程中,最适合用gettimeofday
来计时。muduo中也是这么做的。
有没有一种可能,两个线程,或者两段出现在1us内执行?答案是有可能的,对于常规情况,即使时间戳相同,并不影响我们的日常计时功能;对于特殊需求,比如排序、查找,需要区分时间戳大小的,后面遇到具体情况具体分析。
Timestamp类
由于时间戳希望在不同变量之间赋值、拷贝,因此设计成值语义的,继承自copyable
class。
数据成员:
成员变量microSecondsSinceEpoch_
,用来来表示从
Epoch时间到目前为止的微妙数,初值0(也表示无效值)。
microSecondsSinceEpoch_
的数据类型为什么是int64_t
,而不是int32_t
或者uint64_t
?因为32位连一年的微妙数都不能表示,而int64_t
可以表示290余年的微妙数(一年按\(365243600 *
100000\) 计算),未来还能表示一百余年,也就是说,其范围满足目前日常需求。而有符号的int64_t
可以用来让2个时间戳进行差值计算,从而表示先后顺序。当然,时间戳本身为负数没有意义。
构造函数
可以像这样定义Timstamp及其构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Timestamp : public copyable {public : Timestamp () : microSecondsSinceEpoch_ (0 ) { } explicit Timestamp (int64_t microSecondsSinceEpochArg) : microSecondsSinceEpoch_(microSecondsSinceEpochArg) { } ...private : int64_t microSecondsSinceEpoch_; };
继承自copyable,表明这是一个值语义的class,其对象能够进行copy操作;
default
ctor(构造函数),存储时间戳变量microSecondsSinceEpoch_
初值0,0和负数都表示无效值。同时,也提供单一参数版本ctor
,给调用者构造指定时间戳值的Timestamp对象的机会。
对象有效性
至于microSecondsSinceEpoch_
符号,我们可以定义成员函数valid()判断其有效性,通过invalid()构造一个无效的Timestamp对象。
1 2 3 4 5 6 7 8 9 10 bool valid () const { return microSecondsSinceEpoch_ > 0 ; }static Timestamp invalid () { return Timestamp (); }
用gettimeofday()
获取当前时刻,转化为微秒,并构造一个Timestamp
临时对象。1换算公式:sec
= 1e6 usec
时间换算
如何将由time()
获得的自Epoch时间(1970-01-01 00:00:00
+0000 (UTC).)以来的秒数(time_t类型),转化为Timestamp类型对象?
可以定义fromUnixTime
来完成这个工作:
1 2 3 4 5 6 7 8 9 static Timestamp fromUnixTime (time_t t) { return fromUnixTime (t, 0 ); } static Timestamp fromUnixTime (time_t t, int microseconds) { return Timestamp (static_cast <int64_t >(t) * kMicroSecondsPerSecond + microseconds); }
第一个重载版本,只转换提供的秒数,微秒数默认0;第二个版本,提供了秒数和微秒数的设置
对象交换
有时为了避免对象数据成员的拷贝,会利用swap对对象进行交换操作。
1 2 3 4 5 void swap (Timestamp& that) { std::swap (microSecondsSinceEpoch_, that.microSecondsSinceEpoch_); }
就目前的设计来说,完全可以用std::swap来交换2个对象,而不用定义Timestamp::swap()。这里是为了以后方便扩展,自定义swap行为。
获取时间戳
获取从Epoch时间,到目前为止的时间戳数值
1 2 3 4 5 6 int64_t microSecondsSinceEpoch () const { return microSecondsSinceEpoch_;}; time_t secondsSinceEpoch () const { return static_cast <time_t >(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); }
获取可打印字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 std::string toString () const ; std::string toFormattedString (bool showMicroseconds = true ) const ;#ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include <inttypes.h> string Timestamp::toString () const { char buf[32 ] = {0 }; int64_t seconds = microSecondsSinceEpoch_ / kMicroSecondsPerSecond; int64_t microseconds = microSecondsSinceEpoch_ % kMicroSecondsPerSecond; snprintf (buf, sizeof (buf), "%" PRId64 ".%06" PRId64 "" , seconds, microseconds); return buf; }string Timestamp::toFormattedString (bool showMicroseconds) const { char buf[64 ] = {0 }; time_t seconds = static_cast <time_t >(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); struct tm tm_time; gmtime_r (&seconds, &tm_time); if (showMicroseconds) { int microseconds = static_cast <int >(microSecondsSinceEpoch_ % kMicroSecondsPerSecond); snprintf (buf, sizeof (buf), "%4d%02d%02d %02d:%02d:%02d.%06d" , tm_time.tm_year + 1990 , tm_time.tm_mon + 1 , tm_time.tm_mday, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, microseconds); } else { snprintf (buf, sizeof (buf), "%4d%02d%02d %02d:%02d:%02d" , tm_time.tm_year + 1990 , tm_time.tm_mon + 1 , tm_time.tm_mday, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); } return buf; }
toString()
将秒数、微秒数转换为可打印的std::string类型,用PRId64跨平台输出64bit数据到string缓存;
toFormattedString()
将时间戳转换为人类可理解的格式化时间字符串,形如"yyyymmdd
hh:mm:ss.zzzzzz"。
辅助函数(非class member函数)
常需要比较2个时间先后顺序,计算这2个时刻之间的时间差,一个时刻加上一段时间来得到另外一个时刻,可以通过定义helper函数来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 inline bool operator <(Timestamp lhs, Timestamp rhs) { return lhs.microSecondsSinceEpoch () < rhs.microSecondsSinceEpoch (); }inline double timeDifference (Timestamp high, Timestamp low) { int64_t diff = high.microSecondsSinceEpoch () - low.microSecondsSinceEpoch (); return static_cast <double >(diff) / Timestamp::kMicroSecondsPerSecond; }inline Timestamp addTime (Timestamp timestamp, double seconds) { int64_t delta = static_cast <int64_t >(seconds * Timestamp::kMicroSecondsPerSecond); return Timestamp (timestamp.microSecondsSinceEpoch () + delta); }
operator<()
除了比较2个时间戳大小关系(代表的先后顺序),也是实现等价关系判断的重要条件;
timeDifference()
计算2个时间戳差值,精确到1usec,用小数表示,而整数部分表示1sec;
addTime()
利用一个基准时间戳timestamp +
时间段seconds(秒数),得到新的Timestamp对象。
单元测试
单元测试测什么?muduo是以class为单位,根据提供给用户的功能点进行测试。有些进行的是覆盖测试。
Timestamp主要功能点:
构造对象:默认对象,无效对象;
值语义,即引用传递、值传递对象;
now()
获取当前时间;
microSecondsSinceEpoch()
获取微秒数,secondsSinceEpoch()
获取秒数;
valid()
判断对象是否有效;
fromUnixTime()
将Epoch时间转换为Timestamp对象;
toString()
将时间戳转换为string类型;
toFormattedString()
将时间戳转换为格式化字符串string类型;
辅助函数主要功能点:
operator<()
比较2个Timestamp对象大小;
timeDifference()
计算2个Timestamp对象差值;
ddTime()
将一个Timestamp加上指定时间;
由于toString()
和
toFormattedString()
可以输出类的信息,因此可以作为测试时判断的依据。
博客在允许 JavaScript 运行的环境下浏览效果更佳