C++时间库(一) C风格的时间函数

文章目录

  1. 1. 数据类型
    1. 1.1. time_t
    2. 1.2. struct tm
    3. 1.3. clock_t
    4. 1.4. struct timeval
  2. 2. 获取时间函数
    1. 2.1. time
    2. 2.2. clock
    3. 2.3. GetLocalTime
  3. 3. 时间日期数据类型的转换函数
    1. 3.1. gmtime (time_t -> tm)
    2. 3.2. localtime (time_t -> tm)
    3. 3.3. mktime (tm -> time_t)
  4. 4. 获取时间函数与时区关系
  5. 5. 时间日期数据的格式化函数
    1. 5.1. ctime (time_t -> char*)
    2. 5.2. asctime (tm -> char*)
    3. 5.3. strftime (tm -> char*)
    4. 5.4. strptime (char* -> tm)
    5. 5.5. std::put_time
  6. 6. 对时间数据的操作
    1. 6.1. difftime
  7. 7. 线程安全
    1. 7.1. ctime和asctime
    2. 7.2. localtime和gmtime
  8. 8. 参考

在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
12
struct 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
4
struct 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
2
time_t timer;
time(&timer); /* get current time; same as: timer = time(NULL) */

clock

1
clock_t clock(void);

得到从进程启动到此次函数调用的累计的时钟滴答数。可以用于算法计时。

1
2
3
4
5
6
clock_t start, finish;
start = clock();
...
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "%2.1f seconds\n", duration );

GetLocalTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void WINAPI GetLocalTime(
_Out_ LPSYSTEMTIME lpSystemTime
)
;


typedef struct _SYSTEMTIME {
WORD wYear; //有效值是从1601年到30827年
WORD wMonth; //有效值是从1到12
WORD wDayOfWeek; //有效值是从0到6,0是周日
WORD wDay; //有效值是从1到31
WORD wHour; //有效值是从0到23
WORD wMinute; //有效值是从0到59
WORD wSecond; //有效值是从0到59
WORD wMilliseconds; //有效值是从0到999
} SYSTEMTIME, *PSYSTEMTIME;

Windows函数,能获取当前日期和时间(UTC),能精确到毫秒,不具有跨平台性。
如果想获取更高精度的时间(<1us),可以用QueryPerformanceCounterQueryPerformanceFrequency

时间日期数据类型的转换函数

gmtime (time_t -> tm)

1
struct tm * gmtime (const time_t * timer);

转换time_t成UTC时间的tm结构体。

1
2
3
4
time_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
6
time_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
2
Today is           Wed Dec 28 09:56:10 2011 EST
100 months ago was Thu Aug 28 10:56:10 2003 EDT

获取时间函数与时区关系

enter description here
关于时区的总结,请看时区和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
18
char* 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
2
size_t strftime (char* ptr, size_t maxsize, const char* format,
const struct tm* timeptr );

转换结构体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
2
template< class CharT >
/*unspecified*/ put_time( const std::tm* tmb, const CharT* fmt );

转换结构体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

ctimeasctime不是线程安全的,原因是返回的字符串指针指向了一个静态分配的字符串,会被后续函数调用复写掉。

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
4
t1 (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_rasctime_r这两个线程安全的函数,它们线程安全的原因是使用了用户自己分配的buffer,这个buffer至少有26字节的空间。
在Windows系统下,可以使用ctime_sasctime_s这两个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
char *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

localtimegmtime也不是线程安全的。原因是,返回的std::tm指针在内部指向了静态的std::tm结构。
在Linux系统下,可以使用localtime_rgmtime_r这两个线程安全的函数。
在Windows系统下,可以使用localtime_sgmtime_s这两个函数。

1
2
3
4
5
6
7
8
9
10
11
struct 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
47
namespace 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

参考

  1. 维基百科time.h
  2. cppreference.com
  3. cplusplus
  4. c++ 时间类型详解 time_t
  5. Exploring C++11, part 2 (localtime and time again)
  6. Unix, C, and C++Function Reference Time

版权声明

本博客 采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可

本文永久链接:http://yoursite.com/2016/01/06/C++时间库(一)/