C/C++ 日期时间整理备忘

在平时的开发过程中,难免会遇到时间相关的处理,比如心跳、定时任务、超时处理等等,总是很难在众多时间处理函数中选择一个。在假期这几天把经常用到的一些时间处理函数整理了一下,算是个梳理也是备忘吧。

一、一些常用时间类型

1、time_t

在VS2015中,time_t的定义为:

#ifndef _CRT_NO_TIME_T
    #ifdef _USE_32BIT_TIME_T
        typedef __time32_t time_t;
    #else
        typedef __time64_t time_t;
    #endif
#endif

time_t的默认类型是 __time64_t,而__time64_t的定义为 typedef __int64 __time64_t;可以看到time_t就是一个64位的整型类型。

time_t 保存的是自1970年1月1日00:00:00以来的秒数。这个时间点就是常见的 Epoch 时间

2、tm

struct tm {
  int tm_sec;   // 秒,正常范围从 0 到 59,但允许至 61
  int tm_min;   // 分,范围从 0 到 59
  int tm_hour;  // 小时,范围从 0 到 23
  int tm_mday;  // 一月中的第几天,范围从 1 到 31
  int tm_mon;   // 月,范围从 0 到 11
  int tm_year;  // 自 1900 年起的年数
  int tm_wday;  // 一周中的第几天,范围从 0 到 6,从星期日算起
  int tm_yday;  // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
  int tm_isdst; // 夏令时
}

3、clock_t

在VS2015中,clock_t 的定义为:

typedef long clock_t;

4、struct timespec, int clock_gettime(clockid_t clk_id, struct timespec* tp)仅在linux平台使用

struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

通过 clock_gettime() 函数获取系统的当前时间。

参数 clk_id : 检索和设置的clk_id指定的时钟时间。可以设置如下:

CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,如果系统时间被用户改成其他,则对应的时间相应改变
CLOCK_REALTIME_COARSE:和CLOCK_REALTIME类似,但是执行速度快,精度低
CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
CLOCK_MONOTONIC_COARSE :和CLOCK_MONOTONIC类似,但是执行速度快,精度低
CLOCK_BOOTTIME:和  CLOCK_MONOTONIC 类似,但是包括了系统休眠的时间。
CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间
CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间

参数:tp 返回时间值,返回值 0成功, 1失败。

5、struct timeval, int gettimeofday(struct timeval* tv, struct timezone* tz) 仅在linux平台使用

struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_nsec;        /* microseconds */
};

通过 gettimeofday() 函数获取系统的当前时间:

参数:
struct timeval *tv 将带回当前的系统时间,从UTC1970-1-1 0:0:0开始计时
​struct timezone *tz 带回当前的时区信息,如果不需要刻意设置为0

timezone结构描述如下:

struct timezone {
    int tz_minuteswest;   /* minutes west of Greenwich 和格林威治 时间差了多少分钟 */ 
    int tz_dsttime;       /* type of dst correction 日光节约时间的状态(夏时制)*/
};

二、一些时间相关的函数

1、time_t time(time_t* time);

功能:获取系统当前时间。

头文件:#include

返回值:该函数返回系统当前的日历时间,自1970年1月1日00:00 以来经过的秒数。如果系统没有时间,返回-1

参数:如果time不为nullptr,则返回值也存储在time指向的对象中

(1)、简单使用

#include 
#include 

int main()
{
    time_t result = time(nullptr);
    std::cout << "自Epoch以来的秒数:" << result << std::endl;

    system("pause");
}

(2)、结果

自Epoch以来的秒数:1522943543
请按任意键继续…

2、struct tm* localtime(const time_t* time);

功能:获取一个指示本地时间的 struct tm 结构

头文件:#include

返回值:返回指向 struct tm结构的指针,该结构带有被填充的时间信息,使用本地时区表示。(struct tm结构定义见上文)

参数:当前时间

(1)、简单使用

#include <iostream>
#include <time.h>

int main()
{
    time_t result = time(nullptr);
    struct tm* formatTime = localtime(&result);
    std::cout
        << "tm_sec: "   << formatTime->tm_sec   << std::endl
        << "tm_min: "   << formatTime->tm_min   << std::endl
        << "tm_hour: "  << formatTime->tm_hour  << std::endl
        << "tm_mday: "  << formatTime->tm_mday  << std::endl
        << "tm_mon: "   << formatTime->tm_mon   << std::endl
        << "tm_year: "  << formatTime->tm_year  << std::endl
        << "tm_wday: "  << formatTime->tm_wday  << std::endl
        << "tm_yday: "  << formatTime->tm_yday  << std::endl
        << "tm_isdst: " << formatTime->tm_isdst << std::endl;

    system("pause");
}

(2)、结果

tm_sec: 11
tm_min: 24
tm_hour: 0
tm_mday: 6
tm_mon: 3
tm_year: 118
tm_wday: 5
tm_yday: 95
tm_isdst: 0
请按任意键继续…

3、char* ctime(const time_t* time);

功能:获取表示当前时间的字符串指针

头文件:#include

返回值:返回表示当前时间的字符串指针,字符串形式 Www Mmm dd hh:mm:ss yyyy\n\0 ,其中,Www 表示星期几,Mmm 是以字母表示的月份,dd 表示一月中的第几天,hh:mm:ss 表示时间,yyyy 表示年份。

参数:当前时间

(1)、简单使用

#include 
#include 

int main()
{
    time_t result = time(nullptr);
    std::cout << "现在时间是:" << ctime(&result);

    system("pause");
}

(2)、结果

现在时间是:Fri Apr 6 00:05:27 2018
请按任意键继续…

4、char* asctime(const struct tm* time);

功能:获取表示当前时间的字符串指针 (ps: 功能及返回值同 ctime,但是参数不同)

头文件:#include

返回值:返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,字符串形式 Www Mmm dd hh:mm:ss yyyy\n\0 ,其中,Www 表示星期几,Mmm 是以字母表示的月份,dd 表示一月中的第几天,hh:mm:ss 表示时间,yyyy 表示年份

参数:指向 struct tm 结构的当前时间的指针

(1)、简单使用

#include 
#include 

int main()
{
    time_t result = time(nullptr);
    struct tm* formatTime = localtime(&result);
    std::cout << "现在时间是:" << asctime(formatTime);

    system("pause");
}

(2)、结果

现在时间是:Fri Apr 6 00:41:41 2018
请按任意键继续…

5、size_t strftime(char* strDest, size_t maxsize, const char* format, const struct tm* time);

作用:自定义日期时间输出格式

头文件:#include

返回值:

参数:strDest 用来存放格式化后的字符串缓存,maxsize 指定最多可以输出的字符数, format 格式化字符串,time 要转换的时间

格式化字符串 含义 格式化字符串 含义 格式化字符串 含义 格式化字符串 含义
%a 星期几的简写 %F 年 - 月 - 日 %n 新行符 %V 每年的第几周,使用基于周的年
%A 星期几的全称 %g 年份的后两位数字,使用基于周的年 %p 本地的AM或PM的等价显示 %w 十进制表示的星期几(值从0到6,星期天为0)
%b 月分的简写 %G 年分,使用基于周的年 %r 12小时的时间 %W 每年的第几周,把星期一做为第一天(值从0到53)
%B 月份的全称 %h 简写的月份名 %R 显示小时和分钟:hh : mm %x 标准的日期串
%c 标准的日期的时间串 %H 24小时制的小时 %S 十进制的秒数 %X 标准的时间串
%C 年份的后两位数字 %I 12小时制的小时 %t 水平制表符 %y 不带世纪的十进制年份(值从0到99)
%d 十进制表示的每月的第几天 %j 十进制表示的每年的第几天 %T 显示时分秒:hh : mm : ss %Y 带世纪部分的十进制年份
%D 月 / 天 / 年 %m 十进制表示的月份 %u 每周的第几天,星期一为第一天 (值从0到6,星期一为0) %z %Z 时区名称,如果不能得到时区名称则返回空字符。
%e 在两字符域中,十进制表示的每月的第几天 %M 十进制表示的分钟数 %U 第年的第几周,把星期日做为第一天(值从0到53) %% 百分号

(1)、简单使用

#include 
#include 

int main(void)
{
    time_t result = time(nullptr);
    struct tm *localTime = localtime(&result);

    char outBuffer[40] = { 0 };
    strftime(outBuffer, 40, "%Y-%m-%d %H:%M:%S", localTime);
    std::cout << "now: " << result << " = " << outBuffer << std::endl;

    system("pause");
}

(2)、结果

now: 1522951679 = 2018-04-06 02:07:59
请按任意键继续…

5、struct tm* gmtime(const time_t* time);

功能:使用 time_t 的值来填充 struct tm 结构,使用格林尼治标准时间(GMT)表示 。(ps: 可以对比 localtime 函数)

头文件:#include

返回值:被填充了的 struct tm结构

参数:表示日历时间的 time_t 类型的指针

#include <iostream>
#include <time.h>

int main()
{
    time_t result = time(nullptr);
    struct tm* formatTime = gmtime(&result);
    std::cout
        << "tm_sec: "   << formatTime->tm_sec   << std::endl
        << "tm_min: "   << formatTime->tm_min   << std::endl
        << "tm_hour: "  << formatTime->tm_hour  << std::endl
        << "tm_mday: "  << formatTime->tm_mday  << std::endl
        << "tm_mon: "   << formatTime->tm_mon   << std::endl
        << "tm_year: "  << formatTime->tm_year  << std::endl
        << "tm_wday: "  << formatTime->tm_wday  << std::endl
        << "tm_yday: "  << formatTime->tm_yday  << std::endl
        << "tm_isdst: " << formatTime->tm_isdst << std::endl;

    system("pause");
}

tm_sec: 30
tm_min: 57
tm_hour: 16
tm_mday: 5
tm_mon: 3
tm_year: 118
tm_wday: 4
tm_yday: 94
tm_isdst: 0
请按任意键继续…

可以看到:日期时间较localtime不一样了,格林尼治标准时间(GMT)比当地时间要晚8个小时。

6、time_t mktime(struct tm* time);

功能:与 gmtime 函数功能相反,使用一个 struct tm 类型的指针获取一个日历时间 (ps,若参数 time 指向的是格林尼治标准时间,返回的time_t也是格林尼治标准时间,比当地时间晚8小时;若参数 time 指向的是本地时间,返回的也是本地日期时间)

头文件:#include

返回值:日历时间

参数:已经被填充的 struct tm 结构指针

举例省略

7、double difftime(time_t time2, time_t time1);

功能:返回time2time1之间相差的秒数,time2time1都是日历时间,都表示距离1970年1月1日00::00:00的秒数

头文件:#include

返回值:两个时间点之间相差的秒数

参数:两个日历时间

(1)、简单使用

#include 
#include 
#include 

int main()
{
    time_t time1 = time(nullptr);
    Sleep(3000);     // 睡眠3秒
    time_t time2 = time(nullptr);
    std::cout << "程序运行的秒数:" << difftime(time2, time1) << std::endl;

    system("pause");
}

(2)、结果

程序运行的秒数:3
请按任意键继续…

8、clock_t clock(void);

功能:返回”进程开始启动”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,通常使用两次clock调用,返回之间CPU使用时间

头文件:#include

返回值:CPU时钟计时单元数

参数:无

(1)、简单使用

#include 
#include 
#include 

int main()
{
    clock_t start = clock();
    Sleep(1234);     // 睡眠1234毫秒
    clock_t end = clock();
    std::cout
        << "start: " << start << std::endl
        << "end: " << end << std::endl
        << "CLOCKS_PER_SEC: " << CLOCKS_PER_SEC << std::endl
        << "程序运行的秒数:" << (double)(end - start) / CLOCKS_PER_SEC << std::endl;

    system("pause");
}

(2)、结果

start: 253
end: 1487
CLOCKS_PER_SEC: 1000
程序运行的秒数:1.234
请按任意键继续…

可以看到,clock()可以精确到毫秒

三、一些日期时间库

1、std::chrono

std::chronoC++11boost 中引入的,用于C++的时间相关的处理。

(1)、std::chrono 库主要实现了三个组件,分别是:

  • duration // 持续时间或时间段
  • time_point // 时间点
  • clock // 时钟类型,时钟又分为三类:
    • system_clock // 系统的时钟
    • steady_clock // 永远不会被调整的单调时钟,始终以统一的速度在增长
    • high_resolution_clock // 提供尽可能小的滴答周期以及更高的分辨率

(2)、使用方式

  • 要使用 std::chrono 库,需要 #include
  • std::chrono 库的所有组件,实现都在 std::chrono 命名空间下,所以各个组件的调用都要加上 std::chrono 前缀

(3)、三个组件的介绍及使用

  1. duration

    持续时间对象通过诸如分钟,两小时或十毫秒的计数来表示时间跨度。例如:

    “60秒”这个持续时间:

    ​ 可以用由60个周期为1s的时间长度表示;也可以由1个周期为1min的时间长度表示。

    std::chrono 中,duration 可表示六种持续时间,分别是: 小时、分钟、秒、毫秒、微秒、纳秒,定义如下:

    // duration TYPEDEFS
    typedef duration<long long, nano> nanoseconds;
    typedef duration<long long, micro> microseconds;
    typedef duration<long long, milli> milliseconds;
    typedef duration<long long> seconds;
    typedef duration<int, ratio<60> > minutes;
    typedef duration<int, ratio<3600> > hours;

    duration 的声明如下:

    // CLASS TEMPLATE duration
    template<class _Rep, class _Period = ratio<1> >
    class duration;

    可以看到,duration 是一个模板类。可以把 _Rep 理解为上面提到的多少个周期,比如60,把 _Period 理解为一个周期有多长时间。 六种持续时间都是 duration 根据不同模板参数 typedef 而来的。

    这里简单说一下周期的默认参数 ratio 这个类型。

    ratio 的声明如下:

    template1>
    struct ratio;

    模板类 ratio 有两个数值型模板参数,可以简单的把 ratio 想象成一个 比率 类型。_Nx 作为分子,_Dx 作为分母。根据 _Nx_Dx 的相对大小区别不同的时间分辨率。

    几个分辨率的定义:

    typedef ratio<1, 1000000000> nano;
    typedef ratio<1, 1000000> micro;
    typedef ratio<1, 1000> milli;

    以上对 duration 进行了简单介绍,下面看下 duration 的使用吧!

    
    #include 
    
    
    #include 
    
    using namespace std::chrono;
    
    int main()
    {
       // 定义 '100毫秒的持续时间'
       duration<long long, std::milli> one_handurd_millis(100);  // 或者这样定义: milliseconds one_handurd_millis(100);
    
       std::cout << "分辨率(一个周期长): " << duration<long long, std::milli>::period::num << "/" << duration<long long, std::milli>::period::den << " s" << std::endl
           << "周期数: " << one_handurd_millis.count() << std::endl;
    
       one_handurd_millis += milliseconds(30);
       std::cout << "加上30ms后,原时间长度变为: " << one_handurd_millis.count() << std::endl;
    
       system("pause");
    }

    结果:

    分辨率(一个周期长): 1/1000 s
    周期数: 100
    加上30ms后,原时间长度变为: 130
    请按任意键继续…

  2. clock

    三种时钟类型都是作为 time_point 类型的参数,单独看时钟类型没有意义。

    只是 system_clocksteady_clock 都有一个静态成员函数: now() 可用。

    
    #include 
    
    
    #include 
    
    using namespace std::chrono;
    
    int main()
    {
       std::cout << system_clock::now().time_since_epoch().count() << std::endl;
       std::cout << steady_clock::now().time_since_epoch().count() << std::endl;
    
       time_t t = system_clock::to_time_t(system_clock::now());
       std::cout << ctime(&t);
    
       system("pause");
    }

    结果:

    15230306967929631
    90472482570038
    Sat Apr 7 00:04:56 2018
    请按任意键继续…

  3. time_point

    时间点需要以某一个时钟的计算为基准,使用 time_point 需要一个时钟类型。

    
    #include 
    
    
    #include 
    
    using namespace std::chrono;
    
    int main()
    {
       system_clock::time_point tp = system_clock::now();    // 或者这样定义:time_point tp;
       time_t t = system_clock::to_time_t(tp);
       std::cout << "距离Epoch时间点已经过去: " << duration_cast(tp.time_since_epoch()).count() << "ms" << std::endl
           << "现在时间是: " << ctime(&t);
    
       tp += hours(24);
       time_t t1 = system_clock::to_time_t(tp);
       std::cout << "24小时后: " << ctime(&t1);
    
       system("pause");
    }

    结果:

    距离Epoch时间点已经过去: 1523032933770ms
    现在时间是: Sat Apr 7 00:42:13 2018
    24小时后: Sun Apr 8 00:42:13 2018
    请按任意键继续…

2、Poco::Timestamp 和 Poco::DateTime

(1)、Poco::Timestamp

Poco::Timestamp 内部存储了一个距离 Epoch 时间点的微秒级整型变量,主要的对外接口有以下几个:

  1. 默认构造函数。使用 Poco::Timestamp 的默认构造函数会使用当前时间构造一个时间戳。例如:Poco::Timestamp ts;
  2. 更新时间戳。使用 void update() 方法会把时间戳更新到当前时间。
  3. 时间戳之间的算术比较运算符。==、!=、>、>=、<、<=
  4. 时间戳的增减。+、-、+=、-=,这些方法的参数有 Timestamp ,也有另外的 Poco::Timespan (时间间隔) ,如果需要深入了解这个类,可以看官网文档: https://pocoproject.org/docs/Poco.Timespan.html 。
  5. 获取超时时间以及判断是否超过指定时间间隔。TimeDiff elapsed() const;bool isElapsed(TimeDiff interval) const;
  6. EpochUTC 时间的相互转化。std::time_t epochTime() const;UtcTimeVal utcTime() const; 静态函数 static Timestamp fromEpochTime(std::time_t t);static Timestamp fromUtcTime(UtcTimeVal val);

在服务器里面,要维护多个客户端的心跳,对每个客户端都用一个定时器维护心跳是不可能的,这时候 Poco::TImestamp 用来判断心跳是否超时以及是否到了发送心跳的时间就比较合适.

(2)、Poco::DateTime

Poco::DateTime 内部存储了 short 类型的 年、月、日、时、分、秒、毫秒、微秒 以及一个 int64 类型的分辨率为100纳秒的 utc时间戳。跟Poco::Timestamp 相比,Poco::DateTime 主要是对 ”年月日时分秒“ 进行操作与获取,接口类型也与 Poco::Timestamp 类似,而这些在程序中较少用到,代码也不多,就不花篇幅讲述了。Poco::DateTime 的接口文档可以从官网获取 https://pocoproject.org/docs/Poco.DateTime.html 。

今天就先写到这儿,本来准备把 boost::date_time 也一块比较下,看了下书,发现 date_time 库很杂,而且 date_time 库还需要编译安装,boost在平时的开发中用的也不多,就不放到这里了。

你可能感兴趣的:(C/C++ 日期时间整理备忘)