总论点:掌握这7个函数可以解决一般linux c 的时间编程问题。
1、 7个重要函数说明对比
a、int gettimeofday(struct timeval *tv, NULL) 相比time_t time(time_t *timep)可以获得微妙,都可以获得当前时间的总秒数。
time_t mktime(struct tm *tmp) 可以获得struct tm *tmp结构体指针的总秒数。
b、asctime_r, ctime_r, gmtime_r, localtime_r这4个*_r函数符合多线程安全要求,而类localtime不带_r的4个函数建议不要用。
char *asctime_r(const struct tm *tmp, char *buf);
//将结构体tm指针代表的日期时间转换为通用标准的日期时间字符串格式
char *ctime_r(const time_t *timep, char *buf);
//将总秒数指针直接转换为通用标准的日期时间字符串格式
struct tm *gmtime_r(const time_t *timep, struct tm *result);
//通过已知时间总秒数获取对应GMT标准的日期时间的struct tm 指针
struct tm *localtime_r(const time_t *timep, struct tm *result);
//通过已知时间总秒数获取对应UTC标准的日期时间的struct tm 指针
//调用相关函数获取struct tm 指针值后,可以自定义日期时间格式。
2、函数返回值及重要数据结构
函数返回值:
time()、mktime()函数正常返回总秒数,错误返回值为-1.
gettimeofday()return 0 for success, or -1 for failure
剩下4个函数根据返回值类型进行判断即可。
数据结构:
time_t 宏定义的长整型 用来表示总秒数类型;
struct tm 用来存储日期时间的结构体;
struct timeval 用来存储秒、微秒的结构体;
3、如何使用和记忆
除了time_t mktime(&tm)必须需要接收返回值函数外,其它上述函数都可以把获取到的返回值存入函数形参内。
上述所有函数形参都是指针格式,为了避免申请释放空间造成的段错误,建议都申明定义结构体变量,然后用&结构体变量地址方式进行函数调用。
4、除gettimeofday()函数的头文件为<sys/time.h>,上述其它函数头文件均为<time.h>
论述步骤:
简单时间概念:
地球每天的自转是有些不规则的,而且正在缓慢减速。所以,格林尼治时间已经不再被作为标准时间使用(GMT)。现在的标准时间──协调世界时(UTC)──由原子钟提供。
epoch时间点:1970-1-1 0:0:0
1、gettimeofday 函数获取从epoch点到现在的总秒数和总微秒数,比time(time_t timep)经度更高
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
时间和时区相关的俩个结构体:
struct timeval {
time_t tv_sec; /* seconds (秒)*/
suseconds_t tv_usec; /* microseconds(微秒) */
};
struct timezone {
int tz_minuteswest; /* (minutes west of Greenwich),个人理解,此数据项的含义是当地时间以格林威治时间为基准向西算的分钟数。 */
int tz_dsttime; /* (type of DST correction),字面理解是日光节约时间的修正方式 */
};
//gettimeofday函数获取当前时间存于tv结构体中,相应的时区信息则存于tz结构体中
//tz是依赖于系统,有的系统可能存在获取不到值比如linux系统,一般情况用不到,一般设置为NULL
//gettimeofday 相比time(NULL)可以获取到微秒,但没time(NULL)形式简单。
栗子:
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
int main()
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
printf("tv_sec: %d\n", tv.tv_sec);
printf("tv_sec_time_null: %d\n", time(NULL));//通过打印结果可以看到和上面gettimeofday函数获取到的秒数是一样的
printf("tv_usec: %d\n", tv.tv_usec);
printf("tz_minuteswest: %d\n", tz.tz_minuteswest);
printf("tz.tz_dsttime: %d\n", tz.tz_dsttime);
return 0;
}
//打印结果:
//tv_sec: 1555900175
//tv_sec_time_null: 1555900175
//tv_usec: 718336
//tz_minuteswest: 0
//tz.tz_dsttime: 0
2、time_t time(time_t *t); 函数获取从Epoch到当前时间的总秒数
Epoch 时代; 纪元; 时期 指计算时间的开始点,即1970-01-01 00:00:00
mktime() 将时间结构体struct tm tmp的值转化为自epoch到tmp的总秒数
#include <time.h>
time_t time(time_t *t);
time_t mktime(struct tm *tm);
time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
//如果t 并非空指针的话,此函数也会将返回值存到t指针所指的内存。
RETURN VALUE
On success, the value of time in seconds since the Epoch is returned. On error,
((time_t) -1) is returned, and errno is set appropriately.
ERRORS
EFAULT t points outside your accessible address space. 默认指针t空间不能访问。
//成功返回秒数,错误则返回(time_t) -1),错误原因存于errno中
#include <errno.h>
要获取errno的值需加载errno.h头文件。Linux中系统调用的错误都存储于 errno中,
errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误。
栗子:
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>
int main()
{
time_t seconds1;
time_t seconds2;
struct tm tmp;
seconds1 = time((time_t *)&seconds2);//从打印结果可以看到seconds1,seconds2可以通过time()函数把值返回到俩个不同的地址
if(seconds1 != -1) {
printf("seconds1=%d\n", seconds1);
printf("seconds2=%d\n", seconds2);
}else
printf("Error:get seconds Error,errno is %d\n", errno);
localtime_r(&seconds2, &tmp);
time_t seconds3=mktime(&tmp);
printf("struct tm tmp 被mktime转换成总秒数seconds3=%d\n", seconds3);
return 0;
}
/*打印结果:
gcc tmp.c -o a -g && ./a
seconds1=1555952686
seconds2=1555952686
struct tm tmp 被mktime转换成总秒数seconds3=1555952686
*/
3、localtime_r() localtime() 通过秒数值取得时间和日期结构体
The localtime() return value points to a statically allocated
struct which might be overwritten by subsequent calls to any of the date and time functions.
The localtime_r() function does the same, but stores the data in a user-supplied struct.
localtime()函数返回一个指向静态分配的指针,如果再次使用该函数返回的值会覆盖静态指针保存的值。
localtime_r()函数也可以做同样的工作,但是数据存储到用户提供的结构体中。
#include <time.h>
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
/*这俩个函数将time函数获取的总秒数拆分成日期时间的结构体变量并存入struct tm结构体,然后返回该结构体指针。
区别是localtime函数不是线程安全的,多线程应用里面,应该用localtime_r函数。
另外localtime()每次返回的值都存入1个静态指针,多次调用时,存储的最后一次的值;
而localtime()_r会给用户定义的struct tm *result的内存地址拷贝一份内容,只要用户指针不改变值也不会变。
*/
3小节结论:通过原理和实际测试得出结论:在用秒数值取得时间和日期结构体时,
用 struct tm *localtime_r(const time_t *timep, &(struct tm result)) 函数形式最安全、简单;
获取现在的日期和时间最优方式:struct tm result; time(time_t timep); localtime_r(&timep, &result);
struct tm {
int tm_sec; /* seconds */ //int tm_sec 代表目前秒数,正常范围为0-59,但允许至61秒
int tm_min; /* minutes */ //int tm_min 代表目前分数,范围0-59
int tm_hour; /* hours */ //int tm_hour 从午夜算起的时数,范围为0-23
int tm_mday; /* day of the month */ //int tm_mday 目前月份的日数,范围01-31
int tm_mon; /* month */ //int tm_mon 代表目前月份,从一月算起,范围从0-11
int tm_year; /* year */ //int tm_year 从1900 年算起至今的年数
int tm_wday; /* day of the week */ //int tm_wday 一星期的日数,从星期一算起,范围为0-6
int tm_yday; /* day in the year */ //int tm_yday 从今年1月1日算起至今的天数,范围为0-365
int tm_isdst; /* daylight saving time */ //int tm_isdst 日光节约时间的旗标
};
栗子:
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
int main()
{
time_t timep;
struct tm *p0,*p, *p1,*p2,*p3,*p4,*p5,*p6;
struct tm p_0, p_1, p_3;
p2=(struct tm *)calloc(1, sizeof(struct tm));
p3=(struct tm *)calloc(1, sizeof(struct tm));
p4=(struct tm *)calloc(1, sizeof(struct tm));
time(&timep);
p = localtime(&timep);
localtime_r(&timep, p3);
localtime_r(&timep, &p_0);
sleep(2);
time(&timep);
p0 = localtime(&timep);
//1、通过打印p和p0值后发现值一样,可以看到localtime()调用俩次之后俩个变量却指向同一个地址,
//2、印证了上面原理说的localtime()返回值是指向一个静态的struct tm 指针;另外帮助文档说线程用该函数也是不安全的。
//3、通过上面2条得出结论:获得当前日期时间的函数用localtime_r()更安全,建议不用localtime()
localtime_r(&timep, p4);
localtime_r(&timep, &p_1);
//紧邻的上面俩条语句可以看出承接返回值的变量用结构体变量更简单;
//如果用结构体变量指针需要申请释放内存,而用结构体变量直接用该变量地址即可,无需申请内存。
printf("0 %d-%d-%d %d:%d:%d\n", (1900 + p->tm_year), ( 1 + p->tm_mon), p->tm_mday,
(p->tm_hour), p->tm_min, p->tm_sec);
printf("0 %d-%d-%d %d:%d:%d\n", (1900 + p0->tm_year), ( 1 + p0->tm_mon), p0->tm_mday,
p0->tm_hour, p0->tm_min, p0->tm_sec);
printf("0-1 %d-%d-%d %d:%d:%d\n", (1900 + p3->tm_year), ( 1 + p3->tm_mon), p3->tm_mday,
(p3->tm_hour), p3->tm_min, p3->tm_sec);
printf("0-1 %d-%d-%d %d:%d:%d\n", (1900 + p4->tm_year), ( 1 + p4->tm_mon), p4->tm_mday,
(p4->tm_hour), p4->tm_min, p4->tm_sec);
printf("0-2 %d-%d-%d %d:%d:%d\n", (1900 + p_0.tm_year), ( 1 + p_0.tm_mon), p_0.tm_mday,
(p_0.tm_hour), p_0.tm_min, p_0.tm_sec);
printf("0-2 %d-%d-%d %d:%d:%d\n", (1900 + p_1.tm_year), ( 1 + p_1.tm_mon), p_1.tm_mday,
(p_1.tm_hour), p_1.tm_min, p_1.tm_sec);
//localtime_r(&timep, p2);
p1=localtime_r(&timep, p2);
p_3=*(localtime(&timep));
printf("1 %d-%d-%d %d:%d:%d\n", (1900 + p1->tm_year), ( 1 + p1->tm_mon), p1->tm_mday,
(p1->tm_hour), p1->tm_min, p1->tm_sec);
printf("2 %d-%d-%d %d:%d:%d\n", (1900 + p2->tm_year), ( 1 + p2->tm_mon), p2->tm_mday,
p2->tm_hour, p2->tm_min, p2->tm_sec);
printf("3 %d-%d-%d %d:%d:%d\n", (1900 + p_3.tm_year), ( 1 + p_3.tm_mon), p_3.tm_mday,
(p_3.tm_hour), p_3.tm_min, p_3.tm_sec);
sleep(2);
time(&timep);
p5=localtime_r(&timep, p6);
//实际测试指针p5 p6,打印p5值不报段错误,但如果打印p6值报段错误
//另外资料也没看到类似p5返回值的这种用法,较多的是localtime_r(&timep, &tm)形式
printf("4 %d-%d-%d %d:%d:%d\n", (1900 + p5->tm_year), ( 1 + p5->tm_mon), p5->tm_mday,
p5->tm_hour, p5->tm_min, p5->tm_sec);
printf("1 %d-%d-%d %d:%d:%d\n", (1900 + p1->tm_year), ( 1 + p1->tm_mon), p1->tm_mday,
(p1->tm_hour), p1->tm_min, p1->tm_sec);
//通过上面打印结果看到,localtime_r()的函数返回值p5、p1不会因多次调用localtime_r()而一样。
if(p2 != NULL) free(p2);
if(p3 != NULL) free(p3);
if(p4 != NULL) free(p4);
return 0;
}
/*打印结果:
gcc tmp.c -o a -g && ./a
0 2019-4-23 10:12:2
0 2019-4-23 10:12:2
0-1 2019-4-23 10:12:0
0-1 2019-4-23 10:12:2
0-2 2019-4-23 10:12:0
0-2 2019-4-23 10:12:2
1 2019-4-23 10:12:2
2 2019-4-23 10:12:2
3 2019-4-23 10:12:2
4 2019-4-23 10:12:4
1 2019-4-23 10:12:2
*/
4、类似localtime_r的其他*_r函数介绍
#include <time.h>
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
/**gmtime是把从Epoch到现在的秒数转换成格林威治(GMT)格式的日期时间并返回struct tm的指针的函数。
gmtime_r()和gmtime()功能一样,但将结果存储到用户提供的result变量中,gmtime_r多线程运行是安全
该函数的应用是计算时区时间。步骤先求得GMT的时间,然后通过东/西几个时区(+/-几个小时)求得指定时区时间。
**/
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);
/**asctime 将日期时间的结构体形式换为固定日期时间格式的字符串格式返回。
asctime_r()和asctime()功能一样,但将结果存储到用户提供的buf变量中,asctime_r多线程运行是安全**/
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
/**ctime()将参数timep所指的总秒数转换成日期时间的字符串形式(省去tm结构体),返回字符指针。
ctime_r()和ctime()功能一样,但将结果存储到用户提供的buf变量中,ctime_r多线程运行是安全**/
栗子:
//以字符串形式打印当前时间的日期时间不要用strunct tm结构体,按计算机标准的UTC时间(世界通用的UTC时间)格式
//以字符串形式计算并打印东八区(北京时间)的日期时间,按计算机标准的UTC时间(世界通用的UTC时间)格式
//以字符串形式打印当前时间的日期时间要用astime_r函数,按计算机标准的UTC时间(世界通用的UTC时间)格式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main()
{
struct tm tmp;
time_t timep;
char da_time[128] = {0};
if(time(&timep) == -1)
exit(-1);
ctime_r(&timep, da_time);
printf("打印当前时间的日期时间不要用strunct tm结构体,按计算机标准的UTC时间(世界通用的UTC时间)格式: %s\n", da_time);
gmtime_r(&timep, &tmp);
asctime_r(&tmp, da_time);
//下面为判断东八区可能出现日期的各种情况
printf("格林尼治地区的日期时间现在是: %s\n", da_time);
if (tmp.tm_hour <16)
tmp.tm_hour = tmp.tm_hour + 8;
else {
{
if (tmp.tm_mday <31)
{
tmp.tm_mday = tmp.tm_mday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
tmp.tm_wday = tmp.tm_wday + 1;
}else{
tmp.tm_mday = 1;
tmp.tm_wday = tmp.tm_wday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
if (tmp.tm_mon == 11)
{
tmp.tm_mon =1;
tmp.tm_year =1+tmp.tm_year;
}else
tmp.tm_mon = tmp.tm_mon + 1;
}
}else if (tmp.tm_mon==2)
{
if ((tmp.tm_year%4))
{
if (tmp.tm_mday <29)
{
tmp.tm_mday = tmp.tm_mday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
tmp.tm_wday = tmp.tm_wday + 1;
}else{
tmp.tm_mday = 1;
tmp.tm_wday = tmp.tm_wday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
if (tmp.tm_mon == 11)
{
tmp.tm_mon =1;
tmp.tm_year =1+tmp.tm_year;
}else
tmp.tm_mon = tmp.tm_mon + 1;
}
}else{
if (tmp.tm_mday <28)
{
tmp.tm_mday = tmp.tm_mday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
tmp.tm_mday = 1;
tmp.tm_wday = tmp.tm_wday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
if (tmp.tm_mon == 11)
{
}else
tmp.tm_mon = tmp.tm_mon + 1;
}
}
}else{
if (tmp.tm_mday <30)
{
tmp.tm_mday = tmp.tm_mday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
tmp.tm_wday = tmp.tm_wday + 1;
}else{
tmp.tm_mday = 1;
tmp.tm_wday = tmp.tm_wday + 1;
tmp.tm_hour = tmp.tm_hour + 8 - 24;
if (tmp.tm_mon == 11)
{
tmp.tm_mon =1;
tmp.tm_year =1+tmp.tm_year;
}else
tmp.tm_mon = tmp.tm_mon + 1;
}
}
}
asctime_r(&tmp, da_time);
printf("以字符串形式计算并打印东八区(北京时间)的日期时间,按计算机标准的UTC时间(世界通用的UTC时间)格式: %s\n", da_time);
sleep(2);
time(&timep);
localtime_r(&timep, &tmp);
asctime_r(&tmp, da_time);
printf("以字符串形式打印当前时间的日期时间要用astime_r函数,按计算机标准的UTC时间(世界通用的UTC时间)格式: %s\n", da_time);
return 0;
}
/*打印结果:
gcc tmp.c -o a -g && ./a
打印当前时间的日期时间不要用strunct tm结构体,按计算机标准的UTC时间(世界通用的UTC时间)格式: Tue Apr 23 01:00:01 2019
格林尼治地区的日期时间现在是: Mon Apr 22 17:00:01 2019
以字符串形式计算并打印东八区(北京时间)的日期时间,按计算机标准的UTC时间(世界通用的UTC时间)格式: Tue Apr 23 01:00:01 2019
以字符串形式打印当前时间的日期时间要用astime_r函数,按计算机标准的UTC时间(世界通用的UTC时间)格式: Tue Apr 23 01:00:03 2019
*/
本人拙见、欢迎指正和批评。
另外感谢参考链接文章作者的优秀总结。
参考链接https://blog.csdn.net/zwzhzhzhzhzh/article/details/79004484;