190811更新
我以为搞了这么一篇就一劳永逸了,结果写了个日期处理的题还是有很个小坑,当初没搞清楚。
①修正行文错误,正确版本【mktime与localtime互为反函数】。②补充mktime过程中yday的运算逻辑问题。
①头文件
#include<ctime> //或<time.h>
②两个类型
需要理解的只有2个类型,time_t和tm;
time_t 为长整数。
表示从19xx年开始经过的秒。
在vs2017中测得time_t为typedef定义的_time64_t类型 //corecrt.h
而_time64_t又是typedef定义的__int64类型 //corecrt.h
如果是某些32位系统,则time_t === _time32_t === _int32。
如果没有定义,还可以这样
#ifndef _TIME_T_DEFINED
typedef long time_t; /* 时间值 */
#define _TIME_T_DEFINED /* 避免重复定义 time_t */
#endif
tm是个结构体,包含年月日时分秒。
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Types
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct tm
{
int tm_sec; // seconds after the minute - [0, 60] including leap second
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
};
//来自correct_wtime.h
//最后一个_isdat表示夏令时,国内不用理
time_t 和 strcut tm
我们要做的就是在这两个东西之间根据需要转来转去。
③tm转time_t --- mktime介绍
通常我们取得的日期都是字符串,网上有现成的轮子。
把日期字符串里的值一个个赋予struct tm的成员变量
//一般我们遇到的时间字符串格式各不相同
//有时候是"2001/01/01",有时候是"2001-01-01",按需修改即可
//核心思路是构造m结构体之后,拿它去mktime,算出size_t的秒
//如果不存在时分秒,就令它们为0
time_t StringToDatetime(const string &str)
{
const char *cha = str.c_str(); // 将string转换成char*。
tm tm_; // 定义tm结构体。
int year, month, day, hour, minute, second; // 定义时间的各个int临时变量。
sscanf(cha, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);// 将string存储的日期时间,转换为int临时变量。
tm_.tm_year = year - 1900; // 年,由于tm结构体存储的是从1900年开始的时间,所以tm_year为int临时变量减去1900。
tm_.tm_mon = month - 1; // 月,由于tm结构体的月份存储范围为0-11,所以tm_mon为int临时变量减去1。
tm_.tm_mday = day; // 日。
tm_.tm_hour = hour; // 时。
tm_.tm_min = minute; // 分。
tm_.tm_sec = second; // 秒。
tm_.tm_isdst = 0; // 非夏令时。
time_t t_ = mktime(&tm_); // 将tm结构体转换成time_t格式。
return t_; // 返回值。
}
该算法的核心是struct tm -------> size_t
通过mktime()函数完成。
time_t t_=mktime(&tm_); //获得秒数
注意事项
3.1
mktime获得的秒数是从1970年开始的!
而struct tm存储的年份是从1900年开始的!
所以一切在1970年之前的日期进入该函数,都会报错!
time_t mktime (struct tm * timeptr){};
然后返回-1!
3.2
mktime计算秒数仅采用年月日+时分秒,struct tm中的wday与yday不会纳入计虑范围。
更方便的是,计算mktime秒数的过程中,还会顺便帮你填好wday与yday。
A call to this function automatically adjusts the values of the members of timeptr if they are off-range or -in the case of tm_wday and tm_yday- if they have values that do not match the date described by the other members.
wday表示从sunday开始的星期几[0~6];
yday表示这是一年中的第几天[0~365];
//http://www.cplusplus.com/reference/ctime/mktime/
190811更新。再次加重声明,【mktime计算秒数用且仅用 "年月日+时分秒" 】。
这意味着,如果你的输入格式是【2019-01-03】,没有时分秒信息。
你需要自己手动把tm_.tm_hour,tm_.tm_min,tm_.tm_sec,都设为0。
不然的话,声明一个struct tm时,时分秒一般默认被置为负数。
我不知道为什么负数是合法的,明明这个数据的合法范围应该是[0,24),[0,60),[0,60),但VS能看到确实是负数-858993460。
这样一来,算出来的秒数是不对的,一般情况下返回的秒数为-1。
连带着yday和wday也是不对的。
④日期大小的比较
我研究这个问题一段时间之后,发现貌似用字符串的直接比较是最快的(存疑)。
string str1="2001-01-01";
string str2="2001-01-02";
string str3="2001-02-01";
if(str1<str2) cout<<"str2 is bigger"<<endl;
if(str2<str3) cout<<"str3 is bigger"<<endl;
另一种思路是比较秒数(仅限1970年之后)
string str1="2001-01-01";
string str2="2001-01-02";
time_t t1=StringToDatetime(str1);
time_t t2=StringToDatetime(str2);
//在vs2017中测得time_t不支持<运算符
//我们有2种方法
//方法1
//借助-运算符
if(t1-t2 <0) cout<<"t1 is former"<<endl;
//方法2
//借助difftime函数
//time_t difftime(time_t t1,time_t t2) 返回两个秒数之间的差值
//大部分情况下等价于-运算符
time_t t3 = difftime(t1,t2); //一般等价于t1-t2
cout<<t3<<endl;
if(t3<0) cout<<"t1 is former"<<endl;
特殊情况在哪呢?
前文提过,我们获取time_t一般是用mktime(&tm);
而时间早于1970年会得到-1;
显然difftime无法处理负数
time_t t1 = mktime(&tm_);
time_t t2 = -1;
time_t t3 = difftime(t1, t2);
time_t t4 = t1 - t2;
cout << (t3 == t4)<<endl; //输出0,说明不相等
-------------
time_t t1 = mktime(&tm_);
time_t t2 = 1;
time_t t3 = difftime(t1, t2);
time_t t4 = t1 - t2;
cout << (t3 == t4)<<endl; //输出1,说明相等
综上所述,要比较日期
最快是字符串直接比较字典序大小(前提是格式相同)
最稳是转成秒数之后用 - 减法运算符来比(1970年之后)
⑤time_t转tm - gmtime与localtime介绍
gmtime与localtime
两者都将time_t表示的秒数还原成一个struct tm结构体。
两者的区别在于是否修正时区。
//转为格林尼治标准时间
struct tm * gmtime (const time_t * timer);
//Convert time_t to tm as UTC time
//转为本地时区的时间
struct tm * localtime (const time_t * timer);
//Convert time_t to tm as local time
cplusplus.com上写道,mktime是localtime的反过程。【感谢评论区指出】
This function performs the reverse translation that localtime does.
一般仅用localtime与mktime,可以达成无损的循环。
struct tm *tm_ptr;
time_t t_;
t_= mktime(tm_ptr);
tm_ptr = localtime(&t_); //传入的参数为time_t *类型,返回值是struct tm *
//如此可以无损循环
⑥time_t 转 string
一般方法就不用介绍了吧,转成struct tm之后,
分别取出tm_.year .month ......按要求拼接字符串即可。
这里介绍一个函数ctime(time_t*),和文件头同名
char* ctime (const time_t * timer);
//Convert time_t value to string
ctime()的输出遵从如下格式:
Www Mmm dd hh:mm:ss yyyyWed Feb 13 16:06:10 2013
等价函数为asctime(struct tm *timerptr)
不过用内置函数,就只有这个输出格式。
一般没什么用,我们日常还是习惯看【yyyy-mm-dd hh:mm:ss】。
所以还是自己拼接字符串比较好。
参考:
部分代码不能编译的有删改。
//https://www.cplusplus.com/reference/ctime/