(笔记)处理日期和时间的方法(chrono 库,time_t等)

0 摘要

需求:“看来是同类型的替身”
需要处理时间的地方可太多了,当然了方法也有很多,有C++11 中提供了日期和时间相关的库 chrono,还有Linux中经常用time_t,处理高精度的如timeval。(chrono库中还有函数可以与time_t相转换)

1 chrono 库

chrono 库参考:苏丙榅
详细说明请参考上述两篇,以下为简要概述

chrono 库主要包含三种类型的类:时间间隔duration时间点time point时钟clocks
关系:由来确定时间点,时间点可通过时间间隔来计算,而时间间隔= 时钟周期 X 周期次数

1.1 时间间隔duration

// 定义于头文件 <chrono>
template<
    class Rep,   //表示时钟数(周期数)的类型(默认为整形)
    class Period = std::ratio<1>    //时钟的周期长度
> class duration;

ratio表示每个时钟周期的秒数(一个分子除以分母的数值,分母默认为1)。
如ratio< 60 > 代表一分钟,ratio< 60 * 60 * 24 > 代表一天,ratio<1,1000 > 代表的是 1/1000 秒,也就是 1 毫秒。(当然也有定义了一些常用的时间间隔,看参考里面)。

  1. 构造函数原型 和 操作符重载 看参考。
  2. 获取时间间隔的时钟周期数 方法
    duration 类还提供了获取时间间隔的时钟周期数的方法 count (),函数原型:constexpr rep count() const;

使用示例:

#include <chrono>
#include <iostream>
using namespace std;
int main()
{
    chrono::hours h(1);                          // 一小时 :时钟周期为 1 小时,共有 1 个时钟周期
    chrono::milliseconds ms{ 3 };                // 3 毫秒 :时钟周期为 1 毫秒,共有 3 个时钟周期
    chrono::duration<int, ratio<1000>> ks(3);    // 3000 秒

    // chrono::duration<int, ratio<1000>> d3(3.5);  // error,指明了int还用浮点,错了。应当改为double
    chrono::duration<double> dd(6.6);               // 6.6 秒

    // 使用小数表示时钟周期的次数
    chrono::duration<double, std::ratio<1, 30>> hz(3.5);  //1/30*3.5 秒 :时钟周期为 1/30 秒,共有 3.5 个时钟周期
}

由于在 duration 类内部做了操作符重载,因此时间间隔之间可以直接进行算术运算。

注意事项:duration 的加减运算有一定的规则,当两个 duration 时钟周期不相同的时候,会先统一成一种时钟,然后再进行算术运算,具体看参考。

1.2 时间点time point

// 定义于头文件 <chrono>
template<
    class Clock,   //此时间点在此时钟上计量
    class Duration = typename Clock::duration  //用于计量从纪元起时间的 std::chrono::duration 类型
> class time_point;
  1. 构造函数原型 和 操作符重载 看参考
  2. 获取新纪元到现在的时间间隔 方法
    time_since_epoch() 函数:用来获得 1970 年 1 月 1 日到 time_point 对象中记录的时间经过的时间间隔

使用示例:
跟下面 钟 一起说。

1.3 时钟clocks

chrono 库中提供了获取当前的系统时间的时钟类,包含的时钟一共有三种:

  • system_clock:系统的时钟,系统的时钟可以修改,甚至可以网络对时,因此使用系统时间计算时间差可能不准。
  • steady_clock:是固定的时钟,相当于秒表。开始计时后,时间只会增长并且不能修改,适合用于记录程序耗时
  • high_resolution_clock:和时钟类 steady_clock 是等价的(是它的别名)。

在使用chrono提供的时钟类的时候,不需要创建类对象,直接调用类的静态方法就可以得到想要的时间了

1.3.1 system_clock

时钟类 system_clock 是一个系统范围的实时时钟。system_clock 提供了对当前时间点 time_point 的访问,将得到时间点转换为 time_t 类型的时间对象,就可以基于这个时间对象获取到当前的时间信息了。

  • rep:时钟周期次数是通过整形来记录的 long long
  • period:一个时钟周期是 100 纳秒 ratio<1, 10’000’000>
    (源码看参考)

三个静态成员函数

  1. now()
// 返回表示当前时间的时间点
static std::chrono::time_point<std::chrono::system_clock> now() noexcept;
  1. to_time_t( const time_point& t )
// 将 time_point 时间点类型转换为 std::time_t 类型 :
static std::time_t to_time_t( const time_point& t ) noexcept;
  1. from_time_t( std::time_t t )
// 将 std::time_t 类型转换为 time_point 时间点类型 :
static std::chrono::system_clock::time_point from_time_t( std::time_t t ) noexcept;

其中二三函数就是跟 Linux的time_t相联系的地方。

使用方法:

#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
    // 新纪元1970.1.1时间
    system_clock::time_point epoch;

    duration<int, ratio<60*60*24>> day(1);
    // 新纪元1970.1.1时间 + 1天
    system_clock::time_point ppt(day);

    using dday = duration<int, ratio<60 * 60 * 24>>;
    // 新纪元1970.1.1时间 + 10天
    time_point<system_clock, dday> t(dday(10));

    // 系统当前时间
    system_clock::time_point today = system_clock::now();
    
    // 转换为time_t时间类型
    time_t tm = system_clock::to_time_t(today);
    cout << "今天的日期是:    " << ctime(&tm);

    time_t tm1 = system_clock::to_time_t(today+day);
    cout << "明天的日期是:    " << ctime(&tm1);

    time_t tm2 = system_clock::to_time_t(epoch);
    cout << "新纪元时间:      " << ctime(&tm2);

    time_t tm3 = system_clock::to_time_t(ppt);
    cout << "新纪元时间+1天:  " << ctime(&tm3);

    time_t tm4 = system_clock::to_time_t(t);
    cout << "新纪元时间+10天: " << ctime(&tm4);
}

1.3.2 steady_clock

如果我们通过时钟不是为了获取当前的系统时间,而是进行程序耗时的时长,此时使用 syetem_clock 就不合适了,因为这个时间可以跟随系统的设置发生变化。
在 C++11 中提供的时钟类 steady_clock 相当于秒表,只要启动就会进行时间的累加,并且不能被修改,非常适合于进行耗时的统计。

  • rep:时钟周期次数是通过整形来记录的 long long
  • period:一个时钟周期是 1 纳秒 nano
    (源码看参考)

一个静态成员函数

  1. now()
// 返回表示当前时间的时间点
static std::chrono::time_point<std::chrono::steady_clock> now() noexcept;

假设要测试某一段程序的执行效率,可以计算它执行期间消耗的总时长

#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
    // 获取开始时间点
    steady_clock::time_point start = steady_clock::now();
    // 执行业务流程
    cout << "print 1000 stars ...." << endl;
    for (int i = 0; i < 1000; ++i)
    {
        cout << "*";
    }
    cout << endl;
    // 获取结束时间点
    steady_clock::time_point last = steady_clock::now();
    // 计算差值
    auto dt = last - start;
    cout << "总共耗时: " << dt.count() << "纳秒" << endl;
}

1.4 转换函数

过程中要对时钟周期和周期次数进行修改,且不能直接用重载的操作符处理,就需要用到转换函数

1.4.1 duration_cast

作用是:通过这个函数可以对 duration 类对象内部的时钟周期 Period,和周期次数的类型 Rep 进行修改。

以下条件都可以隐式处理,若不满足,那么就需要使用 duration_cast 进行显示转换。

  1. 如果是对时钟周期进行转换:源时钟周期必须能够整除目的时钟周期(比如:小时到分钟)。
  2. 如果是对时钟周期次数的类型进行转换:低等类型默认可以向高等类型进行转换(比如:int 转 double)。
  3. 如果时钟周期和时钟周期次数类型都变了,根据第二点进行推导(也就是看时间周期次数类型)。

示例说明:

#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
void f()
{
    cout << "print 1000 stars ...." << endl;
    for (int i = 0; i < 1000; ++i)
    {
        cout << "*";
    }
    cout << endl;
}

int main()
{
    auto t1 = steady_clock::now();    // steady_clock的周期是1 纳秒
    f();
    auto t2 = steady_clock::now();

    // 整数时长:时钟周期纳秒转毫秒,要求 duration_cast
    auto int_ms = duration_cast<chrono::milliseconds>(t2 - t1);

    // 小数时长:不要求 duration_cast
    duration<double, ratio<1, 1000>> fp_ms = t2 - t1;

    cout << "f() took " << fp_ms.count() << " ms, "
        << "or " << int_ms.count() << " whole milliseconds\n";
}

1.4.2 time_point_cast

作用是:对时间点进行转换。因为不同的时间点对象内部的时钟周期 Period,和周期次数的类型 Rep 可能也是不同的,一般情况下它们之间可以进行隐式类型转换,也可以通过该函数显示的进行转换。

示例说明

#include <chrono>
#include <iostream>
using namespace std;

using Clock = chrono::high_resolution_clock;
using Ms = chrono::milliseconds;
using Sec = chrono::seconds;
template<class Duration>
using TimePoint = chrono::time_point<Clock, Duration>;

void print_ms(const TimePoint<Ms>& time_point)
{
    std::cout << time_point.time_since_epoch().count() << " ms\n";
}

int main()
{
    TimePoint<Sec> time_point_sec(Sec(6));
    // 无精度损失, 可以进行隐式类型转换
    TimePoint<Ms> time_point_ms(time_point_sec);    //将time_point_sec的周期从秒改为微秒
    print_ms(time_point_ms);    // 6000 ms

    time_point_ms = TimePoint<Ms>(Ms(6789));
    // error,会损失精度,不允许进行隐式的类型转换
    TimePoint<Sec> sec(time_point_ms);

    // 显示类型转换,会损失精度。6789 truncated to 6000
    time_point_sec = std::chrono::time_point_cast<Sec>(time_point_ms);
    print_ms(time_point_sec); // 6000 ms
}

联系实际问题解决

uint64_t getTimeHost(void)函数需要获取本地时间,并返回

{
  //创建system_clock类型的时间点  t,并通过now()获取当前时间的时间点,将其赋值给t。
  std::chrono::system_clock::time_point t = std::chrono::system_clock::now();

  //创建system_clock类型的时间间隔 t_s。并通过time_since_epoch获取从1970到时间点 t 的 时间间隔
  //注 system_clock类型的:rep时钟周期次数是整形来记录的 long long
  //                    :period时钟周期是 100 纳秒 ratio<1, 10'000'000>
  std::chrono::system_clock::duration t_s = t.time_since_epoch();

 //创建时间间隔t_us,并将  经过转换处理时间周期的t_s  赋值给t_us
 //通过转换函数duration_cast,将system_clock类型的时间间隔 t_s:
 //                                       rep时钟周期次数类型:uint64_t
 //                                       period时钟周期:100 纳秒  ratio<1, 10'000'000>
 //转换为
 //                                       rep时钟周期次数类型:uint64_t
 //                                       period时钟周期:1 微秒  ratio<1, 1'000'000>  
 //将处理好的t_us赋值给t_s。(因为100纳秒转1微秒,有精度丢失,要显式转)。
  std::chrono::duration<uint64_t, std::ratio<1l, 1000000l>> t_us = 
    std::chrono::duration_cast<std::chrono::duration<uint64_t, std::ratio<1l, 1000000l>>>(t_s);
  
  //通过count()返回时间间隔t_us的周期次数(单位为uint64_t)
  return t_us.count();
}

2 Linux时间相关

参考:Linux时间相关函数
参考:c++ 时间类型详解 time_t
开发中经常会用到时间,针对不同应用,对时间的需求有所不同:

  • 时间精度(计时等,秒级、毫秒级、微秒级、纳秒级);
  • 时间格式(RTC显示等,年、月、日、时、分、秒的常用不同排列组合);
  • 时间计时(周期报文的发送等);
  • 时间点(闹钟等);
  • 时间段(进程运行时间统计等);

世界协调时(UTC:CoordinatdeUniversalTime)。UTC的表示方式为:年(y)、月(m)、日(d)、时(h)、分(min)、秒(s),均用数字表示。

linux下存储时间常见的有两种存储方式:

  1. 从1970年到现在经过了多少秒。
    time_t ,或者准确到微妙的struct timeval
  2. 用一个结构来分别存储年月日时分秒的。
    tm

其余的如到毫秒的timeb,到纳秒的timespec,整型表示的clockid_t…都不做详细说明,可以看参考文章。
下面主要介绍time_ttimevaltm

2.1 结构体

2.1.1 time_ttimeval

time_t 就是长整型long int。(此外还有整型等类型表示的)
timeval精确到微妙,用结构体表示

struct timeval
{
    long tv_sec; /*秒*/
    long tv_usec; /*微秒*/
};

2.1.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*/  1+p->tm_mon;
    int tm_year;  /*年, 从1900至今已经多少年*/  1900+ p->tm_year;
    int tm_wday; /*星期,一周中的第几天, 从星期日算起,0-6*/
    int tm_yday; /*从今年1月1日到目前的天数,范围0-365*/
    int tm_isdst; /*日光节约时间的旗标*/
};

注:

  1. 年份是从1900年起
  2. 月份从0开始的,0表示一月,星期也是从0开始的, 0表示星期日,1表示星期一。

2.2 时间函数

2.2.1 time (获取 time_t)

一般跟time_t一起用。
作用:取得从1970年1月1日至今的秒数。
头文件:time.h

//原型
time_t time(time_t *t);

//使用方法
time_t timep;
time(&timep);   //或 timep = time(NULL);

2.2.2 gettimeofday (获取 timeval)

一般跟timeval一起用。
作用:返回当前距离1970年的秒数和微妙数,后面的tz是时区,一般不用。(也常常跟settimeofday一起用)
头文件:sys/time.h

//原型
int gettimeofday(struct timeval *tv, struct timezone *tz);

//使用方法
struct timeval tv;
gettimeofday(&tv,NULL);

2.2.3 difftime (获取 两time_t间)

一般跟time_t一起用。
作用:返回两个时间相差的秒数。

//原型
double difftime(time_t time1, time_t time2);

2.2.4 ctime (time_t 转 字符串)

一般跟time_t一起用。
作用:将time_t转换为真实世界的时间,以字符串显示。

//原型
char *ctime(const time_t *timep);

2.2.5 actime (tm 转 字符串)

一般跟tm一起用。
作用:将tm转换为真实世界的时间,以字符串显示。

//原型
char *asctime(const struct tm* timeptr);

2.2.6 gmtime (time_t 转 tm)

一般跟tmtime_t一起用。
作用:将time_t表示的时间转换为没有经过时区转换的UTC时间,是一个struct tm结构指针。(还有种stuct tm* localtime(const time_t *timep)但是它是经过时区转换的时间)

//原型
struct tm* gmtime(const time_t *timep);

2.2.7 mktime (tm 转 time_t)

一般跟tmtime_t一起用。
作用:将struct tm 结构的时间转换为从1970年至今的秒数

//原型
time_t mktime(struct tm* timeptr);

联系实际问题解决

#include <time.h>

    time_t timep;
    
   //获取time_t类型的当前时间
    time(&timep); 
    
    //用gmtime将time_t类型的时间转换为struct tm类型的时间(没有经过时区转换的UTC时间)
    //然后再用asctime转换为字符串
    printf("%s", asctime(gmtime(&timep)));

}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值