POSIX 时间管理

更新时间:
2024-03-14
下载文档

POSIX 时间管理

UTC 时间与本地时间

UTC(Universal Time Coordinated),即世界协调时间,在实际使用中,它等同于 GMT(Greenwich Mean Time),即格林威治标准时间。UTC 时间以 1970 年 1 月 1 日 0 时 0 分 0 秒为基准时间,并以秒为最小计数单位。以英国伦敦格林威治(本初子午线)为中时区,将地球分为东西各 12 个时区,各时区之间相差 1 小时,这就是各个时区的本地时间。由于地球自西向东旋转,因此东时区时间早于中时区时间,西时区时间晚于中时区时间。

#include <time.h>
time_t   time(time_t *time);
time_t   timelocal(time_t *time);

函数 time 原型分析:

  • 此函数返回类型为 time_t 的 UTC 时间。
  • 输出参数 time 为获得的 UTC 时间,与返回值相同,该参数可以为 NULL。

函数 timelocal 原型分析:

  • 此函数返回类型为 time_t 的本地时间。
  • 输出参数 time 为获得的本地时间,与返回值相同,该参数可以为 NULL。

time_t 在某些类 Unix 系统中定义为一个 32 位的有符号整型数,能表示的最大正秒数为 2147483647 秒,它能表示的最晚时间为 2038 年 1 月 19 日 03:14:07,这意味着,如果超过这个时间将溢出。SylixOS 中将 time_t 定义为 64 位有符号整型数,因此不存在上述问题。使用下面的函数可以处理更高精度的时间。

#include <time.h>
int  gettimeofday(struct timeval *tv, struct timezone *tz);
int  settimeofday(const struct timeval *tv, const struct timezone *tz);

函数 gettimeofday 原型分析:

  • 此函数返回 0,没有错误值返回。
  • 输出参数 tv 为一个 timeval 结构的指针,保存获取的时间信息。
  • 输出参数 tz 为一个 timezone 结构的指针,保存获取的时区信息。

结构 timeval 的定义如下:

struct timeval {
    time_t    tv_sec;                              /*  seconds                           */
    LONG     tv_usec;                              /*  microseconds                      */
};

tv_usec 的值为 0~999999,即不超过 1 秒,tv_sec 与 tv_usec 组成了当前的时间。注意该时间是 UTC 时间。结构 timezone 的定义如下:

struct timezone {
    int   tz_minuteswest;                          /*  是格林威治时间往西方的时差    */
                                                   /*  以分钟为单位 (东8区) -60*8    */
    int   tz_dsttime;                              /*  时间的修正方式 必须为0        */
};

tz_minuteswest 的定义与前面讲的不同,这里的定义为相对于格林威治时间向西方的时差,因此东时区的时区值为负数。

#include <time.h>
void   tzset(void);

tzset 函数设置系统的时区,该函数没有任何参数,实际上它内部获取一个名称为 TZ 的环境变量,该变量是对时区的描述,在 SylixOS 中,tzset 函数使用环境变量 TZ 的当前值经过一定的处理后赋值给全局变量 timezone、tzname(目前 SylixOS 不支持 daylight),TZ 的描述如下:

# echo $TZ
CST-8:00:00

CST(China Standard Time)即表示中国标准时间,相对于格林威治向西方的时差为负的 8 小时 0 分 0 秒,实际就是东八区。可使用如下的程序设置当前时区:

#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[])
{
    system("TZ=CST-6:0:0");
    tzset();

    return  (0);
}

以上程序将当前时区设置为东 6 区,以下两条 Shell 命令有完全相同的效果。

# TZ=CST-6:0:0
# tzsync

时间格式转换

上一节获取的时间均是单一地以秒来表示,不符合正常使用习惯,因此有以下函数将该时间转换为我们通常熟悉的时间格式。

#include <time.h>
struct tm *gmtime(const time_t *time);
struct tm *gmtime_r(const time_t *time, struct tm *ptmBuffer);

函数 gmtime 原型分析:

  • 此函数成功返回 tm 结构的指针,失败返回 NULL。
  • 参数 time 为本地时间。

函数 gmtime_r 原型分析:

  • 此函数成功返回 tm 结构的指针,失败返回 NULL。
  • 参数 time 是本地时间。
  • 输出参数 ptmBuffer 是 tm 结构缓冲区。

需要注意的是,gmtime 函数是不可重入的,因此是非线程安全的。通过程序清单 gmtime测试(Test_Gmtime)和程序清单 gmtime_r测试(Test_Gmtime_r) 可以看出这一点。

结构体 tm 描述了我们通常习惯的用法,其定义如下:

struct tm {
    INT  tm_sec;                          /* seconds after the minute - [0, 59]      */
    INT  tm_min;                          /* minutes after the hour   - [0, 59]      */
    INT  tm_hour;                         /* hours after midnight     - [0, 23]      */
    INT  tm_mday;                         /* day of the month     - [1, 31]      */
    INT  tm_mon;                          /* months since January     - [0, 11]      */
    INT  tm_year;                         /* years since 1900                        */
    INT  tm_wday;                         /* days since Sunday    - [0, 6]       */
    INT  tm_yday;                         /* days since January 1     - [0, 365]     */
#define tm_day      tm_yday
    INT  tm_isdst;                        /* Daylight Saving Time flag               */
                                          /* must zero                               */
};

gmtime 将 time_t 类型的时间转换为 tm 类型的时间。从函数名可以得知,它将输入参数 time 当做是 UTC 时间(通常也叫做 GMT 时间),内部并不会进行时区的转换处理。因此,当我们使用 UTC 时间作为参数时,返回的 tm 指针代表的是 UTC 时间,使用本地时间作为参数时,返回的 tm 指针代表的是本地时间。

注意:
gmtime 返回的指针实际上指向的是一个内部全局变量,因此连续调用该函数试图获取不同的时间将得到同一个时间值,如以下程序清单所示。

程序清单 gmtime测试(Test_Gmtime)

#include <time.h>
int main (int argc, char *argv[])
{
    struct tm     *tm_old;
    struct tm     *tm_new;
    time_t           time_old;
    time_t           time_new;
    time_old = time(NULL);
    sleep(10);
    time_new = time(NULL);
    tm_old = gmtime(&time_old);
    tm_new = gmtime(&time_new);
    fprintf(stdout, "old: %d:%d\n", tm_old->tm_min, tm_old->tm_sec);
    fprintf(stdout, "new: %d:%d\n", tm_new->tm_min, tm_new->tm_sec);
    return  (0);
}

运行该程序后,其结果如下,可见两个时间是一样的。实际上 tm_old 与 tm_new 都指向同一个对象,也就是最后一次调用的结果,即 tm_new。

# ./Test_Gmtime
old: 55:12
new: 55:12

为解决以上问题,有与之对应的 gmtime_r 函数,后缀 _r 表示这是一个可重入的版本,它多了一个输出参数 ptmBuffer ,将原来使用的内部全局变量改为让用户给出输出结果的缓冲区对象,这样只要用户使用不同的缓冲区对象,就会得到不同的结果。以下程序清单使用 gmtime_r 处理同样的问题。

程序清单 gmtime_r测试(Test_Gmtime_r)

#include <time.h>
int main (int argc, char *argv[])
{
    struct tm      tm_old;
    struct tm      tm_new;
    time_t         time_old;
    time_t         time_new;
    time_old = time(NULL);
    sleep(10);
    time_new = time(NULL);
    gmtime_r(&time_old, &tm_old);
    gmtime_r(&time_new, &tm_new);
    fprintf(stdout, "old: %d:%d\n", tm_old.tm_min, tm_old.tm_sec);
    fprintf(stdout, "new: %d:%d\n", tm_new.tm_min, tm_new.tm_sec);
    return  (0);
}

程序运行后结果如下,可见两者之间的时间差和预想的一样为 10 秒。

# ./Test_Gmtime_r
old: 55:41
new: 55:51

在上面的程序中,我们必须定义保存结果的两个数据对象,而不是像之前那样仅仅定义两个指针,这样两个对象分别保存了不同的数据,达到了程序本来的目的。上面的例子展示的是在单个线程里面连续调用 gmtime 产生的问题。在多线程中,我们必须使用 gmtime_r。后面还有几个函数也有相同的问题以及同样的解决措施,本书将不再对它们的可重入版本函数进行详细说明,仅仅列出它们的函数原型。

#include <time.h>
struct tm *localtime(const time_t *time);
struct tm *localtime_r(const time_t *time, struct tm *ptmBuffer);

localtime 与 gmtime 的功能相同,但是它内部会进行 UTC 时间到本地时间的转换处理。因此,正确的使用方法是传入的参数为 UTC 时间。

#include <time.h>
char *asctime(const struct tm *ptm);
char *asctime_r(const struct tm *ptm, char *pcBuffer);

函数 asctime 原型分析:

  • 此函数成功返回格式化后的时间字符串指针,失败返回 NULL。
  • 参数 ptm 为一个 tm 结构的指针。

注意,asctime 函数处理参数 ptm 时,将不会作任何时区转换,如同 gmtime 函数一样,程序应该根据需要传入需要的 tm 数据对象。asctime 函数返回的时间字符串的格式如“Tue May 21 13:46:22 1991\n”,因此在使用该函数的可重入版本 asctime_r 时,其参数 pcBuffer 必须保证长度不小于 26 个字节。

#include <time.h>
char  *ctime(const time_t *time);
char  *ctime_r(const time_t *time, char *pcBuffer);

函数 ctime 原型分析:

  • 此函数成功返回格式化后的时间字符串指针,失败返回 NULL。
  • 输入参数 time 为 time_t 类型的指针。

ctime 函数可以将本地时间转换成符合人类习惯的字符串格式,该字符串格式与 asctime 函数转换后的相似,因此其可重入版本函数 ctime_r 中的参数 pcBuffer 长度必须保证不小于 26 个字节。注意 ctime 内部会进行 UTC 时间到本地时间的转换。

#include <time.h>
time_t   mktime(struct tm *ptm);
time_t   timegm(struct tm *ptm);

函数 mktime 原型分析:

  • 该函数成功返回转换后的 UTC 时间。
  • 输入参数 ptm 为 tm 类型的指针。

mktime 函数的功能是将本地时间转换为 UTC 时间,它内部会进行本地时间到 UTC 时间的转换,因此正确的输入参数应该为本地时间。与之功能相同的 timegm 则只是进行 tm 到 time_t 数据类型的转换,正确的输入参数应该为 UTC 时间。

可以调用以下函数计算两个 time_t 类型时间之间的差值。

#include <time.h>
double  difftime(time_t time1, time_t time2);

高精度时间

#include <time.h>
clock_t  clock(void);

函数 clock 返回自系统启动后到目前为止经过的时钟计数,该计数类型为 clock_t。这里的时钟就是指时钟节拍,因此 clock 函数的功能与 Lw_Time_Get 函数相同。

使用 clock_getres 可以获得系统高精度时间的精度。

#include <time.h>
int  clock_getres(clockid_t  clockid, struct timespec *res);

函数 clock_getres 原型分析:

  • 该函数成功返回 0,失败返回错误码。
  • 输入参数 clockid 为时钟源 ID,其类型 clockid_t 有以下定义:。
时钟源名称说明
CLOCK_REALTIME代表实际的物理时间
CLOCK_MONOTONIC单调增长时间
CLOCK_PROCESS_CPUTIME_ID进程从启动开始所消耗的 CPU 时间
CLOCK_THREAD_CPUTIME_ID线程从启动开始所消耗的 CPU 时间
  • 输出参数 res 保存获得的时间精度,可达到 1 纳秒精度,其类型 timespec 的定义如下:。
struct timespec {                                                
    time_t    tv_sec;                                /*  seconds                    */
    LONG       tv_nsec;                              /*  nanoseconds                */
};

由于 CLOCK_REALTIME 代表实际的物理时间,因此对系统时间的修改将会影响它。CLOCK_MONOTONIC 自系统启动后一直增长,且不受任何操作的影响,通常使用 CLOCK_MONOTONIC 时钟源来计算两个操作之间的时间差。

#include <time.h>
int clock_gettime(clockid_t  clockid, struct timespec  *tv);

函数 clock_gettime 原型分析:

  • 该函数成功时返回 0,失败时返回-1 并设置错误码。
  • 参数 clockid 是时钟源,如上表所示。
  • 输出参数 tv 保存获得的高精度时间。

clock_gettime 函数根据不同的时钟源类型获得 struct timespec 类型的时间值,注意,clock_gettime 获得的是 UTC 时间。

#include <time.h>
int clock_settime(clockid_t  clockid, const struct timespec  *tv);

函数 clock_settime 原型分析:

  • 该函数成功时返回 0,失败时返回-1 并设置错误码。
  • 参数 clockid 指定时钟源(只能为 CLOCK_REALTIME)。
  • 参数 tv 为需要设置的高精度时间。

因为 CLOCK_MONOTONIC 不受任何操作的影响,而 CLOCK_PROCESS_CPUTIME_ID 和 CLOCK_THREAD_CPUTIME_ID 仅由系统内部更新,因此 clock_settime 的时钟源只能是 CLOCK_REALTIME。

#include <time.h>
int  clock_nanosleep(clockid_t                clockid, 
                     int                      flags, 
                     const struct timespec   *rqtp, 
                     struct timespec         *rmtp);

函数 clock_nanosleep 原型分析:

  • 此函数成功时返回 0,失败时返回-1 并设置错误号。
  • 参数 clockid 指定了时钟源。
  • 参数 flags 是时间类型(如 TIMER_ABSTIME)。
  • 参数 rqtp 指定了睡眠时间。
  • 输出参数 rmtp 返回睡眠剩余时间。

clock_nanosleep 与 nanosleep 相似可以使进程睡眠指定的纳秒时间,不同的是,如果参数 flags 指定为绝对时间(TIMER_ABSTIME),则 rmtp 不再有意义。睡眠剩余时间的意义通常是指睡眠被信号中断后剩余的时间。

获得进程或线程时钟源

#include <time.h>
int   clock_getcpuclockid(pid_t pid, clockid_t *clock_id);

函数 clock_getcpuclockid 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号。
  • 参数 pid 是进程 ID。
  • 输出参数 clock_id 返回时钟源类型如下表所示。

调用 clock_getcpuclockid 函数可以获得指定进程 pid 的时钟源类型,SylixOS 总是返回 CLOCK_PROCESS_CPUTIME_ID。

时钟源名称说明
CLOCK_REALTIME代表实际的物理时间
CLOCK_MONOTONIC单调增长时间
CLOCK_PROCESS_CPUTIME_ID进程从启动开始所消耗的 CPU 时间
CLOCK_THREAD_CPUTIME_ID线程从启动开始所消耗的 CPU 时间
#include <pthread.h>
int pthread_getcpuclockid(pthread_t thread, clockid_t *clock_id);
  • 此函数成功返回 0,失败返回错误号。
  • 参数 thread 是线程 ID。
  • 输出参数 clock_id 返回时钟类型。

调用 pthread_getcpuclockid 函数可以获得指定线程 thread 的时钟源类型,SylixOS 总是返回 CLOCK_THREAD_CPUTIME_ID。

时间相关的扩展操作

针对 timeval 结构,系统提供了几个有用的宏,方便操作该结构对象,注意这些操作并不是 POSIX 中定义的,其存在于 Linux 和大多数类 Unix 系统中。它们的定义如下(虽然他们都是宏定义,但是这里还是根据它们的实际使用方式,以函数的形式给出它们的定义):

#include <sys/time.h>
void    timeradd(struct timeval *a, struct timeval *b, struct timeval *result);
void    timersub(struct timeval *a, struct timeval *b, struct timeval *result);
void    timerclear(struct timeval *tvp);
int     timerisset(struct timeval *tvp);
int     timercmp(struct timeval *a, struct timeval *b, CMP);

timeradd 将 a、b 时间相加,结构保存于 result 中,内部会自动处理微秒到秒的进位问题,timersub 将 a 时间减去 b 时间,结构保存于 result 中,内部会自动处理微秒到秒的进位问题。注意,这两个操作内部不会有溢出或大小等任何安全性检测,因此结果可能出现不符合预期时间值,这些问题需要应用程序处理。

timerclear 将一个时间值清零,timerisset 检测该时间值是否为 0。

timercmp 比较两个时间,CMP 为一个操作符,如 >、==、<、!=、<= 等。

以下程序展示了这些宏的使用方法。

#include <stdio.h>
#include <sys/time.h>

#define timeval_show(des, tv) \
    fprintf(stdout, des "sec = %llu, usec = %lu.\n",  \
            tv.tv_sec, tv.tv_usec)

int main(int argc, char *argv[])
{
    struct timeval  a;
    struct timeval  b;
    struct timeval  result;

    a.tv_sec      = 100;
    a.tv_usec     = 800000;
    b.tv_sec      = 80;
    b.tv_usec     = 330000;

    timeval_show("time a: ", a);
    timeval_show("time b: ", b);
    timeradd(&a, &b, &result);
    timeval_show("time a + b: ", result);
    timersub(&a, &b, &result);
    timeval_show("time a - b: ", result);
    timerclear(&result);

    if (timerisset(&result)) {
        timeval_show("time is set: ", result);
    } else {
        timeval_show("time is zero: ", result);
    }
    if (timercmp(&a, &b, >)) {
        fprintf(stdout, "time a is larger than time b.\n");
    } else {
        fprintf(stderr, "time a is not larger than time b.\n");
    }
    return  (0);
}

程序运行后,输出以下结果:

# ./timeext_test
time a: sec = 100, usec = 800000.
time b: sec = 80, usec = 330000.
time a + b: sec = 181, usec = 130000.
time a - b: sec = 20, usec = 470000.
time is zero: sec = 0, usec = 0.
time a is larger than time b.
文档内容是否对您有所帮助?
有帮助
没帮助