写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
目录
- 时间与日期
- 时间及时区查看命令
- 时区设置命令
- 常用 API
- 时间结构体
- 时间函数归类
- 函数应用
- localtime 和 localtime_r 区别
- 参考
首发及后期更新将于 https://arachnid.cc/linux_c-time-configuration/ 上,此处仅作二次备份。
时间与日期
GMT 和 UTC
GMT,即格林尼治标准时间,也就是世界时。GMT 的正午是指当太阳横穿格林尼治子午线(本初子午线)时的时间。但由于地球自转不均匀不规则,导致 GMT 不精确,现在已经不再作为世界标准时间使用。
UTC,即协调世界时。UTC 是以原子时秒长为基础,在时刻上尽量接近于 GMT 的一种时间计量系统。为确保 UTC 与 GMT 相差不会超过 0.9 秒,在有需要的情况下会在 UTC 内加上正或负闰秒。UTC 现在作为世界标准时间使用。
所以,UTC 与 GMT 基本上等同,误差不超过 0.9 秒。
时区
地球自西向东旋转,东边比西边先看到太阳,东边的时间也比西边的早。为了统一世界的时间,1884 年的国际经度会议规规定将全球划分为 24 个时区(东、西各 12 个时区)。规定英国(格林尼治天文台旧址)为零时区(GMT+00),东 1-12 区,西 1-12 区,中国北京处于东 8 区(GMT+08)。
若中国当前时间为 8 点整,则英国时间为 0 点整。
UTC时间与本地时间
UTC + 时区差 = 本地时间
时区差东为正,西为负。在此,把东八区时区差记为 +08
UTC + (+08) = 本地(北京)时间
UNIX 时间戳
由 Unix 内核提供的基本时间服务是自国际标准时间公元 1970 年 1 月 1 日 00:00:00 以来的秒数。
时间及时区查看命令
- 获取 UTC 世界时间
date -u
- 获取当地时间
date
- 获取当地时间及时差
date -R
时区设置命令
1、对于完整的 Linux 系统
- 命令跟描述对不上号的
tzselect
看起来很像一个时区选择的工具,但并非如此。事实上tzselect仅仅是一个查看时区表示方式的『向导』程序而已。通过依次询问大洲→国家→城市,最后告诉你如何TZ变量的写法,比如北京时间是:Asia/Shanghai
- TZ 变量
根据上面的指导,可以获知通过修改 TZ 变量,直接修改时区信息,例如:
date -RTue, 17 Jan 2017 13:57:06 +0000
export TZ='Asia/Shanghai'
date -RTue, 17 Jan 2017 19:57:18 +0600
但如果不写在环境变量文件配置里的话,一般是会话级的操作,取消重新打开便会失效;因此对于在 shell 中实现更改 TZ 变量,只能做到临时变更时区信息。
正确做法是到 /etc/profile
里(或用户的 ~/.profile
或 ~/.bashrc
文件等),直接 export TZ='xxx'
更改时区(时区的名字可以用 tzselect
向导来确定)
/etc/localtime
文件
默认情况下情况下,TZ 属性是空,这时候是靠 /etc/localtime
文件来确定的时区。而此文件通常又是一个到 /usr/share/zoneinfo/
下各种时区文件的软连接。通过修改 /etc/localtime
指向的软连接,进而修改系统的时区。比如下面的方法,将 localtime 文件设置为了北京时间:
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
2、对于裁剪的 Linux 系统,如:arm linux
tzselect
命令
正常情况下,裁剪过的是不支持该命令的,因此相对于上面的 2、3 两点并不适用于此。
- TZ 变量
与上面一样可通过修改 TZ 变量,直接修改时区信息,但与此不同的是,并不能通过 TZ='Asia/Shanghai'
去修改,只能通过时区偏移量来修改,例如我们中国的北京时间相对于 UTC-0
的偏移量是 UTC+8
;那么则修改为:
date -RTue, 17 Jan 2017 13:57:06 +0000
export TZ='CST-8'
date -RTue, 17 Jan 2017 19:57:18 +0600
注意,在这里,UTC+、- 是相反的,UTC-8 代表的是相对于 UTC 加八个小时,反之减八个小时…;而 CST 则是对应为北京时区缩写。
时区表信息可看:https://www.timeanddate.com/time/zones/
/etc/localtime
文件
由于裁剪问题,系统中是没有 /usr/share/zoneinfo/
文件夹的,如有需要,将 PC 端的 /usr/share/zoneinfo
整个 zoneinfo
文件夹复制到 rootfs 的 /usr/share
下,这样嵌入式系统中就有了 timezone
。
最后同样执行:
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
note:时区信息保存在 /etc/localtime
文件里,如果没有该文件则系统是零时区,有该文件时系统会去读取该文件。具体该文件的内容可以不同关心,在 /usr/share/zoneinfo/
目录下有各个时区对应的文件,只需要拷贝过去就可以。比如我们常用的东八区时间就是对应 /usr/share/zoneinfo/Asia/Shanghai
文件,只需要将该文件指向或拷贝到 /etc/localtime
就将系统时间改为东八区。
常用 API
时间结构体
struct tm {int tm_sec; /* Seconds (0-60) */int tm_min; /* Minutes (0-59) */int tm_hour; /* Hours (0-23) */int tm_mday; /* Day of the month (1-31) */int tm_mon; /* Month (0-11) */int tm_year; /* Year - 1900 */int tm_wday; /* Day of the week (0-6, Sunday = 0) */int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */int tm_isdst; /* Daylight saving time */
};
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 */
};
时间函数归类
1、C99 标准库函数
需 #include <time.h>
- 获取时间戳:
/* time()函数,返回一个从 1970 年 1 月 1 日 00:00:00 到现在的 time_t 类型 UTC 时间,当参数为 NULL 时直接返回秒数,当然也会将该值写入 t 指针指向的地址。 */
time_t time(time_t *t);/* mktime() 会把本地时间转换为 UTC 时间 */
time_t mktime(struct tm *tm);// note:两者区别在于传入的参数结构体不同,mktime 存在时区转换;time(t) 等价于 mktime(localtime(time(t)))。
- 获取
struct tm
类型的时间:
/* gmtime() 是零时区,把 UTC 时间转换成北京时间的话,需要在年数上加 1900,月份上加 1,小时数加上 8。 */
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);/* localtime() 将得到本地时间,该函数与 gmtime() 唯一区别是,在转换成北京时间的小时数不需要加上 8。 */
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);// note:localtime 是将时区考虑在内了,转出的是当前时区的时间。但是注意,有些嵌入式设备上被裁减过的系统,时区没有被设置好,导致二者转出来的时间都是零时区的。在多线程应用里面,应该用后缀不带 `_r` 的函数,如: localtime_r 函数替代 localtime 函数,因为 localtime_r 是线程安全的,例子看第五大点。
- 时间日期格式化:
/* 将 tm 结构中的时间信息转换为相应时间的字符串 */
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);/* 将日历时间参数 timep 转换为一个表示本地当前时间的字符串,函数已经由时区转换成当地时间 */
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);// note:两者区别在于传入的参数结构体不同,但转换出来的信息格式显示是一样的;asctime 是直接把时间格式化,而 ctime 是经过时区转换后再格式化输出;ctime(t) 等价于 asctime(localtime(t))。/* 常用时间格式化参数看下方描述 */
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
在使用 strftime
时间格式化函数所涉及的相关参数的含义如下:
参数 | 含义 |
---|---|
%F | 将时间格式化为年-月-日 |
%T | 将时间格式化为显示时分秒: hh:mm:ss |
%Y | 将时间格式化为带世纪部分的十制年份 |
%m | 将时间格式化为十进制表示的月份 |
%d | 将时间格式化为十进制的每月中的第几天 |
%H | 将时间格式化为24小时制的小时 |
%M | 将时间格式化为十进制表示的分钟数 |
%S | 将时间格式化为十进制表示的秒数 |
更多参数请阅:https://www.runoob.com/cprogramming/c-function-strftime.html
- 获取时间差:
double difftime(time_t time1, time_t time0);
2、Uinx 系统函数
需 #include <sys/time.h>
#include <sys/time.h>
获取 struct timeval 类型的时间:
/* 相对于 time() 和 mktime() ,gettimeofday() 能获取更精准的微秒级别,及相应的时区信息,需要注意的是 tz 是依赖于系统,不同的系统可能存在获取不到的可能,因此通常设置为 NULL */
int gettimeofday(struct timeval *tv, struct timezone *tz);
// 成功则返回 0,失败返回 -1,错误代码存于 errno
// EFAULT:指针 tv 或 tz 所指的内存空间无效。设置 struct timeval 类型的时间:
int settimeofday(const struct timeval *tv,const struct timezone *tz);
// 成功则返回 0,失败返回 -1,错误代码存于 errno
// EPERM:调用进程没有足够权限调用 settimeofday(),即权限不够。
// EINVAL:时区或其它内容无效,无法正确设置时间。// note:settimeofday 的修改时间需要在 root 权限下才能配置成功。
3、总结
time
、gmtime
、asctime
所表示的时间都是 UTC 时间,只是数据类型不一样,
而 mktime
、localtime
、ctime
的时间都存在时区之间变换。
函数 | 传参类型 | 返回类型 | 时区转换 | 描述 |
---|---|---|---|---|
time() | time_t | time_t | UTC+0 | 用于获取 UTC 零时区的时间戳格式 |
mktime() | struct tm | time_t | UTC-t | 用于获取 UTC 零时区的时间戳格式, 但会经过时区转换, 把本地时间内部换成 UTC+0 |
gmtime() | time_t | struct tm | UTC+0 | 用于获取 UTC 零时区的 |
localtime() | time_t | struct tm | UTC+t | |
asctime() | struct tm | string | UTC+0 | |
ctime() | time_t | string | UTC+t |
函数应用
1、获取当前时区的时间戳偏移量
time_t get_localtime_interval(void)
{time_t timep_utc, timep_local;struct tm tm_utc;time(&timep_utc);gmtime_r(&timep_utc, &tm_utc);timep_local = mktime(&tm_utc);return (timep_utc - timep_local);
}
2、获取当前时区的时间戳(带时区的本地时间)
time_t get_time_stamp(void)
{time_t timep;time(&timep);timep += get_localtime_interval(timep);return timep;
}
3、获取时区偏移量
time_t get_time_stamp(void)
{time_t timep_zone;timep_zone = get_localtime_interval(timep) / 3600;return timep_zone;
}
4、时区设置函数
时间函数除了gmttime()、asctime() 不受环境变量 TZ 的影响外,大部分函数都受到环境变量 TZ 的影响,这几个函数是: localtime、mktime、ctime 和 strftime。如果定义了 TZ,则这些函数将使用其值以代替系统默认时区。
在 Unix 环境下可以通过改变系统文件修改环境变量,也可以通过函数 setenv() 修改。
TZ 指定了当前的系统时区。这个时区会影响我们所做的时间转换。例如假设当前的系统时间是 8:00AM,如果我们把当前的时区设置成东八区,则标准时间就是(即 UTC+0)的时间就是 8-8=0:00AM,如果是看成是东六区的话,则标准时间就变成了 8-6=2:00AM。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(int argc, const char * argv[])
{ setenv("TZ", "CST-8", 1); // 北京东八区// setenv("TZ", "UTC+0", 1); //将当前时区设置成标准区char buf[64];struct timeval tv;struct tm* tm_time;gettimeofday(&tv, NULL);tm_time = gmtime(&tv.tv_sec);strftime(buf, sizeof(buf), "%a %b %m %H:%M:%S %Z %Y", tm_time);printf("GMT time: %s\n", buf);tm_time = localtime(&tv.tv_sec);strftime(buf, sizeof(buf), "%a %b %m %H:%M:%S %Z %Y", tm_time);printf("local time: %s\n", buf);return 0;
}
localtime 和 localtime_r 区别
示例来源(下方为原文备份记录):https://blog.csdn.net/test1280/article/details/80917962
localtime
和 localtime_r
的函数功能: converts the calendar time timep to broken-time representation
在调用 localtime
和 localtime_t
函数时,需特别注意:
-
localtime
是不可重入函数,非线程安全 -
localtime_r
是可重入函数,线程安全
1、使用 localtime
时不可重入示范:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>int main()
{time_t curTime = time(NULL);time_t aftTime = curTime + 3600*3;struct tm *pTm1 = localtime(&curTime);struct tm *pTm2 = localtime(&aftTime);fprintf(stdout, "%04d%02d%02d%02d%02d%02d\n",pTm1->tm_year + 1900,pTm1->tm_mon + 1,pTm1->tm_mday,pTm1->tm_hour,pTm1->tm_min,pTm1->tm_sec);fprintf(stdout, "%04d%02d%02d%02d%02d%02d\n",pTm2->tm_year + 1900,pTm2->tm_mon + 1,pTm2->tm_mday,pTm2->tm_hour,pTm2->tm_min,pTm2->tm_sec);return 0;
}
编译 & 运行:
$ gcc -o main main.c
$ ./main
20180704225205
20180704225205
调用 localtime
函数并获取其返回值(一个指向 struct tm
结构类型数据的指针)后,我们并未对返回值进行显式地释放。
这并没有什么问题(不会导致内存泄漏)。
因为 localtime
函数返回值是一个指针,指向一个静态变量,这个静态变量是库中的一个 static struct tm
类型数据。
man localtime:
The return value points to a statically allocated struct which might be overwritten by subsequent calls to any of the date and time functions.
这将引出新的问题,同一进程多个线程中同时调用(极短时间内连续调用) localtime
函数,返回值 tm
可能被覆盖。
举个栗子:
两个线程 A 和 B 同时调用 localtime
函数:
时刻 1:线程 A 调用 localtime
函数,得到一个指针,指向 static struct tm
类型变量;(tm 中存储的值更新为 value-a)
时刻 2:线程 B 调用 localtime
函数,得到一个指针,指向 static struct tm
类型变量;(tm 中存储的值更新为 value-b)
时刻 3:线程 A 对 localtime
返回的指针进行相关引用操作(例如 printf 输出某字段),此时 static struct tm
中的值实际是 value-b,并非预期的 value-a。
时刻 4:线程 B 对 localtime
返回的指针进行相关引用操作,此时 static struct tm
中的值实际是 value-b。
上面的示范代码虽然是在同一线程中,但是已经可以简单模拟这样的多线程执行调用流程。
如何解决?
localtime_r
是 localtime
的可重入版本(线程安全版本)。
localtime
不可重入是由于 static struct tm
是库中的一个静态变量,如果我们在调用 localtime
时传入一个 struct tm
类型变量(指针)用于存放结果,岂不是实现可重入?
Bingo!
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
调用 localtime
只需要传入指向 time_t
的一个常量指针;
调用 localtime_t
不仅需要传入指向 time_t
的一个常量指针,还需要传入指向 struct tm
的一个指针,结果将存储在 result
指向的 struct tm
对象中;
The 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.
2、使用 localtime_r
时可重入示范:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>int main()
{time_t curTime = time(NULL);time_t aftTime = curTime + 3600*3;struct tm tm1;struct tm tm2;localtime_r(&curTime, &tm1);localtime_r(&aftTime, &tm2);fprintf(stdout, "%04d%02d%02d%02d%02d%02d\n",tm1.tm_year + 1900,tm1.tm_mon + 1,tm1.tm_mday,tm1.tm_hour,tm1.tm_min,tm1.tm_sec);fprintf(stdout, "%04d%02d%02d%02d%02d%02d\n",tm2.tm_year + 1900,tm2.tm_mon + 1,tm2.tm_mday,tm2.tm_hour,tm2.tm_min,tm2.tm_sec);return 0;
}
编译 & 运行:
$ gcc -o main main.c
$ ./main
20180704200531
20180704230531
参考
https://www.cnblogs.com/sun-frederick/p/4772535.html
https://blog.csdn.net/test1280/article/details/80917962