在C++开发时,经常会用到与时间相关的函数或者库,例如C风格的时间函数、std::chrono库、Boost.Date_Time库。
他们都包含哪些时间函数或者类?它们的设计思想是什么?什么情况选择什么样的时间函数?怎么使用呢?大家可能都会有这些方面的疑问,于是我决定就对这些库总结梳理一下。
由于文章太长,分成四部分:C风格的时间函数、std::chrono库、Boost.Date_Time库和总结。
C风格的时间类型和函数,有C和C++两个版本,如果使用C版本需要包含#include <time.h>
,如果使用C++版本需要包含#include <ctime>
,并且使用命名空间std
。与平台相关的类型和函数会特别指出。
数据类型
time_t
日历时间(calendar time)。在C/C++编程中经常会遇到它,它定义为从协调时间时(UTC)1970年01月01日00时00分00秒起至现在的总秒数,被用于表示Unix时间(戳)。
一般编译器使用32位整数表示time_t,因此这个时间最多使用到2038年1月18日19时14分07秒。一些编译器使用64位整数来表示time_t,可以避免这个问题。
struct tm
分解时间(broken-down time)。是一个存储了日期和时间的结构体,定义如下:1
2
3
4
5
6
7
8
9
10
11
12struct tm
{
int tm_sec; /*秒,正常范围0-59, 但允许至61, C99(C++11)改为60*/
int tm_min; /*分钟,0-59*/
int tm_hour; /*小时, 0-23*/
int tm_mday; /*日,即一个月中的第几天,1-31*/
int tm_mon; /*月, 从一月算起,0-11,1+tm_mon*/
int tm_year; /*年, 从1900至今已经多少年, 1900+tm_year*/
int tm_wday; /*星期,一周中的第几天, 从星期日算起,0-6*/
int tm_yday; /*从今年1月1日到目前的天数,范围0-365,其中0代表1月1日,1代表1月2日,以此类推*/
int tm_isdst; /* 夏令时标识符,tm_isdst>0,实行夏令时的时候;tm_isdst=0, 不实行夏令时的进候;tm_isdst<0, 表示未知。*/
};
需要特别注意的是,年份是从1900年起至今多少年,而不是直接存储如2011年,月份从0开始的,0表示一月,星期也是从0开始的,0表示星期日,1表示星期一。
clock_t
时钟滴答数(clock tick)。记录从进程开始的时间,是一个相对时间,与CLOCKS_PER_SEC(每秒内时钟滴答数,定义在time.h中,一般为1000)相关。一般用32位整数表示clock_t。
struct timeval
它表示一个时间间隔,这个类型不是标准的C++类型,而是定义在Berkeley Software Distribution (BSD)的Time.h中的一个类型,定义如下:1
2
3
4struct timeval {
long tv_sec; //秒
long tv_usec; //微秒
} timeval;
这个类型具有更高的精度,但不具有跨平台性,主要在Unix或类Unix系统中使用,需要包含头文件<sys/time.h>
,与gettimeofday
函数配合使用。
在Windows下也定义了此结构,主要被Windows Sockets函数select
使用,定义在头文件Winsock2.h
中。
为了跨平台,尽量不要使用此类型。
获取时间函数
time
1 | time_t time (time_t* timer); |
返回自从00:00 hours, Jan 1, 1970 UTC (i.e., the current unix timestamp)到现在的秒数。1
2time_t timer;
time(&timer); /* get current time; same as: timer = time(NULL) */
clock
1 | clock_t clock(void); |
得到从进程启动到此次函数调用的累计的时钟滴答数。可以用于算法计时。1
2
3
4
5
6clock_t start, finish;
start = clock();
...
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "%2.1f seconds\n", duration );
GetLocalTime
1 | void WINAPI GetLocalTime( |
Windows函数,能获取当前日期和时间(UTC),能精确到毫秒,不具有跨平台性。
如果想获取更高精度的时间(<1us),可以用QueryPerformanceCounter
和QueryPerformanceFrequency
。
时间日期数据类型的转换函数
gmtime (time_t -> tm)
1 | struct tm * gmtime (const time_t * timer); |
转换time_t成UTC时间的tm结构体。1
2
3
4time_t rawtime;
struct tm * ptm;
time ( &rawtime );
ptm = gmtime ( &rawtime );
localtime (time_t -> tm)
1 | struct tm * localtime (const time_t * timer); |
转换time_t成本地时区的tm结构体。
多个线程同时调用localtime会引起数据竞争,下面的线程安全部分会讲到它,也可以参看localtime的安全性总结。1
2
3
4
5
6time_t rawtime;
struct tm * timeinfo;
time (&rawtime);
timeinfo = localtime (&rawtime);
printf ("Current local time and date: %s", asctime(timeinfo));
mktime (tm -> time_t)
1 | time_t mktime (struct tm * timeptr); |
转换本地时间的tm结构体成time_t。
这个函数是localtime的逆操作。1
2
3
4
5
6
7
8
9
10
11
12
13#include <iostream>
#include <iomanip>
#include <ctime>
int main()
{
std::time_t t = std::time(NULL);
std::tm tm = *std::localtime(&t);
std::cout << "Today is " << std::put_time(&tm, "%c %Z") <<'\n';
tm.tm_mon -= 100; // tm_mon is now outside its normal range
std::mktime(&tm);
std::cout << "100 months ago was " << std::put_time(&tm, "%c %Z") << '\n';
}
结果1
2Today is Wed Dec 28 09:56:10 2011 EST
100 months ago was Thu Aug 28 10:56:10 2003 EDT
获取时间函数与时区关系
关于时区的总结,请看时区和Unix时间戳的整理
时间日期数据的格式化函数
ctime (time_t -> char*)
1 | char* ctime (const time_t * timer); |
转换time_t成人类可读的字符串,是本地时间。
这个函数等价:1
asctime(localtime(timer))
格式如下:1
Www Mmm dd hh:mm:ss yyyy
Www是周几,Mmm是月份(字母表示),dd是当月的一天,hh::mm::ss是时间,yyyy是年份1
2
3
4
5
6
7
8
9
10
11
12#include <stdio.h> /* printf */
#include <time.h> /* time_t, time, ctime */
int main ()
{
time_t rawtime;
time (&rawtime);
printf ("The current local time is: %s", ctime (&rawtime));
return 0;
}
asctime (tm -> char*)
1 | char* asctime (const struct tm * timeptr); |
转换tm结构体成字符串。
这个函数等价:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18char* asctime(const struct tm *timeptr)
{
static const char wday_name[][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char mon_name[][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static char result[26];
sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
wday_name[timeptr->tm_wday],
mon_name[timeptr->tm_mon],
timeptr->tm_mday, timeptr->tm_hour,
timeptr->tm_min, timeptr->tm_sec,
1900 + timeptr->tm_year);
return result;
}
字符串格式同ctime。1
2
3
4
5
6
7
8
9
10
11
12
13
14#include <stdio.h> /* printf */
#include <time.h> /* time_t, struct tm, time, localtime, asctime */
int main ()
{
time_t rawtime;
struct tm * timeinfo;
time ( &rawtime );
timeinfo = localtime ( &rawtime );
printf ( "The current date/time is: %s", asctime (timeinfo) );
return 0;
}
strftime (tm -> char*)
1 | size_t strftime (char* ptr, size_t maxsize, const char* format, |
转换结构体tm为自定义格式的字符串,类似于sprintf
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <stdio.h> /* puts */
#include <time.h> /* time_t, struct tm, time, localtime, strftime */
int main ()
{
time_t rawtime;
struct tm * timeinfo;
char buffer [80];
time (&rawtime);
timeinfo = localtime (&rawtime);
strftime (buffer,80,"Now it's %I:%M%p.",timeinfo);
puts (buffer);
return 0;
}
strptime (char* -> tm)
1 | char * strptime(const char* buf, const char* format, struct tm* tptr) |
strftime
逆操作,把字符串按照自定义的格式转换为tm。
std::put_time
1 | template< class CharT > |
转换结构体tm为自定义格式的字符串,类似于strftime
,使用上比strftime
方便,可以直接输出到stream中1
cout << std::put_time(ptm,"%c") << endl;
对时间数据的操作
difftime
1 | double difftime (time_t end, time_t beginning); |
计算两个时间的差值,单位为秒。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#include <stdio.h> /* printf */
#include <time.h> /* time_t, struct tm, difftime, time, mktime */
int main ()
{
time_t now;
struct tm newyear;
double seconds;
time(&now); /* get current time; same as: now = time(NULL) */
newyear = *localtime(&now);
newyear.tm_hour = 0; newyear.tm_min = 0; newyear.tm_sec = 0;
newyear.tm_mon = 0; newyear.tm_mday = 1;
seconds = difftime(now,mktime(&newyear));
printf ("%.f seconds since new year in the current timezone.\n", seconds);
return 0;
}
线程安全
ctime和asctime
ctime和asctime不是线程安全的,原因是返回的字符串指针指向了一个静态分配的字符串,会被后续函数调用复写掉。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/** asctime returns a static allocated string shared with other calls
* to asctime or ctime. Every time these functions are called the content
* of the static string is overwritten*/
int main()
{
std::time_t t1 = std::time(nullptr);
std::tm* t1_tm = std::localtime(&t1);
const char* t1_as_text = std::asctime(t1_tm); // save as dangerous string
std::cout << "t1 (original) : " << t1_as_text << "\n";
std::this_thread::sleep_for(std::chrono::seconds(5));
std::time_t t2 = std::time(nullptr);
std::tm* t2_tm = std::localtime(&t2);
const char* t2_as_text = std::asctime(t2_tm); // overwrite t1_as_text
std::cout << "t1 (overwritten): " << t1_as_text;
std::cout << "t2 : " << t2_as_text << std::endl;
return 0;
}
输出:1
2
3
4t1 (original) : Mon Jan 21 13:56:30 2013
t1 (overwrite): Mon Jan 21 13:56:35 2013
t2 : Mon Jan 21 13:56:35 2013
在Linux系统下,可以使用ctime_r
和asctime_r
这两个线程安全的函数,它们线程安全的原因是使用了用户自己分配的buffer,这个buffer至少有26字节的空间。
在Windows系统下,可以使用ctime_s
和asctime_s
这两个函数。1
2
3
4
5
6
7
8
9
10
11
12
13char *ctime_r(const time_t *timep, char *buf);
char *asctime_r(const struct tm *tm, char *buf);
errno_t ctime_s(
char* buffer,
size_t numberOfElements,
const time_t *time
);
errno_t asctime_s(
char* buffer,
size_t numberOfElements,
const struct tm *_tm
);
localtime和gmtime
localtime和gmtime也不是线程安全的。原因是,返回的std::tm指针在内部指向了静态的std::tm结构。
在Linux系统下,可以使用localtime_r
和gmtime_r
这两个线程安全的函数。
在Windows系统下,可以使用localtime_s
和gmtime_s
这两个函数。1
2
3
4
5
6
7
8
9
10
11struct tm *localtime_r(const time_t *timep, struct tm *result);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
errno_t localtime_s(
struct tm* _tm,
const time_t *time
);
errno_t gmtime_s(
struct tm* _tm,
const __time_t* time
);
为了跨平台并且多线程使用这些功能,下面定义安全的版本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
42
43
44
45
46
47namespace g2{
typedef std::chrono::time_point<std::chrono::system_clock> system_time_point;
tm localtime(const std::time_t& time)
{
std::tm tm_snapshot;
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
localtime_s(&tm_snapshot, &time);
#else
localtime_r(&time, &tm_snapshot); // POSIX
#endif
return tm_snapshot;
}
// To simplify things the return value is just a string. I.e. by design!
std::string put_time(const std::tm* date_time, const char* c_time_format)
{
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
std::ostringstream oss;
// BOGUS hack done for VS2012: C++11 non-conformant since it SHOULD take a "const struct tm* "
// ref. C++11 standard: ISO/IEC 14882:2011, § 27.7.1,
oss << std::put_time(const_cast<std::tm*>(date_time), c_time_format);
return oss.str();
#else // LINUX
const size_t size = 1024;
char buffer[size];
auto success = std::strftime(buffer, size, c_time_format, date_time);
if (0 == success)
return c_time_format;
return buffer;
#endif
}
// extracting std::time_t from std:chrono for "now"
std::time_t systemtime_now()
{
system_time_point system_now = std::chrono::system_clock::now();
return std::chrono::system_clock::to_time_t(system_now);
}
} // g2-namespace
参考
- 维基百科time.h
- cppreference.com
- cplusplus
- c++ 时间类型详解 time_t
- Exploring C++11, part 2 (localtime and time again)
- Unix, C, and C++Function Reference Time
版权声明