UNIX 时间概念
在 UNIX 系统中,将从 1970 年 1 月 1 日开始经过的秒数用一个整数存放,这种高效简洁的时间表示方法被称为 Unix 时间戳,向左和向右偏移都可以得到更早或者更后的时间。实际开发中,对日期和时间的操作非常多,基本无处不在。
时间的概念:
-
本地时间(locale time)
本地时间是在纪元时间(UTC)时间上加上时区。
-
格林威治时间(Greenwich Mean Time GMT)
世界时是最早的时间标准。在1884年,国际上将 1s 确定为全年内每日平均长度的 1/8.64×104 。以此标准形成的时间系统,称为世界时,即UT1。1972 年国际上开始使用国际原子时标,从那以后,经过格林威治老天文台本初子午线的时间便被称为世界时,即UT2,或称格林威治时间(GMT),是对地球转速周期性差异进行校正后的世界时。
-
世界协调时间 (Universal Time Coordinated UTC)
世界协调时(也有称为纪元时间)是以地球自转为基础的时间标准。由于地球自转速度并不均匀,并非每天都是精确的 86400 原子 s,因而导致了自转时间与世界时之间存在 18 个月有 1s 的误差。为纠正这种误差,国际地球自转研究所根据地球自转的实际情况对格林威治时间进行增减闰 s 的调整,与国际度量衡局时间所联合向全世界发布标准时间,这就是所谓的世界协调时。UTC 的表示方式为:年(y)、月(m)、日(d)、时(h)、分(min)、秒(s),均用数字表示。
GPS 系统中有两种时间区分,一为UTC,另一为LT(地方时)。两者的区别为时区不同,UTC就是 0 时区的时间,地方时为本地时间,如北京为早上八点(东八区),UTC 时间就为零点,时间比北京时晚八小时,以此计算即可通过上面的了解,我们可以认为格林威治时间就是世界协调时间(GMT = UTC),格林威治时间和 UTC 时间均用秒数来计算的。
而在我们平时工作当中看到的计算机日志里面写的时间大多数是用UTC时间来计算的,那么我们该怎么将UTC时间转化为本地时间便于查看日志,那么在作程序开发时又该怎么将本地时间转化为UTC时间呢?
可以使用 Linux 命令 date 来进行本地时间和 local 时间的转化。大家都知道,在计算机中看到的 UTC 时间都是从(1970 年 01 月 01 日 0:00:00)开始计算秒数的。所看到的UTC时间那就是从1970年这个时间点起到具体时间共有多少秒。
原文:https://www.cnblogs.com/LubinLew/p/Knownledge_Time.html
参考:http://www.stjarnhimlen.se/comp/time.html
C 语言的时间操作函数
time_t 类型
C 语言中,用 time_t 表示一个时间类型,实际 long int 类型的的别名。表示一个日历时间,是从 1970 年 1 月1日 0 时 0 分 0 秒到现在的秒数,也称为时间戳。
time 函数
time 函数的用途是返回现在的时间戳,也就是从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的秒数。time 函数是 C 语言标准库中的函数,在 time.h 中声明,函数原型如下:
time_t time(time_t* t);
time_t 函数有两种调用方法:
time_t tnow;
tnow = time(NULL);
//或者
time(&tnow)
这两种效果完全相同,如下实例:
int main()
{
time_t tnow;
tnow = time(NULL);
cout << tnow << endl;
time(&tnow);
cout << tnow << endl;
return 0;
}
运行结果:
$ ./test
1616748809
1616748809
struct tm 结构体
time_t 只是一个长整型,仅仅表示一个时间戳,不符合我们的使用习惯,需要转换成可以方便表示时间信息的结构体,这正是 struct tm
结构体的作用。struct time 结构体在 time.h 中声明,如下:
struct tm
{
int tm_sec; // Seconds. 秒:取值区间为[0,59]
int tm_min; // Minutes. 分:取值区间为[0,59]
int tm_hour; // Hours. 时:取值区间为[0,23]
int tm_mday; // Day. 日期:一个月中的日期:取值区间为[1,31]
int tm_mon; // Month. 月份:(从一月开始,0代表一月),取值区间为[0,11]
int tm_year; // Year - 1900. 年份:其值等于实际年份减去1900
int tm_wday; // Day of week. 星期:取值区间为[0,6],0代表星期天,1代表星期一
int tm_yday; // Days in year. 从每年的1月1日开始的天数:取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推
int tm_isdst; /* DST. 夏令时标识符,该字段意义不大,很少用到
};
此结构体中定义了年、月、日、时、分、秒、星期、当年中的某一天、夏令时。struct tm 结构体可以很方便的显示出一个时间的详细信息。
localtime 函数
localtime 函数用于将 time_t 表示的时间戳转换为 struct tm 结构体表示的时间,函数返回 struct tm 结构体的地址。
struct tm 结构体包含了时间的各要素,但还一定是我们习惯的时间表达方式,可以用格式化输出 printf 等函数,把 struct tm 结构体转换为我们想要的结果。
int main()
{
time_t tnow;
tnow = time(NULL);
printf("tnow=%lu\n", tnow); // 输出时间戳
struct tm* sttm = localtime(&tnow);
printf("%04u-%02u-%02u %02u:%02u:%02u\n", sttm->tm_year + 1900, sttm->tm_mon + 1, sttm->tm_mday, sttm->tm_hour, sttm->tm_min, sttm->tm_sec);
return 0;
}
运行结果:
$ ./test
tnow=1616749813
2021-03-26 17:10:13
在实际开发中,通常需要获取十分钟之后的时间,方法是采用 time 函数得到一个整数后,再加上 10*60 秒,再用 localtime 函数转换为结构体即可。
mktime 函数
mktime 函数的功能与 localtime 函数相反。所以,mktime 函数用于把 struct tm 表示的时间转换为 time_t 表示的时间,其函数声明如下:
time_t mktime(struct tm* tm);
用法如下实例:
int main()
{
struct tm sttm;
bzero(&sttm, sizeof(sttm));
sttm.tm_year = 2021 - 1900;
sttm.tm_mon = 3 - 1;
sttm.tm_mday = 25;
sttm.tm_hour = 17;
sttm.tm_min = 15;
sttm.tm_sec = 45;
sttm.tm_isdst = 0;
time_t tnow = mktime(&sttm);
cout << tnow << endl;
return 0;
}
运行结果:
$ ./test
1616663745
gettimeofday 函数
gettimeofday 是获得 1970 年 1 月 1 日 到当前时间的秒和微秒,微秒是指当前秒已逝去的微秒数,可以用于程序的计时。调用 gettimeofday 函数需要包含 sys/time.h 头文件。gettimeofday 函数在 windows 平台中不能使用。
在 Linux 中,精确到微秒的 timeval 结构体定义如下:
struct timeval
{
long tv_sec; // 1970年1月1日到现在的秒。
long tv_usec; // 当前秒的微妙,即百万分之一秒。
};
gettimeofday 函数中使用到了 timezone 结构体,其在 sys/time.h 文件中定义:
struct timezone
{
int tz_minuteswest; // 和UTC(格林威治时间)差了多少分钟。
int tz_dsttime; // type of DST correction,修正参数据,忽略
};
gettimeofday 函数的声明如下:
int gettimeofday(struct timeval* tv, struct timezone* tz )
参数:tv 用来存放当前的时间,tz 用来存放当地时区的信息,tz 不关心可以为 NULL。
返回值:执行成功返回 0,失败后返回 -1。
实例程序如下:
#include <sys/time.h>
int main()
{
struct timeval begin, end;
gettimeofday(&begin, NULL);
printf("begin time(0)=%d,tv_sec=%d,tv_usec=%d\n", time(0), begin.tv_sec, begin.tv_usec);
sleep(2);
usleep(100000);
gettimeofday(&end, 0);
printf("end time(0)=%d,tv_sec=%d,tv_usec=%d\n", time(0), end.tv_sec, end.tv_usec);
printf("计时过去了%d微秒\n", (end.tv_sec - begin.tv_sec) * 1000000 + (end.tv_usec - begin.tv_usec));
}
运行结果:
$ ./test
begin time(0)=1616768189,tv_sec=1616768189,tv_usec=671573
end time(0)=1616768191,tv_sec=1616768191,tv_usec=771734
计时过去了2100161微秒
从运行结果看出实际时间大于十分之一秒?因为程序执行需要时间,虽然这个时间很短,在千分之一秒内。
asctime 函数
asctime 函数将结构体中的信息转换为真实世界中的时间(无时区转换),以字符串的形式显示。其定义如下:
#include <time.h>
char *asctime(const struct tm* timeptr);
程序实例如下:
#include <time.h>
int main()
{
time_t timep;
time(&timep);
printf("%s", asctime(gmtime(&timep)));
return 0;
}
运行结果:
$ ./test
Fri Mar 26 14:23:21 2021
在上面的程序中,首先使用 gmtime 将 time_t 类型的时间转换为 struct tm 类型的时间,按没有经过时区转换的 UTC 时间,再用 asctime 转换为我们常见的格式,如:Fri Jan 11 17:25:24 2008。
ctime 函数
ctime 函数将一个 time_t 的时间戳转换为真实世界的时间(以字符串显示),和 asctime 不同在于传入的参数不一样。
char *ctime(const time_t* timep);
实例程序如下:
int main()
{
time_t timep;
time(&timep);
printf("%s", ctime(&timep));
return 0;
}
运行结果:
$ ./test
Fri Mar 26 22:28:58 2021
可以看到运行结果和电脑时间一致,表示已经经过时区转换了。
gmtime 函数
gmtime 函数将一个 time_t 的时间戳转换为没有经过时区转换的 UTC 时间,是一个 struct tm 结构体指针:
struct tm* gmtime(const time_t* timep);
实例程序如下:
int main()
{
char* wday[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
time_t timep;
time(&timep);
struct tm *p = gmtime(&timep);
printf("%d/%d/%d ", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday);
printf("%s %d:%d:%d\n", wday[p->tm_wday], p->tm_hour, p->tm_min, p->tm_sec);
return 0;
}
运行结果:
$ ./test
2021/3/26 Fri 14:35:17
可以看到运行结果比当前电脑时间少 8 个小时,也就是没有经过时区转换。
!!!注意:gmtime 和 localtime 函数都是线程不安全的,也就是不可重入的,在实际开发中,使用 gmtime_r 和 localtime_r 函数。
difftime 函数
difftime 函数用于返回两个时间相差的秒数,一般用于将两个 struct tm 转换为 time_t 后计算其相差的秒数。
函数原型如下:
double difftime(time_t time1, time_t time2);
示例程序:
int main()
{
struct timeval begin, end;
gettimeofday(&begin, NULL);
sleep(2);
gettimeofday(&end, 0);
cout << difftime(end.tv_sec, begin.tv_sec) << endl;
}
运行结果:
$ ./test
2
strftime 函数
strftime() 函数将时间格式化,我们可以使用strftime()函数将时间格式化为我们想要的格式。它的原型如下:
size_t strftime(
char *strDest,
size_t maxsize,
const char *format,
const struct tm *timeptr
);
根据 format 指向字符串中格式命令把 timeptr 中保存的时间信息放在 strDest 指向的字符串中,最多向 strDest 中存放 maxsize 个字符。该函数返回向 strDest 指向的字符串中放置的字符数。
函数 strftime() 的操作有些类似于 sprintf()。识别以百分号 (%) 开始的格式命令集合,格式化输出结果放在一个字符串中。格式化命令说明串 strDest 中各种日期和时间信息的确切表示方法。格式串中的其他字符原样放进串中。格式命令列在下面,它们是区分大小写的。
格式 | 说明 |
---|---|
%a | 星期几的简写 |
%A | 星期几的全称 |
%b | 月分的简写 |
%B | 月份的全称 |
%c | 标准的日期的时间串 |
%C | 年份的后两位数字 |
%d | 十进制表示的每月的第几天 |
%D | 月/天/年 |
%e | 在两字符域中,十进制表示的每月的第几天 |
%F | 年-月-日 |
%g | 年份的后两位数字,使用基于周的年 |
%G | 年分,使用基于周的年 |
%h | 简写的月份名 |
%H | 24小时制的小时 |
%I | 12小时制的小时 |
%j | 十进制表示的每年的第几天 |
%m | 十进制表示的月份 |
%M | 十时制表示的分钟数 |
%n | 新行符 |
%p | 本地的AM或PM的等价显示 |
%r | 12小时的时间 |
%R | 显示小时和分钟:hh:mm |
%S | 十进制的秒数 |
%t | 水平制表符 |
%T | 显示时分秒:hh:mm:ss |
%u | 每周的第几天,星期一为第一天(值从0到6,星期一为0) |
%U | 第年的第几周,把星期日做为第一天(值从0到53) |
%V | 每年的第几周,使用基于周的年 |
%w | 十进制表示的星期几(值从0到6,星期天为0) |
%W | 每年的第几周,把星期一做为第一天(值从0到53) |
%x | 标准的日期串 |
%X | 标准的时间串 |
%y | 不带世纪的十进制年份(值从0到99) |
%Y | 带世纪部分的十制年份 |
%z,%Z | 时区名称,如果不能得到时区名称则返回空字符。 |
%% | 百分号 |
如果想显示现在是几点了,并以12小时制显示,就象下面这段程序:
int main(void)
{
struct tm *ptr;
time_t lt;
char str[80];
lt = time(NULL);
ptr = localtime(<);
strftime(str, 100, "It is now %I %p\n", ptr);
printf(str);
return 0;
}
运行结果如下:
$ ./test
It is now 10 PM
C++ 中时间操作函数
C++ 基本时间函数
C++ 标准库中很多实践操作函数是从 C 里来的,上面说的有些函数只能在 Linux/Unix 下使用,在 C++ 中进行了封装,使用和 C 语言的类似。
C++ 中类似于 C 的常用时间操作函数如下:
std::tm
:类似于 C 中的 struct tm 函数
std::time
:类似于 C 中的 time 函数
std::localtime
:类似于 C 中的 localtime 函数
std::gmtime
:类似于 C 中的 gmtime 函数
std::strfime
:类似于 C 中的 strfime 函数
std::asctime
:类似于 C 中的 asctime 函数(但已被弃用)
std::ctime
:类似于 C 中的 ctime 函数(但已被启用)
除此之外,C++ 中比如:std::colck、std::chrono,获取的时间精度都比较高,同时也比较慢,比 std::time 的慢了 2~3 个数量级,在这里不做过多赘述。