时间基础概念及Linux中的时间函数
- 时间相关概念
- GMT 时间
- UTC 时间
- 时区 `Time Zone`
- 夏令时 `DST`
- 本地时间 `localtime`
- Linux 系统中的时间
- 时钟基础概念
- 系统节拍数 `jiffies`
- Linux系统查看时间及配置时区
- 获取时间函数
- 获取 ==当前时间== `time()`
- 获取 ==当前时间(微秒)== `gettimeofday()`
- `struct timeval` 结构体
- 时间转换函数
- ==time_t时间== 转换为 ==时间字符串== `ctime()/ctime_r()`
- ==time_t时间== 转换为 ==本地时间== `localtime()` / `localtime_r()`
- `struct tm` 结构体
- ==time_t时间== 转换为 ==UTC时间== `gmtime()` / `gmtime_r()`
- 转换回 ==time_t时间== `mktime()`
- ==分解时间== 转换为 ==固定时间字符串== `asctime()/asctime_r()`
- ==分解时间== 转换为 ==自选时间字符串== `strftime()`
- ==自选时间字符串== 转换为 ==分解时间== `strptime()`
- 设置时间函数
- 设置 ==系统本地时间== `settimeofday()`
- 例程
- 总结
- 参考文献
时间相关概念
GMT 时间
GMT(Greenwich Mean Time)中文全称是 格林威治标准时间 ,它规定太阳每天经过位于英国伦敦郊区的皇家格林威治天文台的时间为中午12点。
格林威治皇家天文台为了海上霸权的扩张计划,在十七世纪就开始进行天体观测。为了天文观测,选择了穿过英国伦敦格林威治天文台子午仪中心的一条经线作为零度参考线,这条线,简称格林威治子午线。
1884年10月在美国华盛顿召开了一个国际子午线会议,该会议将格林威治子午线设定为本初子午线,并将格林威治平时 (GMT, Greenwich Mean Time) 作为世界时间标准(UT, Universal Time)。由此也确定了全球24小时自然时区的划分,所有时区都以和 GMT 之间的偏移量做为参考。
1972年之前,格林威治时间(GMT)一直是世界时间的标准。1972年之后,GMT 不再是一个时间标准了。
UTC 时间
UTC(Coodinated Universal Time), 协调世界时 ,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。
UTC是基于原子钟的国际标准时间。它的定义是通过原子钟测量出的时间来提供一个准确且稳定的时间参考。UTC起源于1970年代,旨在替代格林尼治平均时间(GMT)作为全球标准时间。
以下是对UTC时间的详细介绍:
- 原子钟基准:UTC是基于国际原子时(International Atomic Time,缩写为TAI)的基础上进行微调得出的。国际原子时是由全球各地的原子钟测量得出的最准确的时间标准,它以国际计量单位制中的秒作为基本单位。
- 闰秒:为了确保UTC与地球自转的持续时间保持一致,每隔一段时间会插入一个闰秒。闰秒的插入是由国际地球自转与参考系统服务(International Earth Rotation and Reference Systems Service,缩写为IERS)根据地球自转速度的变化情况进行决定的。这样做是为了防止地球自转与UTC时间之间的差异过大。
- 时间标准化:UTC时间是全球通用的时间标准,不受时区的影响。它以24小时制表示,使用从0到23的数字表示小时,从0到59的数字表示分钟,以及从0到59的数字表示秒。例如,12:00:00表示中午12点,而23:59:59表示晚上11点59分59秒。
- 与其他时间标准的关系:UTC与GMT非常接近,UTC 比 GMT更精准,以原子时计时,但 在不需要精确到秒的情况下,两者几乎完全一致 。在实际使用中,GMT和UTC通常可以互换使用,尽管严格来说它们在定义和计算方法上有一些微小的差异。
- 时间表示格式:UTC时间可以使用多种表示格式来呈现。最常见的格式是"YYYY-MM-DD HH:MM:SS",其中"YYYY"表示年份,"MM"表示月份,"DD"表示日期,"HH"表示小时,"MM"表示分钟,“SS"表示秒。例如,2023年5月31日下午3点30分15秒的UTC时间可以表示为"2023-05-31 15:30:15”。
时区 Time Zone
从格林威治本初子午线起,经度每向东或者向西间隔15°,就划分一个时区,在这个区域内,大家使用同样的标准时间。
但实际上,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。另外:由于目前,国际上并没有一个批准各国更改时区的机构。一些国家会由于特定原因改变自己的时区。
全球共分为24个标准时区,相邻时区的时间相差一个小时。
在不同地区,同一个时区往往会有很多个不同的时区名称,因为名称中通常会包含该国该地区的地理信息。
在夏令时期间,当地的时区名称及字母缩写会有所变化(通常会包含“daylight”或“summer”字样)。例如美国东部标准时间叫:EST,Estern Standard Time;而东部夏令时间叫:EDT,Estern Daylight Time。
想查看世界所有时区的名字可以访问这个网站:Time Zone Abbreviations - Worldwide List
全球被划分为24个时区,每一个时区横跨经度15度,以英国格林威治的本初子午线作为零度经线,将全球划分为东西两半球,分为东一区、东二区、东三区……东十二区以及西一区、西二区、西三区……西十二区,而本初子午线所在时区被称为中时区(或者叫零时区),划分图如下所示:
东十二区和西十二区其实是一个时区,就是十二区 , 东十二区与西十二区各横跨经度7.5度,以180度经线作为分界线 。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时。相邻两个时区的时间相差1小时。例如,我国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间晚1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。凡向西走,每过一个时区,就要把表向前拨1小时(比如2点拨到1点);凡向东走,每过一个时区,就要把表向后拨1小时(比如1点拨到2点)。
实际上,世界上不少国家和地区都不严格按时区来计算时间。为了在全国范围内采用统一的时间,一般都把某一个时区的时间作为全国统一采用的时间。例如,我国把首都北京所在的东8区的时间作为全国统一的时间,称为北京时间,北京时间就作为我国使用的本地时间,譬如我们电脑上显示的时间就是北京时间,我国国土面积广大,由东到西横跨了5个时区,也就意味着我国最东边的地区与最西边的地区实际上相差了4、5个小时。又例如,英国、法国、荷兰和比利时等国,虽地处中时区,但为了和欧洲大多数国家时间相一致,则采用东1区的时间。
夏令时 DST
夏时制 (daylight saving time,DST)
,又称夏令时、日光节约时间(英国与其他地区称为 (Summer Time)
),是一种在夏季月份牺牲正常的日出时间,而将时间调快的做法。使用夏时制的地区,会在接近春季开始的时候,通常将时间调快60分钟,并在秋季调回正常时间。夏时制会造成在春季转换当日的睡眠时间减少一个小时,而在秋季转换当日则会多出一小时的睡眠时间。不过,澳洲豪勋爵岛的时间调整只有30分钟,而不是一般的60分钟。
全球约40%的国家在夏季使用夏令时,其他国家则全年只使用标准时间。标准时间在有的国家也因此被相应地称为冬季时间。
1895年,乔治·哈德逊提出夏时制的概念。1916年4月30日,德意志帝国与奥匈帝国首次在全国实施夏时制。后来许多国家在不同时期都曾实行过夏时制,其中1970年代能源危机开始尤为盛行。邻近赤道的地区通常不实施夏时制,因为这些地区的日出变化极小,无法成为实施夏时制的正当理由。部分国家只在其中部分地区实施夏时制;例如澳大利亚在东南部的州分采用,其他地区则不实施。香港、澳门及台湾在二战后曾经连续多年采用夏时制,至1980年停用,中国大陆也曾经在1986年采用,至1992年停用,现时亚洲与非洲地区一般不实施夏时制。
有时,夏时制的转换会让报时工作变得更加复杂,并且会扰乱旅行、计费、纪录保存、医疗设备、重机设备与睡眠模式的运作。电脑软件通常可以自动转换时间,但各地司法管辖区的夏时制政策变更仍有可能会造成困扰。
本地时间 localtime
在日常生活中所使用的时间我们通常称之为本地时间。这个时间等于我们所在(或者所使用)时区内的当地时间,它由与世界标准时间(UTC)之间的偏移量来定义。这个偏移量可以表示为 UTC-
或 UTC+
,后面接上偏移的小时和分钟数。例如北京时间为 UTC+8
。
Linux 系统中的时间
时钟基础概念
操作系统中一般会有两个时钟,一个系统时钟(system clock),一个实时时钟RTC(Real time clock):
- 系统时钟由系统启动之后由内核来维护,譬如使用
date
命令查看到的就是系统时钟,所以在 系统关机情况下是不存在的 - 实时时钟一般由RTC时钟芯片提供,RTC芯片有相应的电池为其供电,以保证 系统在关机情况下RTC能够继续工作、继续计时 。
Linux系统 在开机启动之后首先会读取RTC硬件获取实时时钟作为系统时钟的初始值,之后内核便开始维护自己的系统时钟 。所以由此可知, RTC硬件只有在系统开机启动时会读取一次 ,目的是用于对系统时钟进行初始化操作,之后的运行过程中便不会再对其进行读取操作了。
而在系统关机时,内核会将系统时钟写入到RTC硬件、进行同步操作。
系统节拍数 jiffies
jiffies是内核中定义的一个全局变量, 内核使用 jiffies 来记录系统从 启动以来的系统节拍数 ,所以这个变量用来记录以系统节拍时间为单位的时间长度,Linux内核在编译配置时定义了一个节拍时间,使用节拍率(一秒钟多少个节拍数)来表示,譬如常用的节拍率为:
-
100Hz(一秒钟100个节拍数,节拍时间为1s / 100)
-
200Hz(一秒钟200个节拍,节拍时间为1s / 200)
-
250Hz(一秒钟250个节拍,节拍时间为1s / 250)
-
300Hz(一秒钟300个节拍,节拍时间为1s / 300)
-
500Hz(一秒钟500个节拍,节拍时间为1s / 500)
-
…………
由此可以发现配置的节拍率越低,每一个系统节拍的时间就越短,也就意味着jiffies记录的时间精度越高,当然, 高节拍率会导致系统中断的产生更加频繁 ,频繁的中断会加剧系统的负担, 一般默认情况下都是采用 100Hz
作为系统节拍率 。
内核其实通过jiffies来维护系统时钟,全局变量 jiffies
在系统开机启动时会设置一个初始值 ,上面也给大家提到过,RTC实时时钟会在系统开机启动时读取一次,目的是用于对系统时钟进行初始化,这里说的初始化其实指的就是对内核的jiffies变量进行初始化操作,具体如何将读取到的实时时钟换算成jiffies数值,这里便不再给大家介绍了。
所以由此可知,操作系统使用jiffies这个全局变量来记录当前时间,当我们需要获取到系统当前时间点时,就可以使用jiffies变量去计算,当然并不需要我们手动去计算,Linux系统提供了相应的系统调用或C库函数用于获取当前时间,譬如系统调用 time()
、 gettimeofday()
,其实质上就是通过jiffies变量换算得到。
Linux系统查看时间及配置时区
我们可以使用 date
命令查看系统当前的本地时间,如下所示:
可以看到显示出来的字符串后面有一个 CST
字样, CST
在这里其实指的是 China Standard Time
(中国标准时间)的缩写 ,表示当前查看到的时间是中国标准时间,也就是我国所使用的标准时间——北京时间,一般在安装Ubuntu系统的时候会提示用户设置所在城市,那么系统便会根据你所设置的城市来确定系统的本地时间对应的时区,譬如设置的城市为上海,那么系统的本地时间就是北京时间,因为我国统一使用北京时间作为本国的标准时间。
在Ubuntu系统下,时区信息通常以标准格式保存在一些文件当中,这些文件通常位于 /usr/share/zoneinfo
目录下, 该目录下的每一个文件(包括子目录下的文件)都包含了一个特定国家或地区内时区制度的相关信息 ,且往往根据其所描述的城市或地区缩写来加以命名,譬如 EST(美国东部标准时间)
、 CET(欧洲中部时间)
、 UTC(世界标准时间)
、 Hongkong
、 Iran
、 Japan(日本标准时间)
等,也把这些文件称为时区配置文件,如下图所示:
本地时间由时区配置文件 /etc/localtime
定义,通常链接到 /usr/share/zoneinfo
目录下的某一个文件(或其子目录下的某一个文件),如上图所示。
如果我们要 修改Ubuntu系统本地时间的时区信息 ,可以 直接将 /etc/localtime 链接到 /usr/share/zoneinfo 目录下的任意一个时区配置文件 ,譬如EST(美国东部标准时间),首先进入到 /etc
目录下,执行下面的命令:
sudo rm -rf localtime #删除原有链接文件
sudo ln -s /usr/share/zoneinfo/EST localtime #重新建立链接文件
可以发现后面的标识变成了EST,也就意味着当前系统的本地时间变成了EST时间(美国东部标准时间)。
获取时间函数
获取 当前时间 time()
系统调用time()用于获取当前时间,以秒为单位,返回得到的值是自 1970-01-01 00:00:00 +0000 (UTC)
以来的秒数,其函数原型如下所示(可通过"man 2 time"命令查看):
#include <time.h> // tloc: 如果tloc参数不是NULL,则返回值也存储在tloc指向的内存中
time_t time(time_t *tloc);
返回值: 成功则返回自 1970-01-01 00:00:00 +0000 (UTC)
以来的时间值(以秒为单位);失败则返回-1,并会设置errno。
获取 当前时间(微秒) gettimeofday()
time()
获取到的时间只能精确到秒,如果想要获取更加精确的时间可以使用系统调用 gettimeofday
来实现, gettimeofday()
函数提供微秒级时间精度,函数原型如下所示:
#include <sys/time.h> // tv: struct timeval结构体指针变量
// tz:是个历史产物,早期实现用其来获取系统的时区信息,目前已遭废弃,调用时应设置为NULL。
int gettimeofday(struct timeval *tv, struct timezone *tz);
返回值: 成功返回0;失败将返回-1,并设置errno。
struct timeval
结构体
获取得到的时间值存储在参数tv所指向的 struct timeval
结构体变量中,该结构体包含了两个成员变量 tv_sec
和 tv_usec
,分别用于表示秒和微秒:
struct timeval { long tv_sec; /* 秒 */long tv_usec; /* 微秒 */
}
时间转换函数
time_t时间 转换为 时间字符串 ctime()/ctime_r()
ctime()
是一个C库函数,可以将日历时间转换为可打印输出的字符串形式, ctime()
函数原型如下所示:
#include <time.h> // time_t: 时间变量指针
// buf: 是一个指向字符数组的指针,用于存储转换后的时间字符串
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
返回值: 成功将返回一个char *类型指针,指向转换后得到的字符串;失败将返回NULL。
ctime_r()
是 ctime()
的可重入版本,一般推荐大家使用可重入函数 ctime_r()
,可重入函数 ctime_r()
多了一个参数 buf
,也就是缓冲区首地址,所以 ctime_r()
函数需要调用者提供用于存放字符串的缓冲区。
为了保证足够的缓冲区空间来存储时间字符串,
buf
的长度应该至少为 26(包括终止符\0
)。这是因为时间字符串的最大长度为 25,再加上一个终止符。
time_t时间 转换为 本地时间 localtime()
/ localtime_r()
localtime()
函数可以把 time()
或 gettimeofday()
得到的秒数(time_t时间或日历时间)变成一个 struct tm
结构体所表示的时间,该时间对应的是本地时间。localtime函数原型如下:
#include <time.h> // timep:需要进行转换的time_t时间变量对应的指针,可通过time()或gettimeofday()获取得到
// result: struct tm结构体类型指针,存储转换结果
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
返回值:
localtime()
:不可重入,成功则直接返回一个有效的struct tm
结构体指针。localtime_r()
:可重入,成功则返回一个有效的struct tm
结构体指针,返回值将会等于参数result
;失败则返回NULL。
可重入版本 localtime_r()
的区别是调用者需要自己定义 struct tm
结构体变量、并将该变量指针赋值给参数 result
,在函数内部会对该结构体变量进行赋值操作。
struct tm
结构体
struct tm
结构体如下所示,该结构体中包含了年月日时分秒星期等信息:
struct tm { int tm_sec; /* 秒(0-60) */ int tm_min; /* 分(0-59) */ int tm_hour; /* 时(0-23) */ int tm_mday; /* 日(1-31) */ int tm_mon; /* 月(0-11) */int tm_year; /* 年(这个值表示的是自1900年到现在经过的年数) */ int tm_wday; /* 星期(0-6, 星期日Sunday = 0、星期一=1…) */ int tm_yday; /* 一年里的第几天(0-365, 1 Jan = 0) */ int tm_isdst; /* 夏令时 (-1 表示无夏令时信息,0 表示不使用夏令时,1 表示使用夏令时) */
};
time_t时间 转换为 UTC时间 gmtime()
/ gmtime_r()
gmtime()
函数也可以把time_t时间变成一个 struct tm
结构体所表示的时间,与 localtime()
所不同的是, gmtime()
函数所得到的是UTC国际标准时间,并不是计算机的本地时间,这是它们之间的唯一区别。 gmtime()
函数原型如下所示:
#include <time.h> struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
转换回 time_t时间 mktime()
mktime()
函数与 localtime()
函数相反, mktime()
可以将使用 struct tm
结构体表示的分解时间转换为 time_t
时间(日历时间),同样这也是一个C库函数,其函数原型如下所示:
#include <time.h> // tm:需要进行转换的struct tm结构体变量对应的指针
time_t mktime(struct tm *tm);
返回值: 成功返回转换得到time_t时间值;失败返回-1。
分解时间 转换为 固定时间字符串 asctime()/asctime_r()
asctime()
函数与 ctime()
函数的作用一样,也可将时间转换为可打印输出的字符串形式,与 ctime()
函数的区别在于, ctime()
是将 time_t
时间转换为固定格式字符串、而 asctime()
则是将 struct tm
表示的分解时间转换为固定格式的字符串。 asctime()
函数原型如下所示:
#include <time.h> // tm:需要进行转换的struct tm表示的时间
// buf:可重入版本函数asctime_r需要额外提供的参数buf,指向一个缓冲区,用于存放转换得到的字符串。
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);
返回值: 转换失败将返回NULL;成功将返回一个char *类型指针,指向转换后得到的时间字符串,对于asctime_r函数来说,返回值就等于参数buf。
在使用 struct tm
结构体时,需要注意以下几点:
tm_mon
和tm_year
的取值范围与实际的月份和年份不同。tm_mon
的取值范围是 0 到 11,其中 0 表示 1 月,1 表示 2 月,依此类推。tm_year
表示从 1900 年开始的年数,例如,tm_year
为 121 表示的是 2021 年。tm_wday
的取值范围是 0 到 6,其中 0 表示周日,1 表示周一,依此类推。tm_yday
表示一年中的天数,从 0 开始,例如,1 月 1 日的tm_yday
值为 0,12 月 31 日的tm_yday
值为 365。tm_isdst
是夏令时标识符,用于指示当前时间是否处于夏令时期间。如果系统无法确定夏令时信息,则tm_isdst
的值为 -1;如果不使用夏令时,则值为 0;如果使用夏令时,则值为 1。
分解时间 转换为 自选时间字符串 strftime()
除了 asctime()
函数之外, strftime()
也可以将一个 struct tm
变量表示的分解时间转换为为格式化字符串,并且在功能上比 asctime()
和 ctime()
更加强大,它可以根据自己的喜好自定义时间的显示格式,而 asctime()
和 ctime()
转换得到的字符串时间格式的固定的。
strftime()
函数原型如下所示:
#include <time.h> // s:指向一个缓存区的指针,该缓冲区用于存放生成的字符串
// max:字符串的最大字节数。
// format:这是一个用字符串表示的字段,包含了普通字符和特殊格式说明符,可以是这两种字符的任意组合。
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
返回值: 如果转换得到的目标字符串不超过最大字节数(也就是max),则返回放置到s数组中的字节数;如果超过了最大字节数,则返回0。
format
特殊格式说明符将会被替换为 struct tm
结构体对象所指时间的相应值,这些特殊格式说明符如下:
说明符 | 表示含义 | 实例 |
---|---|---|
% a \% \mathrm{a} %a | 星期的缩写 | S u n \mathrm{Sun} Sun |
% A \% \mathrm{~A} % A | 星期的完整名称 | S u n d a y \mathrm{Sunday} Sunday |
% b \% \mathrm{~b} % b | 月份的缩写 | M a r \mathrm{Mar} Mar |
% B \% \mathrm{~B} % B | 月份的完整名称 | M a r c h \mathrm{March} March |
% c \% \mathrm{c} %c | 系统当前语言环境对应的首选日期和时间表示形式 | |
% C \% \mathrm{C} %C | 世纪(年/100) | 20 20 20 |
% d \% \mathrm{~d} % d | 十进制数表示一个月中的第几天(01-31) | 15 、 05 15 、 05 15、05 |
% D \% \mathrm{D} %D | 相当于 % m / % d / % y \mathrm{m} / \% \mathrm{~d} / \% \mathrm{y} m/% d/%y | 01 / 14 / 21 01 / 14 / 21 01/14/21 |
% e \% \mathrm{e} %e | 与 % d \%\mathrm{d} %d 相同,但是单个数字时, 前导 0 会被去掉 | 15 、 5 15 、 5 15、5 |
% F \% \mathrm{~F} % F | 相当于 % Y − % m − % d \%\mathrm{Y}-\%\mathrm{m}-\%\mathrm{d} %Y−%m−%d | 2021 − 01 − 14 2021\mathrm{-}01\mathrm{-}14 2021−01−14 |
% h \% \mathrm{~h} % h | 相当于 % b \% \mathrm{~b} % b | J a n \mathrm{Jan} Jan |
% H \% \mathrm{H} %H | 十进制数表示的 24 小时制的小时(范围 00-23) | 01 、 22 01 、 22 01、22 |
%I | 十进制数表示的 12 小时制的小时(范围 01-12) | 01 、 11 01 、 11 01、11 |
% j \% \mathrm{j} %j | 十进制数表示的一年中的某天(范围 001-366) | 050 、 285 050 、 285 050、285 |
% k \% \mathrm{k} %k | 与% % \% % 相同, 但是单个数字时, 前导 0 会被去掉(范围 0-23) | 1 、 22 1 、 22 1、22 |
% 1 \% 1 %1 | 与%I 相同, 但是单个数字时, 前导 0 会被去掉(范围 1-12) | 1 、 11 1 、 11 1、11 |
% m \% \mathrm{~m} % m | 十进制数表示的月份(范围 01-12) | 01 、 10 01 、 10 01、10 |
% M \% \mathrm{M} %M | 十进制数表示的分钟(范围 00-59) | 01 、 55 01 、 55 01、55 |
% n \%\mathrm{n} %n | 换行符 | |
% p \% \mathrm{p} %p | 根据给定的时间值, 添加 “AM” 或 “PM” | P M \mathrm{PM} PM |
% P \% \mathrm{P} %P | 与 %p 相同, 但会使用小写字母表示 | p m \mathrm{pm} pm |
% r \% \mathrm{r} %r | 相当于 % I : % M : % S % p \% \mathrm{I}: \% \mathrm{M}: \% \mathrm{~S} \% \mathrm{p} %I:%M:% S%p | 12 : 15 : 31 P M 12: 15: 31 \mathrm{PM} 12:15:31PM |
% R \% \mathrm{R} %R | 相当于 % H : % M \% \mathrm{H}: \% \mathrm{M} %H:%M | 12 : 16 12: 16 12:16 |
% S \% \mathrm{~S} % S | 十进制数表示的秒数(范围 00 − 60 00-60 00−60) | 05 、 30 05 、 30 05、30 |
% T \% \mathrm{~T} % T | 相当于 % H : % M : % S \% \mathrm{H}: \% \mathrm{M}: \% \mathrm{~S} %H:%M:% S | 12 : 20 : 03 12: 20: 03 12:20:03 |
% u \% \mathrm{u} %u | 十进制数表示的星期(范围 1-7, 星期一为 1) | 1 , 5 1,5 1,5 |
% U \% \mathrm{U} %U | 十进制数表示, 当前年份的第几个星期(范围 00 − 53 00-53 00−53), 从第一个星期日作为 01 01 01 周的第一天开始 | |
% W \% \mathrm{~W} % W | 十进制数表示, 当前年份的第几个星期(范围 00 − 53 00-53 00−53), 从第一个星期一作为 01 01 01 周的第一天开始 | |
% W \% \mathrm{~W} % W | 十进制数表示的星期, 范围为 0 − 6 0-6 0−6, 星期日为 0 0 0 | |
% x \% \mathrm{x} %x | 系统当前语言环境的首选日期表示形式, 没有时间 | 01 / 14 / 21 01 / 14 / 21 01/14/21 |
% X \% \mathrm{X} %X | 系统当前语言环境的首选时间表示形式, 没有日期 | 12 : 30 : 16 12: 30: 16 12:30:16 |
% y \% \mathrm{y} %y | 十进制数表示的年份(后两字数字) | 21 21 21 |
% Y \% \mathrm{Y} %Y | 十进制数表示的年份 (4 个数字) | 2021 2021 2021 |
% % \% \% %% | 输出%符号 | % \% % |
通过上表可知,譬如我要想输出 2021-01-14 16:30:25<PM> January Thursday
这样一种形式表示的时间日期,那么就可以这样来设置 format
参数:
"%Y-%m-%d %H:%M:%S<%p> %B %A"
自选时间字符串 转换为 分解时间 strptime()
strptime
用于将字符串解析为时间和日期的结构体 struct tm
。它在 <time.h>
头文件中定义,函数原型如下:
#include <time.h> // str: 要解析的字符串
// format: 字符串的格式
// timeptr: 指向 struct tm 结构体的指针,用于存储解析后的时间和日期信息。
char *strptime(const char *str, const char *format, struct tm *timeptr);
返回值: 指向字符串中解析后剩余部分的指针,如果解析成功,则返回的指针指向字符串的结束位置,如果解析失败,则返回 NULL
。
strptime
函数根据指定的格式解析字符串,并将解析结果存储在 timeptr
指向的结构体中。解析过程中,函数会根据 format
字符串中的格式指示符,逐个匹配 str
中的字符,从而将时间和日期的各个部分提取出来,并存储在 timeptr
中的相应成员中。
设置时间函数
设置 系统本地时间 settimeofday()
使用 settimeofday()
函数可以设置时间,也就是设置系统的本地时间,函数原型如下所示:
#include <sys/time.h> // tv:需要设置的时间便通过参数tv指向的struct timeval结构体变量传递进去
// tz:是个历史产物,早期实现用其来获取系统的时区信息,目前已遭废弃,调用时应设置为NULL。
int settimeofday(const struct timeval *tv, const struct timezone *tz);
返回值: 成功返回0;失败将返回-1,并设置errno。
使用 settimeofday
设置系统时间时内核会进行权限检查, 只有超级用户(root)才可以设置系统时间,普通用户将无操作权限 。
例程
我们通过一个程序进行测试,例程包含 time
、ctime
、localtime
、gmtime
、mktime
、asctime
、strftime
、gettimeofday
和 settimeofday
函数:
#include <stdio.h>
#include <time.h>
#include <sys/time.h>int main() {time_t rawtime;struct tm *timeinfo;struct timeval tv;struct timezone tz;char buffer[80];// 获取当前时间time(&rawtime);printf("Current time: %ld\n", rawtime);// 使用ctime将时间转换为字符串printf("Current time (ctime): %s", ctime(&rawtime));// 使用localtime将时间转换为本地时间timeinfo = localtime(&rawtime);printf("Local time: %s", asctime(timeinfo));// 使用gmtime将时间转换为GMT时间timeinfo = gmtime(&rawtime);printf("GMT time: %s", asctime(timeinfo));// 使用mktime将结构体时间转换为时间值timeinfo->tm_year = 122; // 2022年timeinfo->tm_mon = 0; // 1月timeinfo->tm_mday = 1; // 1号rawtime = mktime(timeinfo);printf("Custom time (mktime): %ld\n", rawtime);// 使用asctime将时间值转换为字符串printf("Custom time (asctime): %s", asctime(localtime(&rawtime)));// 使用strftime将时间值格式化为字符串strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localtime(&rawtime));printf("Custom time (strftime): %s\n", buffer);// 使用gettimeofday获取当前时间和时区信息gettimeofday(&tv, &tz);printf("Current time (gettimeofday): %ld seconds, %ld microseconds\n", tv.tv_sec, tv.tv_usec);printf("Timezone offset: %d seconds\n", tz.tz_minuteswest * 60);// 使用settimeofday设置系统时间tv.tv_sec += 3600; // 延时1小时if (settimeofday(&tv, NULL) == 0) {printf("System time set successfully.\n");} else {printf("Failed to set system time.\n");}return 0;
}
在这个例程中,我们先获取当前时间,并使用 ctime
函数将其转换为字符串并打印。然后,使用 localtime
函数将时间转换为本地时间,并使用 asctime
函数将其转换为字符串并打印。接着,使用 gmtime
函数将时间转换为 GMT 时间,并再次使用 asctime
函数将其转换为字符串并打印。
然后,我们使用 mktime
函数将自定义的 struct tm
时间结构体转换为时间值,并打印转换后的时间值。使用 asctime
函数将时间值转换为字符串并打印。
接下来,我们使用 strftime
函数将时间值格式化为自定义格式的字符串,并打印。
然后,使用 gettimeofday
函数获取当前时间和时区信息,并打印时间值和时区偏移量。
最后,我们使用 settimeofday
函数将系统时间向后延时1小时,并打印设置结果。
运行这个例程,将输出包含以上所有函数的相关信息:
因为settimeofday
函数需要管理员权限才能设置系统时间,所以运行失败。
总结
上面介绍了时间相关的基本概念,譬如GMT时间、UTC时间以及全球24个时区的划分等,并且给大家介绍了Linux系统下常用的时间相关的系统调用和库函数,主要有9个:
参考文献
1:彻底弄懂GMT、UTC、时区和夏令时 - 知乎
2:彻底弄懂GMT、UTC、时区和夏令时 | ChampYin’s Blog
3:Time Zone Abbreviations - Worldwide List
4:Daylight saving time - Wikipedia
5:【正点原子】STM32MP1嵌入式Linux C应用编程指南V1.4 - 第七章
6:ChatGPT
部分图片来源网络,如有侵权请联系我删除。
如有疑问或错误,欢迎和我私信交流指正。
版权所有,未经授权,请勿转载!
Copyright © 2023.05 by Mr.Idleman. All rights reserved.