Linux时间编程

一、Linux时间类型

在Linux系统当中,时间分为两种类型:格林威治时间和日历时间。

Coordinated Universal Time(UTC)是世界标准的时间,即常说的格林威治标准时间(Greenwich Mean Time,GMT);UTC与GMT两者几乎是同一概念,都是指格林威治时间,只不过UTC的称呼更为正式一点。两者的区别是UTC是天文学上的概念,而GMT是基于一个原子钟。

Calendar Time是用“一个标准时间点(如1970年1月1日0点)到此时经过的秒数”来表示的时间,即日历时间;它与格林威治时间不同。

GMT是中央市区,北京在东8区,相差8个小时,所以北京时间=GMT时间+8小时。

++获取系统时间函数有:time()、gettimeofday();++

++设置系统时间函数有:stime()、settimeofday();++

二、Linux时间格式

2.1 time_t 时间类型

time_t类型定义在time.h中:

#ifndef __TIME_T
#define __TIME_T
typedef long time_t
#endif

可见,time_t实际是一个长整型。其值表示为从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的秒数。由于time_t类型长度的限制,它所表示的时间不能晚于2038年1月19日03时14分07秒(UTC)。为了能够表示更久远的时间,可用64位或更长的整型数来保存日历时间。

time_t类型的时间可以通过time()函数获取。

2.2 struct tm时间类型

struct tm结构在time.h中定义为:

#include 
struct tm {
        int tm_sec;         /* seconds */
        int tm_min;         /* minutes */
        int tm_hour;        /* hours */
        int tm_mday;        /* day of the month */
        int tm_mon;         /* month */
        int tm_year;        /* year */
        int tm_wday;        /* day of the week */
        int tm_yday;        /* day in the year */
        int tm_isdst;       /* daylight saving time */
};

ANSI C标准称使用tm结构的这种时间为分解时间(broken-down time)。其成员介绍如下:

  • tm_sec:秒,取值区间为[0,59];
  • tm_min:分,取值区间为[0,59];
  • tm_hour:时,取值区间为[0,23];
  • tm_mday:日期,取值区间为[1,31];
  • tm_mon:月份,取值区间为[0,11];
  • tm_year:年份,其值为1900年至今的年数;
  • tm_wday:星期,取值区间为[0,6],0代表星期天,1代表星期一,以次类推;
  • tm_yday:从年的1月1日开始的天数,取值区间为[0,365],0代表1月1日;
  • tm_isdst:夏令时标识符,使用夏令时,tm_isdst为正;不使用夏令时,tm_isdst为0;不了解情况时,tm_isdst为负。

使用gmtime()和localtime()可将time_t时间类型转换为struct tm结构体;

使用mktime()可将struct tm结构体转换为time_t时间类型;

使用asctime()将struct tm转换为字符串形式。

2.3 struct timeval时间类型

struct timeval结构体在sys/time.h中定义如下:

#include 
struct timeval {
    time_t      tv_sec;     /* seconds:秒 */
    suseconds_t tv_usec;    /* microseconds:微妙 */
};

tv_sec是time_t时间类型,其值也表示为从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当期时刻的秒数。

设置时间函数settimeofday()与获取时间函数gettimeofday()均使用该事件类型作为参数传递。

2.4 struct timespec时间类型

struct timespec结构体在time.h中定义为:

typedef long time_t;
struct timespec {
    time_t   tv_sec;     /* seconds:秒 */
    long     tv_nsec;    /* microseconds:纳妙 */
};

它是POSIX.4标准定义的时间结构,精确度到纳秒,一般由clock_gettime(clockid_t, struct timespec *)获取特定时钟的时间。常用如下4种时钟:

  • CLOCK_REALTIME 统当前时间,从1970年1.1日算起
  • CLOCK_MONOTONIC 系统的启动时间,不能被设置
  • CLOCK_PROCESS_CPUTIME_ID 本进程运行时间
  • CLOCK_THREAD_CPUTIME_ID 本线程运行时间

三、Linux时间编程接口

接下来介绍的时间编程函数接口均属于Linux系统调用函数。

3.1 time()函数

【函数原型】:

#include 
time_t time(time_t *tloc);

【函数说明】:

该函数用于获取日历时间,即从1970年1月1日0点到现在所经历的秒数。参数tloc通常设置为NULL,若tloc非空,time()函数也会将返回值存到tloc指针指向的内存中。

【返回值】:

该函数执行成功返回秒数,失败则返回((time_t)-1)值,错误原因存于errno中。

【使用例程】:

#include 
#include 
    
int main(void)
{
    time_t seconds = 0;
    
    seconds = time((time_t *)NULL);
    printf("seconds = %d\n",seconds);
    
    return 0;
}

执行结果为:

seconds = 1434620150

通常用户得到日历时间的秒数没有实际的意义,但可以为时间转化做一些辅助性质的工作。为了更好的利用时间,用户需要将这些秒数转换为更易接受的时间表示方式,这些表示时间的方式有格林威治时间、本地时间等。

3.2 stime()函数

【函数原型】:

#define _SVID_SOURCE /* glibc2 needs this */
#include 
int stime(time_t *t);

【函数说明】:

该函数用于设置系统时间,参数t指向time_t时间格式变量,表示从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的秒数。

函数执行成功返回0,失败返回-1,并将错误代码放在errno中。

3.3 gmtime()函数

【函数原型】:

#include 
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

【函数说明】:

该函数将time()函数获取的日历时间转化为tm结构体表示的格林威治标准时间,并保存在struct tm结构体中。参数是time()函数获取的日历时间。

gmtime_r()是gmtime()的线程安全版本,在libc5.2.5及以后版本中可用。

3.4 localtime()函数

【函数原型】:

#include 
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

【函数说明】:

该函数将time()函数获取的日历时间转化为tm结构体表示的本地时区时间,并保存至struct tm结构体中。参数是time()函数获取的日历时间。

localtime_r()是localtime()的线程安全版本,在libc5.2.5及以后版本中可用。

【使用实例】:

#include 
#include 
    
int main(void)
{
    time_t seconds = 0;
    struct tm *utc_time = NULL;
    struct tm *local_time = NULL;
    
    seconds = time((time_t *)NULL);
    printf("seconds = %d\n",seconds);
    utc_time = gmtime(&seconds);
    printf("UTC hour is   : %d\n",utc_time->tm_hour);
    local_time = localtime(&seconds);
    printf("Local hour is : %d\n",local_time->tm_hour);
    
    return 0;
}

执行结果为:

seconds = 1434621313
UTC hour is : 9  //格林威治时间是9点
Local hour is : 17 //Linux系统的当前时间是17点

在命令行中执行data命令,可以看到:

Thu Jun 18 17:59:30 CST 2015

说明date命令打印的是Linux系统的本地时间。

3.5 ctime()函数

【函数原型】:

#include 
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

【函数说明】:

该函数将timep指向的日历时间(time_t时间类型)转化为本地时间的字符串形式,并返回该字符串指针,参数是time()函数返回的日历时间。

ctime_r()是ctime()的线程安全版本,在libc5.2.5及以后版本中可用。

【函数使用步骤】:

使用time()获取日历时间—>使用ctime()将日历时间直接转换为本地时间字符串。

3.6 asctime()函数

【函数原型】:

#include 
time_t asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);

【函数说明】:

该函数将struct tm结构体时间转化为字符串形式,并返回该字符串指针,参数是gmtime()函数或localtime()函数返回的struct tm结构体时间。

asctime_r()是asctime()的线程安全版本,在libc5.2.5及以后版本中可用。

【函数使用步骤】:

  • 方法一:使用time()获取日历时间—>使用gmtime()将日历时间转化为格林威治时间—>使用asctime()将struct tm格式的时间转化为字符串;
  • 方法二:使用time()获取日历时间—>使用localtime()将日历时间转化为本地时间—>使用asctime()将struct tm格式的时间转化为字符串;

【使用例程】:

#include 
#include 
    
int main(void)
{
    time_t t;
    char *ptr;
    struct tm *utc;
    struct tm *local;
    
    t = time((time_t *)NULL);
    printf("seconds = %d\n",t);
    ptr = ctime(&t);
    printf("Local time is : %s\n",ptr);
    utc = gmtime(&t);
    ptr = asctime(utc);
    printf("UTC time is :%s\n",ptr);
    local = localtime(&t);
    ptr = asctime(local);
    printf("Local time is :%s\n",ptr);  
    
    return 0;
}

执行结果为:

seconds = 1434622915
Local time is : Thu Jun 18 18:21:55 2015
UTC time is :Thu Jun 18 10:21:55 2015
Local time is :Thu Jun 18 18:21:55 2015

在命令行终端中执行date命令,结果如下:

Thu Jun 18 18:24:32 CST 2015

3.7 mktime()函数

【函数原型】:

#include 
time_t mktime(struct tm *p_tm);

【函数说明】:

该函数将p_tm指向的tm结构体时间类型转换成从1970年1月1日00时00分00秒至今的GMT时间经过的秒数。

【函数实例】:

#include 
#include 
int main(void)
{
    time_t timep;
    struct tm *p_tm;
    
    timep = time((time_t *)NULL);
    printf("time():%d\n",timep);
    p_tm = local(&timep);
    timep = mktime(p_tm);
    printf("time()->localtime()->mktime():%d\n",timep);
    
    return 0;
}

3.8 difftime()函数

【函数原型】:

#include 
double difftime(time_t timep1, time_t timep2);

【函数说明】:

difftime()函数比较参数timep1和timep2时间是否相同,并返回之间相差的秒数,返回类型为double。

【使用实例】:

#include 
#include 
int main(void)
{
    time_t timep1,timep2;
    timep1 = time(NULL);
    sleep(2);
    timep2 = time(NULL);
    printf("The difference is %f seconds\n", difftime(timep1, timep2));
    return 0;
}

3.9 gettimeofday()函数

【函数原型】:

#include 
int gettimeofday(struct timeval *tv, struct timezone *tz);

【函数说明】:

该函数用于获取从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的时间差,并将此时间存入tv指向的结构体中,当地时区信息则放到tz指向的结构体中;该函数常用于计算耗时。函数执行成功返回0,否则返回-1,错误代码存于errno。

【参数说明】:

参数tv用于存放返回从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的时差,时间差以秒或微妙为单位;

参数tz常设置为NULL,因为libc或glibc中并未支持tz,因此在Linux中未使用;struct timezone结构体定义如下:

#include 
struct timezone {
    int   tz_minuteswest;  /* miniutes west of Greenwich:和GMT时间差了多少分钟 */
    int   tz_dsttime;      /* type of DST correction:日光节约时间的状态 */
};

tz_dsttime所代表的状态如下:

DST_NONE    /* not on dst */
DST_USA     /* USA style dst */
DST_AUST    /* Australian style dst */
DST_WET     /* Western European dst */
DST_MET     /* Middle European dst */
DST_EET     /* Eastern European dst */
DST_CAN     /* Canada */
DST_GB      /* Great Britain and Eire */
DST_RUM     /* Rumania */
DST_TUR     /* Turkey */
DST_AUSTALT /* Australian style with shift in 1986 */

【常用用法】:

在做某件事之前调用gettimeofday(),在做完该事情之后调用gettimeofday(),两个函数的tv参数返回值的差就是该事件所消耗的时间。

【使用实例】:

#include 
#include 
    
void function(void)
{
    unsigned int i,j,k;
    
    for(i = 0;i < 500;i++)
        for(j = 0;j < 500;j++)
            k++;
}
int main(void)
{
    time_t t;
    float timeuse;
    struct timeval tpstart,tpend;
    t = time((time_t *)NULL);
    printf("seconds is %d\n",t);
    gettimeofday(&tpstart,NULL);
    printf("seconds is %d,usec is %d\n",tpstart.tv_sec,tpstart.tv_usec);
    function();
    gettimeofday(&tpend,NULL);
    timeuse = 1000*1000*(tpend.tv_sec - tpstart.tv_sec) + tpend.tv_usec - tpstart.tv_usec;
    timeuse /= 1000*1000;
    printf("User time:%f\n",timeuse);
    
    return 0;
}
执行结果为:
User time:0.000727

3.10 settimeofday()函数

【函数原型】:

#include 
int settimeofday(const struct timeval *tv , const struct timezone *tz);

【函数说明】:

该函数把当前时间设成由tv指向的结构体数据;当tz指针不为空时,则设成tz指向的结构体数据。函数执行成功返回0,否则返回-1,错误代码存于errno。只有root权限才能使用此函数修改时间。

3.11 strftime()函数

【函数原型】:

#include 
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

【函数说明】:

该函数将参数tm结构的时间结构,参照参数format所指定的字符串格式做转换,转换后的字符串内容将复制到参数s所指的字符串数组中,该字符串的最大长度为参数max所控制。下面是参数format的格式指令:

  • %a:当地星期日期的名称缩写,如Sun;
  • %A:当地星期日期的名称全称,如Sunday;
  • %b:当地月份的英文缩写;
  • %h:当地月份的英文缩写;
  • %B:当地月份的名称全称;
  • %m:月份,表示法为01~12;
  • %C:以year/100表示年份;
  • %d:月里的天数,表示法为01~31;
  • %e:月里的天数,表示法为1~31;
  • %c:当地适当的日期与时间表示法表示完整时间;
  • %H:以24小时制表示小时数,表示法为00~23;
  • %k:以24小时制表示小时数,表示法为0~23;
  • %l:以12小时制表示小时数,表示法为01~12;
  • %M:分钟数,表示法为00~59;
  • %S:秒数,00~59;
  • %s:从1970年1月1日0时算起至今的UTC时间所经过的秒数;
  • %j:一年中的天数(001~366);
  • %u:一星期中的星期日期,范围1~7,星期一从1开始;
  • %U:一年中的星期数(00~53),一月第一个星期日开始为01;
  • %w:一星期中的星期日期,范围0~6,星期日从0开始;
  • %W:一年中的星期数(00~53),一月第一个星期一开始为01;
  • %p:显示对应的AM或PM;
  • %P:显示对应的am或pm;
  • %R:相当于使用“%H:%M”格式;
  • %r:相当于使用“%I:%M:%S %P”格式;
  • %D:相当于“%m%d%y”格式,如06/19/15;
  • %T:24小时时间表示,相当于“%H:%M:%S”格式;
  • %x:当地适当的日期表示年月日;
  • %X:当地适当的时间表示时分秒;
  • %y:一世纪中的年份表示;
  • %Y:完整的公元年份表示;
  • %Z:使用的时区名称;
  • %n:同\n;
  • %t:同\t;
  • %%:%符号;

返回值复制到参数s所指的字符串数组的总字符数,不包括字符串结束符。如果返回0,表示未复制字符串到参数s内,但不表示一定有错误发生。环境变量TZ和TC_TIME会影响此函数结果。

【使用实例】:

#include 
#include 
#include 
    
int main(int argc, char *argv[])
{
    char outstr[200];
    time_t t;
    struct tm *tmp;
    
    t = time(NULL);
    tmp = localtime(&t);
    if (tmp == NULL) {
        perror("localtime");
        exit(EXIT_FAILURE);
    }
    if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
        fprintf(stderr, "strftime returned 0");
        exit(EXIT_FAILURE);
    }
    
    printf("Result string is \"%s\"\n", outstr);
    exit(EXIT_SUCCESS);
} /* main */

3.12 strptime()函数

【函数原型】:

#define _XOPEN_SOURCE /* glibc2 needs this */
#include 
char *strptime(const char *s, const char *format, struct tm *tm);

【函数说明】:

该函数与scanf类似,同strftime()函数相反,将一个字符串格式时间解释称为struct tm格式时间。

【使用实例】:

#include 
#include 
    
int main() {
    struct tm tm;
    char buf[255];
    
    strptime("2001-11-12 18:31:01", "%Y-%m-%d %H:%M:%S", &tm);
    strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &tm);
    puts(buf);
    return 0;
}

3.13 clock()函数

【函数原型】:

#include 
clock_t clock(void);

【函数说明】:

该函数用来返回进程所占用CPU的大约时间。

3.14 ftime()函数

【函数原型】:

#include 
int ftime(struct timeb *tp);

【函数说明】:

该函数将日前时间日期由参数tp所指向的结构体输出,该函数无论成功还是失败都返回0。struct timep结构体定义如下:

struct timeb{
    time_t time;  /* 从1970年1月1日0点至今的秒数 */
    unsigned short millitm;  /* 为千分之一秒 */
    short timezone; /* 为目前时区和Greenwich相差的时间,单位为妙 */
    short dstflag;  /* 为日光节约时间的修正状态,若非0为启用日光节约时间的修正 */
};

ftime()函数在4.2的BSD中支持。

3.15 tzset()函数

【函数原型】:

#include 
void tzset(void);
extern char *tzname[2];
extern long timezone;
extern int daylight;

【函数说明】:

该函数将环境变量TZ设给全局变量tzname,也就是从环境变量取得目前当地的时区,时间转换函数会自动调用此函数。若TZ为设置,tzname会依照/etc/localtime找出最接近当地的时区。若TZ为NULL,或是无法判认,则使用UTC时区。此函数总是成功,并初始化tzname。

四、Linux休眠编程接口

有时候为了降低CPU的占用率及程序的运行速度,需要让程序休眠一段时间,在Linux应用程序中,可以使用:sleep()和usleep()函数实现。对应Linux内核中,休眠的函数有:udelay()、ndelay()、mdelay()。delay函数是忙等待,占用CPU时间,而sleep函数使调用进程休眠,并不占用CPU时间。

4.1 sleep()函数

【函数原型】:

#include 
unsigned int sleep(unsigned int seconds);

【函数说明】:

该函数使调用sleep()的程序休眠,直到seconds秒之后才被唤醒。该函数返回0(seconds时间到了),或返回seconds还剩余的秒数。

4.2 usleep()函数

【函数原型】:

/* BSD version */
#include 
void usleep(unsigned long usec);
    
/* SUSv2 version */
#define _XOPEN_SOURCE 500
#include 
int usleep(useconds_t usec);

【函数说明】:

该函数使调用usleep()的程序休眠,直到usec微妙之后才被唤醒。该函数睡眠的实际时间会比usec略有延长,因为任何系统活动和时间处理调用的微处理粒度。

该函数执行成功返回0,失败返回-1。该函数可以被信号唤醒,同时返回EINTR。

五、Linux定时闹钟编程接口

Linux应用程序为我们的每一个进程提供了一个定时闹钟alarm,当定时器指定的时间到时,系统会向调用进程发送SIGALARM信号,如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程;当然也可以通过signal()函数向系统注册一个自己的定时闹钟处理函数。关于信号处理方面的知识,可参照信号相关章节的内容。

5.1 alarm()函数

【函数原型】:

#include 
unsigned int alarm(unsigned int seconds);

【函数说明】:

alarm()函数向系统设定一个闹钟,并在闹钟时间到时内核向该进程发送SIGALRM信号。

【注意事项】:

  • 一个进程只能有一个alarm闹钟;
  • 闹钟时间到了后,若不再次调用alarm(),将不会有新的闹钟产生;
  • 任何以seconds非0的调用,都将重新更新闹钟定时时间,并返回上一个闹钟剩余时间;若为第一次设置闹钟,则返回0;以seconds为0的调用,表示取消以前的闹钟,并将剩余时间返回。
  • 在Linux系统中提到,sleep()有可能是使用alarm()来实现的,因此,在一个进程中同时使用alarm()和sleep()并不明智。

【使用实例】:

#include 
#include 
    
int main(void)
{
    int ret;
    sleep(3);
    ret = alarm(3);
    printf("%d\n",ret);
    pause();
    printf("This will not been print.\n");
    return 0;
}

执行结果为:

0   //第一次为该进程设置闹钟
Alarm clock

注意最后一个printf并未打印出来,因为程序没有注册SIGALARM信号处理函数,系统默认处理动作是结束alarm()调用进程。

5.2 setitimer()函数

Linux系统为每个进程提供了三个独立的计时器,每个计时器在不同的时域中递减。当任何一个定时器到点了,都会向进程发送一个信号,并且重启计时器。

【函数原型】:

#include 
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);

【函数说明】:

getitimer()函数获取ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF三个定时器中的一个的时间信息,并保存到value指向的对象中。

setitimer()函数设置ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF三个定时器中的一个的定时时间为value指向的对象,若ovalue非空,则将老的时间信息保存到该对象中。

setitimer()比alarm()功能更加强大,支持3种类型的定时器(但setitimer()并不是标准C库的函数):

  • ITIMER_REAL:以系统真实的时间来计算,它会送出SIGALRM信号;
  • ITIMER_PROF:以该进程在用户态下和内核态下所费时间来计算,它送出SIGPROF信号;
  • ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号;

该函数执行成功返回0,失败返回-1,并将错误号存放在errno中。

【参数说明】:

witch用于指定定时器类型,其取值为上面三种之一;value是struct itimerval的一个实例;ovalue参数不做处理。struct itimerval结构体定义如下:

struct itimerval {
    struct timeval it_interval; /* next value */
    struct timeval it_value;    /* current value */
};
struct timeval {
    long tv_sec;                /* seconds */
    long tv_usec;               /* microseconds */
};

it_value变量用于设置计时器的计时时间,为0时表示禁止;it_interval变量用于设置当计时器到时,重置的时间,从而实现循环定时。也就是说,计时器从it_value开始递减,当递减到0时,向进程发送一个信号,并重置定时器为it_interval,如此循环,从而可以实现循环闹钟的功能。

【使用实例】:

#include 
#include 
#include 
#include 
void sig_func(int signo)
{
    printf("Catch a signal.\n");
    static int realCnt = 0;
    static int virtualCnt = 0;
    switch(signo)
    {
        case SIGALRM:
            printf("The %d times:SIGALRM\n",realCnt++);
            break;
        case SIGVTALRM:
            printf("The %d times:SIGVTALRM\n",virtualCnt++);
            break;
        default:
            break;
    }
}
int main(void)
{
    struct itimerval tv,tv1,otv;
    signal(SIGALRM,sig_func);
    signal(SIGVTALRM,sig_func);
    //how long to run the first time
    tv.it_value.tv_sec = 3;
    tv.it_value.tv_usec = 0;
    //after the first time,how long to run next time
    tv.it_interval.tv_sec = 2;
    tv.it_interval.tv_usec = 0;
    if(setitimer(ITIMER_REAL,&tv,&otv) != 0)
    {
        printf("setitimer err %d\n",errno);
        return -1;
    }
    tv1.it_value.tv_sec = 1;
    tv1.it_value.tv_usec = 0;
    tv1.it_interval.tv_sec = 1;
    tv1.it_interval.tv_usec = 0;
    if(setitimer(ITIMER_VIRTUAL,&tv1,&otv) != 0)
    {
        printf("setitimer err %d\n",errno);
        return -1;
    }
    while(1)
    {
        sleep(30);
        printf("otv:%d,%d,%d,%d\n",otv.it_value.tv_sec,otv.it_value.tv_usec,
                                        otv.it_interval.tv_sec,otv.it_interval.tv_usec);
    }
    return 0;
}

你可能感兴趣的:(Linux时间编程)