时间与日期
通常对于一个程序来说可以确定时间与日期是十分用的。也许他要记录其运行的时间,或者是他要一个特定的时间改变其形为。例如,一个游戏程序也许不会在工作时间运行,或者是一个备份调度会在启动自动备份之前等待早些的工作完成。
所有的Unix系统对于时间与日期都使用相同的起始点:1970年1月1日午午夜GMT。这是Unix的创世纪,而Linux也不例外。而在Linux系统中所有的时间都是以秒记量的。这与MS-DOS处理时间的方式相似,所不同是MS-DOS是由1980年开始的。其他的系统使用其他的记时起始时间。
时间是使用一个定义的time_t类型来处理。这是一个足够大的整数类型来包含以秒计的日期与时间值。在Linux系统上,他是一个长整数,其定义以及相关的操作函数都定义在头文件time.h中。
不要认为时间是32位的。在Unix以及Linux系统上使用一个32位的time_t类型,而这个时间会在2038年重叠。到那时,我们希望这些系统都使用一个大于32位的time_t类型。
#include <time.h>
time_t time(time_t *tloc);
我们可以调用time函数来得到一个底层的时间值,这个函数会返回由纪元起点开始的秒数。如果tloc不为空指针,函数就会将返回值写入tloc所指的变量中。
试验--时间
这里是一个简单的时间程序,envtime.c,来演示time函数:
#include <time.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
int i;
time_t the_time;
for(i = 1; i <= 10; i++) {
the_time = time((time_t *)0);
printf(“The time is %ld/n”, the_time);
sleep(2);
}
exit(0);
}
当我们运行这个程序时,他会每隔2秒打印出底层时间值。
$ ./envtime
The time is 1044695820
The time is 1044695822
The time is 1044695824
The time is 1044695826
The time is 1044695828
The time is 1044695830
The time is 1044695832
The time is 1044695834
The time is 1044695836
The time is 1044695838
工作原量
这个程序使用一个空指针参数来调用time函数,这个函数会返回以秒计的时间与日期值。程序会休眠2秒,并且重复调用time函数10次。
使用由1970年开始的以秒计的时间与日期值对于估量某件事持续多少时间是十分有用的。我们只需要简单的使用两个由time调用得到的时间进行减法操作即可。然而结过仔细的考虑,ISO/ANSIC C标准委员会并没有说时time_t类型要用于计量以秒计的时间间隔值,所以他们开发了一个函数,difftime,这个函数会计算两个time_t值之间的间隔,并且返回一个double类型:
#include <time.h>
double difftime(time_t time1, time_t time2);
difftime函数计算两个时间值之间的间隔,并且返回time1-time2的浮点值。对于Linux系统而言,由time返回的值是秒数,并且可以进行操作,但是为了移植考虑,我们应使用difftime函数。
为了以更为有意义的方式来表示时间与日期,我们需要将时间值转换为可以识别的时间与日期。这有标准的函数可以帮助我们做到。
gmtime函数将底层的时间值分隔并且存放到包含更为通用域的一个结构中:
#include <time.h>
struct tm *gmtime(const time_t timeval);
tm结构的定义至少包含下列成员:
成员 描述
int tm_sec 秒,0-61
int tm_min 分,0-59
int tm_hour 时,0-23
int tm_mday 一月中的天,1-31
int tm_mon 一年中的月,0-11(一月为0)
int tm_year 由1900年起的年数
int tm_wday 一星期中的天,0-6(星期日为0)
int tm_yday 一年中的天,0-365
int tm_isdst 有效的白天数
试验--gmtime
这里有一个程序,gmtime.c,使用tm结构与gmtime函数打印当前时间与日期:
#include <time.h>
#include <stdio.h>
int main()
{
struct tm *tm_ptr;
time_t the_time;
(void) time(&the_time);
tm_ptr = gmtime(&the_time);
printf(“Raw time is %ld/n”, the_time);
printf(“gmtime gives:/n”);
printf(“date: %02d/%02d/%02d/n”,
tm_ptr->tm_year, tm_ptr->tm_mon+1, tm_ptr->tm_mday);
printf(“time: %02d:%02d:%02d/n”,
tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec);
exit(0);
}
当我们运行这个程序时,我们会得到更可读的时间与日期值:
$ ./gmtime; date
Raw time is 1044696004
gmtime gives:
date: 103/02/08
time: 09:20:04
Sat Feb 8 09:20:04 GMT 2003
工作原理
这个程序调用time函数得到一个底层的时间值,然后调用gmtime将其转换为一个具有时间与日期值的结构。程序会使用printf函数打印输出。严格来讲,我们不应使用这种方式打印原始时间,因为并不能保证在所有的系统他都是一个long类型。在gmtime函数之后,我们立即运行date命令来比较其输出。
然而,在这里我们一个小问题。如果我们在一个不使用格林尼治时间的时区运行这个程序,或者是我们本地的白天存储时间是有效的(问题?),我们就会注意到这个时间并不正确。这是因为gmtime是使用GMT返回时间。Linux与Unix系统这样做,从而世界上的程序与系统都是同步的。不同时区在同一时间创建的文件会显示出具有相同的创建时间。要查看本地时间,我们需要使用localtime函数来代替。
#include <time.h>
struct tm *localtime(const time_t *timeval);
localtime函数与gmtime相同,所不同的是他会返回一个包含为本地时区进行过调整后的结构。如果我们再次试验gmtime程序,但是使用localtime函数来替换gmtime,我们就会看到一个正确的时间与日期报告。
要将一个打乱的tm结构转换为一个原始的time_t值,我们可以使用mktime函数:
#include <time.h>
time_t mktime(struct tm *timeptr);
如果结构不可以表示为一个time_t值,mktime函数会返回-1。
与date程序所提供的时间与日期等机器值相反,为了友好,我们可以使用asctime与ctime函数:
#include <time.h>
char *asctime(const struct tm *timeptr);
char *ctime(const time_t *timeval);
asctime函数返回一个代表tm结构timeptr给出的时间与日期的字符串。返回的字符串的格式如下:
Sun Jun 6 12:30:34 1999/n/0
他总是26个字符长的固定格式。函数ctime与下面的函数调用等同:
asctime(localtime(timeval))
他使用一个原始时间值作为参数,并将其转换为一个更可读的本地时间。
试验--ctime
让我们使用下面的代码来实际的看一下ctime:
#include <time.h>
#include <stdio.h>
int main()
{
time_t timeval;
(void)time(&timeval);
printf(“The date is: %s”, ctime(&timeval));
exit(0);
}
将其保存为ctim.c,编译运行,我们应可以看到下面的输出:
$ ./ctime
The date is: Sat Feb 8 09:21:17 2003
工作原理
ctime.c程序调用time函数来得到一个底层的时间值,然后让ctime来完成所有的困难工作,将其转换为一个可读的字符串并打印输出。
为了可以对实际的时间与日期字符串进行更多的控制,Linux与现代的类Unix系统提供了strftime函数。这与用在日期与时间上的sprintf函数相类似,且其工作方式也相似:
#include <time.h>
size_t strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr);
strftime函数会格式化由timeptr指向的tm结构表示的时间与日期,并将其结构放在字符串s中。字符串被指明至少maxsize字符长。format字符串用来控制写入字符串的字符。与printf相似,他包含要传递到字符串的通常字符与格式化时间与日期元素的转义说明。转义说明包括:
转义符 描述
%a 简写的星期名
%A 全写的星期名
%b 简写的月名
%B 全写的月名
%c 日期与时间
%d 一月中的天,01-31
%H 时,00-23
%I 12小时时钟表示的小时,01-12
%j 一年中的天,001-366
%m 一年中的月,01-12
%M 分,00-59
%P a.m或p.m
%S 秒,00-61
%u 星期中的天,1-7(星期1为1)
%U 一年中的星期,01-53(星期日为一个星期的第一天)
%V 一年中的星期,01-53(星期一为一个星期的第一天)
%w 一个星期中的天,0-6(星期日为0)
%x 本地格式日期
%X 本地格式时间
%y 小于1900的年号
%Y 年
%Z 时区名字
%% A%字符
所以,由date程序所指定的通常日期对应于strftime中的格式化字符串为
“%a %b %d %H:%M:%S %Y”
为了有助于读取日期,我们可以使用strptime函数,这个函数会将一个表示日期与时间的字符串作为参数,并且创建一个表示同样日期与时间tm结构:
#include <time.h>
char *strptime(const char *buf, const char *format, struct tm *timeptr);
format字符串的组成与strftime的格式化字符串完全相同。strptime的动作形为与sscanf相似,因为他们都搜索字符串,查找标识的域,然后将他们写入变量。在这里的变量为依据格式化字符串要填充的tm结构的成员。然而,与strftime的转义字符比起来,strptime的转义字符是不严密的,因为strptime会在天与月上允许简写与全名两种形式。每一个表示都会匹配strptime中的一个%a说明符。strftime总是在小于10的数字上添加0,而strptime会将其看作可选的。
strptime会返回一个指向转换操作中最后一个处理字符之后的字符的指针。如果遇到不可以转换的字符,转换操作就简单的在那里停止。调用程序需要检测以确保所传递的字符都是可以写入tm结构的有意义的字符。
试验--strftime与strptime
下面我们看一下下面的程序中所使用的转义字符:
#include <time.h>
#include <stdio.h>
int main()
{
struct tm *tm_ptr, timestruct;
time_t the_time;
char buf[256];
char *result;
(void) time(&the_time);
tm_ptr = localtime(&the_time);
strftime(buf, 256, “%A %d %B, %I:%S %p”, tm_ptr);
printf(“strftime gives: %s/n”, buf);
strcpy(buf,”Sat 26 July 2003, 17:53 will do fine”);
printf(“calling strptime with: %s/n”, buf);
tm_ptr = ×truct;
result = strptime(buf,”%a %d %b %Y, %R”, tm_ptr);
printf(“strptime consumed up to: %s/n”, result);
printf(“strptime gives:/n”);
printf(“date: %02d/%02d/%02d/n”,
tm_ptr->tm_year % 100, tm_ptr->tm_mon+1, tm_ptr->tm_mday);
printf(“time: %02d:%02d/n”,
tm_ptr->tm_hour, tm_ptr->tm_min);
exit(0);
}
当我们运行这个程序strftime.c时,我们会得到下面的输出:
$ ./strftime
strftime gives: Sunday 06 June, 11:55 AM
calling strptime with: Sat 26 July 2003, 17:53 will do fine
strptime consumed up to: will do fine
strptime gives:
date: 03/07/26
time: 17:53
工作原理
strftime程序通过调用time与localtime函数得到当前的本地时间。然后他通过使用一个合适的格式化字符串作为参数来调用strftime将其转换为可读的格式。为了演示strptime的用法,程序设置了一个包含日期与时间的字符串,然后调用strptime来得到原始的时间与日期值,并且打印输出。转义字符%R是strptime中%H:%M的简写形式。
在这里要注意的一点就是,为了可以成功的搜索一个日期,strptime函数需要一个精确的格式化字符串。通常,他并不会精确的搜索由用户读取的时间,除非这个格式是非常严格的。
也许当我们编译strftime.c时,编译器会提出警告。这是因为GNU库默认并没有声明strftime。这个问题的修正方法就是通过在包含time.h之前添加下面的行来显示的请求X/Open标准特征:
#define _XOPEN_SOURCE