C++:时间

时间在计算机中,通常以三种形式存储,三种方式各有各的特点。
1、Unix时间戳
这个东西算是时间表示的元老级结构了,因为出现的早,所以用的也最广泛。原理是,一个32位的数字,代表从1970年1月1日到现在的秒数。由于最高位代表符号位未被使用,所以可以表示的最长的时间点为2038年左右。目前所有Unix及类Unix操作系统都使用这种方式表示时间。著名的水果1970变砖BUG就是因为这种时间表示方式的固有特性所导致。这种时间表示方式还有一个非常大的问题就是,不能计算闰秒。闰秒的含义是,因为地球绕太阳自转的速度越来越慢,导致地球公转一圈的时间更久。现代的人们感受不出来,但如果时间线放长,并且不闰秒,那么可以预见,未来某一天的正午12点是晚上,凌晨12点是白天。所以,在某个时间点的闰秒尤为重要。闰秒通常在某个分钟的变化时,秒数记为57、58、59、60、0、1、2……,也就是说,多出来一个第60秒。闰秒虽好,统一地球公转,为太阳系历法做贡献,但每次闰秒通常会造成几千万美元的经济损失,原因为,使用了Unix时间戳这种计时方法的操作系统,并不能区分闰秒,导致操作系统时间与世界时间不同,然后在金融等领域,一秒钟多计算了什么什么,少计算了什么什么,导致结果不是实际想要的。这时候计算误差所导致的经济损失累积起来,就有这么严重了。另外,这种计时方式最多只能计算到2038年,之后就无能为力了。如果这些操作系统迟迟不更新原子计时方式,那么,等待它们的,只有,系统罢工了。因为以上两个问题太严重了,导致人们常常忽略了它的第三个问题:精度太差。最高精度就是秒了,但对于计算机来说,计算很多东西精度常常需要达到毫秒才够用,有的甚至需要微秒级精度,所以这些地方也用不了这种时间表示方式。
基于这种计时方式,出现了一个分支,使用64位进行计时,这就不存在年份限制这BUG了,不过这分支用的比较少。

2、DATE时间类型
网上也有将其称作VB时间类型或者浮点时间类型,使用一个浮点数,代表从1899年12月30日凌晨到目标时间的天数。比如,1900-01-01 00:00:00表示为:2.0;1900-01-01 06:00:00表示为:2.25。这种事件类型有一个好处就是精度高,另外它也能代表1899年前的时间,使用负数表示。这种时间类型还有一个分支,代表着从1900年1月1日起至今的天数,也就是说,上一个事件类型的值-2就成了这种时间类型的值。这种事件类型通常不能用累加来实现时间增量,所以不会出现闰秒的问题,另外精度也够高。有一个小缺憾是,这种时间类型由于是浮点数,计算稍显麻烦,另外也不能代表时间增量,总的来说,比Unix时间戳好多了。所以用的也比较多。Win32平台内部很多地方都使用的这种事件类型。
3、结构化时间类型
这种计时方式是最笨的,同时也是最有效的。只要一个时间结构,年月日时分秒分别用不同的整型数字存放,那么都属于这种时间类型。这种时间类型的分支也特别多,有的全部用int存储,有的月份用4位、日用5位来存储;有的月份范围为1~12,有的月份范围为0~11;有的精度只有秒,有的甚至可以存放微秒,由于分支特别多,所以在不同的分支上进行转换时,需要特别注意按照规定来。这种时间类型好处都有啥,谁说对了金坷垃送给他。

好咯。基本的时间存储方式说明白了,那么开始讲解代码。
1、计算时间差
有时候,需要计算一段代码的执行时间,比如多少多少毫秒,这时候如果用完整的时间类型通常大材小用了。代表时间差有两种便捷的方式,一种是使用Win32的GetTickCount函数,返回一个DWORD的时间。只需要在需要统计时间的代码前和后面分别调用它,相减,就是时间差的毫秒数。关于这个函数的实际含义,msdn上面说返回系统启动之后所经过的时间的毫秒数。我开始还以为是开机时间,然而经我测试后,结果换算了一下大约为10天左右,刚好这电脑有很长时间没用了,计算BIOS时间的电源也断了,大概在10天前开了一下,所以,我猜测,如果没有我那种完全放电的话,这个应该代表BIOS第一次加电时至今的毫秒数,如果经过断电,那就为上一次加电至今的毫秒数。示例代码如下:

1
2
3
4
5
6
7
8
DWORD d = ::GetTickCount (); // 计时开始
 
// 一大串需要计时的代码,或者直接。。。
::Sleep (1000); //暂停一秒钟
 
// 计时结束
d = ::GetTickCount () - d;
// 这时候的d里面所存储的就是时间差的毫秒数

2、暂停一段时间
大型任务为避免持续占CPU时间,通常需要休息一段时间,让出CPU时间片给其他线程。暂停的方式除了上面Win32提供的Sleep函数外,还有一种是C++11所提供的,示例代码如下:

1
2
3
4
5
6
7
#include <thread>
#include <chrono>
 
// ...
 
std::this_thread::sleep_for (std::chrono::seconds (2));       // 暂停2秒
std::this_thread::sleep_for (std::chrono::milliseconds (10)); // 暂停10毫秒

3、C语言时间结构
C语言时间结构通常有两种类型,一种是time_t,一种是tm。time_t在32位或64位开发环境下,所代表的也分为两种不同大小的时间结构。定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef _TIME32_T_DEFINED
typedef _W64 long __time32_t;   /* 32-bit time value */
#define _TIME32_T_DEFINED
#endif  /* _TIME32_T_DEFINED */
 
#ifndef _TIME64_T_DEFINED
typedef __int64 __time64_t;     /* 64-bit time value */
#define _TIME64_T_DEFINED
#endif  /* _TIME64_T_DEFINED */
 
#ifndef _TIME_T_DEFINED
#ifdef _USE_32BIT_TIME_T
typedef __time32_t time_t;      /* time value */
#else  /* _USE_32BIT_TIME_T */
typedef __time64_t time_t;      /* time value */
#endif  /* _USE_32BIT_TIME_T */
#define _TIME_T_DEFINED         /* avoid multiple def's of time_t */
#endif  /* _TIME_T_DEFINED */

貌似。与目标环境配置无关,只要是在64位环境编译,就算目标生成类型为32位应用程序,那么time_t也是64位。
然后是tm这种结构,定义如下:

1
2
3
4
5
6
7
8
9
10
11
struct tm {
    int tm_sec;     /* seconds after the minute - [0,59] */
    int tm_min;     /* minutes after the hour - [0,59] */
    int tm_hour;    /* hours since midnight - [0,23] */
    int tm_mday;    /* day of the month - [1,31] */
    int tm_mon;     /* months since January - [0,11] */
    int tm_year;    /* years since 1900 */
    int tm_wday;    /* days since Sunday - [0,6] */
    int tm_yday;    /* days since January 1 - [0,365] */
    int tm_isdst;   /* daylight savings time flag */
};

不太节省内存空间哪,4位足够代表月份了,但足足用了一个int。也罢,现在计算机内存这么大,浪费这点也没什么。
以下代码示例基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取当前时间,实际代表的值为本地时间
time_t t = time (NULL);
 
// 将当前时间转为格林威治时间
//tm *t2 = gmtime (&t);
 
// 将当前时间转为本地时间
tm *t2 = localtime (&t);
 
// 输出当前时间
printf ("%4d-%02d-%02d %02d:%02d:%02d", t2->tm_year + 1900, t2->tm_mon + 1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec);
 
//// 下面这段代码效果同上一行代码完全一样,写法稍有不同
//char cBuf [64];
//strftime (cBuf, 64, "%Y-%m-%d %H:%M:%S", t2);
//printf (cBuf);
 
// 将tm时间结构转回time_t
t = mktime (t2);

然后是计算时间差。由于time_t精度为秒,所以无法计算秒以下的单位。示例代码如下:

1
2
3
4
5
6
time_t t = time (NULL);
std::this_thread::sleep_for (std::chrono::milliseconds (1234));
time_t t2 = time (NULL);
 
// 计算时间差
printf ("%lf", difftime (t2, t));

结果为1.0000000,说明单位就是秒。但返回类型是双精度浮点型。估计这个小数没任何卵用。
4、C++11时间结构
使用前需包含头文件chrono。为了同C语言兼容,所以C++11时间也可以直接与time_t互转

1
2
3
4
5
6
7
8
// 获取当前时间
std::chrono::system_clock::time_point tp = std::chrono::system_clock::now ();
 
// C++11时间转C语言时间
time_t t = std::chrono::system_clock::to_time_t (tp);
 
// C语言时间转C++11时间
tp = std::chrono::system_clock::from_time_t (t);

除了必要的转换外,C++11一般格式化时间也是首先转为C语言时间结构体然后进行打印。然后同样是计算时差,这个时间结构稍微高级一些,精度可以达到纳秒(1e-9),示例代码如下:

1
2
3
4
5
6
7
std::chrono::system_clock::time_point t = std::chrono::system_clock::now ();
std::this_thread::sleep_for (std::chrono::seconds (2));
std::chrono::system_clock::time_point t2 = std::chrono::system_clock::now ();
 
// 纳秒级精度
n = std::chrono::duration_cast <std::chrono::nanoseconds> (t2 - t).count ();
// 将以上代码的 nanoseconds 替换成 seconds、milliseconds、microseconds 就分别代表着秒级精度、毫秒级精度、微秒级精度

另外c++11时间的输出如果需要带毫秒那稍微需要转一个弯,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::string format_time ()
{
    char buf_time [32], buf_time2 [32];
    buf_time [0] = buf_time2 [0] = '\0';
    auto time_now = std::chrono::system_clock::now ();
    auto duration_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_now.time_since_epoch ());
    auto ms_part = duration_in_ms - std::chrono::duration_cast<std::chrono::seconds>(duration_in_ms);
    time_t raw_time = std::chrono::system_clock::to_time_t (time_now);
    tm local_time_now;
    _localtime64_s (&local_time_now, &raw_time);
    strftime (buf_time2, sizeof (buf_time2), "%Y-%m-%d %H:%M:%S", &local_time_now);
    //char *xx = std::put_time (&local_time_now, "%Y-%m-%d %H:%M:%S");
    _snprintf (buf_time, sizeof(buf_time), "%s.%03d", buf_time2, ms_part.count ());
    return buf_time;
}

5、MFC/ATL时间结构
主要就是CTime和COleDateTime,后者使用前需包含ATLComTime.h头文件。这两者非常相似所以放在一起说;另外这个放在C++11之后不代表比C++11的更高级,我个人喜欢用标准的东西,但很多大型MFC项目用的基本都是这个,所以有必要说说。基本用法挺相似:

1
2
3
4
5
CTime t = CTime::GetCurrentTime ();
printf (t.Format ("%Y-%m-%d %H:%M:%S"));
 
COleDateTime t2 = COleDateTime::GetCurrentTime ();
printf (t2.Format ("%Y-%m-%d %H:%M:%S"));

它俩构造函数比较相似,但COleDateTime功能稍强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CTime () throw ();
CTime (__time64_t time) throw ();
CTime (int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec, int nDST = -1);
CTime (WORD wDosDate, WORD wDosTime, int nDST = -1);
CTime (const SYSTEMTIME& st, int nDST = -1);
CTime (const FILETIME& ft, int nDST = -1);
CTime (const DBTIMESTAMP& dbts, int nDST = -1) throw ();
 
COleDateTime () throw ();
COleDateTime (const VARIANT& varSrc) throw ();
COleDateTime (DATE dtSrc) throw ();
COleDateTime (__time32_t timeSrc) throw ();
COleDateTime (__time64_t timeSrc) throw ();
COleDateTime (const SYSTEMTIME& systimeSrc) throw ();
COleDateTime (const FILETIME& filetimeSrc) throw ();
COleDateTime (int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec) throw ();
COleDateTime (WORD wDosDate, WORD wDosTime) throw ();
COleDateTime (const DBTIMESTAMP& dbts) throw ();

可见CTime构造是相当受限制的,COleDateTime构造除了有以上内容外,还可以与VARIANT类型、DATE类型(浮点时间类型)互转,甚至time_t也区分了32位与64位。
虽然这俩功能还行但毕竟是M$大大的东西,推荐还是使用C/C++标准写法,兼容性更强,并且如果以后项目需要迁移至其他平台,这种写法也更方便,几乎不用修改代码。

发布者

fawdlstty

又一只萌萌哒程序猿~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注