# 2.1 基础知识

# 2.1.1 时间值

Unix 时间戳,亦称为 Unix 时间、Posix 时间,该值是自协调世界时(Cordinated Universal Time, UTC)1970 年 1 月 1 日 00:00:00 这个特定时间以来所经过的秒数累计值(早期手册称 UTC 为 格林尼治标准时间)。

日历时间包括时间和日期。

系统基本数据类型 time_t 一般是32位或64位整数类型的别名,具体类型取决于当前系统。如果是32位带符号整数,time_t 可以表示的时间到 2038年1月19日03:14:07 UTC 为止;如果是32位无符号整数,则表示到2106年。如果是64位带符号整数,可以表示-2930亿年到+2930亿年的时间范围。

time_t 如果是负数,表示协调世界时之前的时间。

在 shell 中使用 date 命令即可显示当前日期、时间:

$date
2023年 03月 24日 星期五 15:35:33 CST
1
2

使用 cal 显示日历:

$ cal
      三月 2023
日 一 二 三 四 五 六
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
1
2
3
4
5
6
7
8

unix/linux 和其他操作系统的区别:

  1. 以协调同一时间而非本地时间计时。
  2. 可自动进行转换,如变换到夏令时。
  3. 将时间和日期作为一个量值保存。

# 2.2 API

# 2.2.1 time()

函数原型:

#include <time.h>

time_t time(time_t *tloc);
//      tloc:       输出参数,如果非空,时间值也会存放在 *tloc 中。
//      返回值:      成功返回从 1970 年 1 月 1日 00:00:00 起到当前的秒数。
//                  失败返回 -1,并设置错误码 errno。
1
2
3
4
5
6

示例:

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

int main(void)
{
    time_t now = time(NULL);
    printf("now: %ld\n",now);

    return 0;
}
1
2
3
4
5
6
7
8
9
10

# 2.2.2 ctime() (已弃用)

用来将 time_t 类型的值直接输出为人类可读的格式。

函数原型:

#include <time.h>
char* ctime(const time_t* timer);
//          timer:  time_t 指针
//          返回值:类似Www Mmm dd hh:mm:ss yyyy\n\0 的字符串。包含换行符和字符串终止符。
1
2
3
4

示例:

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

int main(void)
{
    time_t now = time(NULL);
    printf("%s",ctime(&now));

    return 0;
}

// 输出结果:Fri Mar 24 16:26:22 2023
1
2
3
4
5
6
7
8
9
10
11
12

# 2.2.3 struct tm

struct tm 是一个用来保存时间的各个组成部分的结构。

定义在 /usr/include/i386-linux-gnu/bits/types/struct_tm.h 中:

 /* ISO C `broken-down time' structure.  */
struct tm
{
  int tm_sec;           /* 秒数 [0-60] (1 leap second) */
  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],星期天 用 0 表示*/
  int tm_yday;          /* 一年的第几天*/
   int tm_isdst;        /* 是否采用夏令时,1 采用,0 未采用■[-1/0/1]*/

 # ifdef»__USE_MISC
   long int tm_gmtoff;      /* Seconds east of UTC.  */
   const char *tm_zone;     /* Timezone abbreviation.  */
 # else
   long int __tm_gmtoff;    /* Seconds east of UTC.  */
   const char *__tm_zone;   /* Timezone abbreviation.  */
 # endif
 };

// struct timespec 最高精确度是纳秒。
struct timespec {
  time_t tv_sec;   // 秒数
  long   tv_nsec;  // 纳秒
};

// struct timeval最高精确度是微秒。
struct timeval {
time_t tv_sec; // seconds
long tv_usec; // microseconds
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

秒可以超过 59 的理由是可以表示闰秒。除了 tm_mday 其他字段都以 0 开始。

# 2.2.4 localtime(),gmtime()

这两个函数可以将 time_t类型的时间 转换成 struct tm结构。

函数原型:

#include <time.h>
struct tm *gmtime(const time_t *timer);
//              timer:  指向要转换的 time_t 对象
//              返回值:  成功返回一个指向 struct tm 结构的指针,其他返回 NULL。

struct tm *localtime  (const time_t *timer);
//                  timer: 指向要转换的 time_t 对象
//                  返回值: 成功指向 struct tm 结构的指针,失败返回 NULL。
1
2
3
4
5
6
7
8

localtime()gmtime() 的区别:

gmtime()time_t 得到的秒数转换成一个 协调统一时间(UTC 时间)的 struct tm 结构体。

localtime() 是将时区考虑在内了,转出的当前时区的本地时间。

# 2.2.5 asctime() (已弃用)

用来将 struct tm 结构,直接输出为人类可读的格式。

函数原型:

#include <time.h>
char *asctime(const struct tm *tm);
            // tm: struct tm 对象
            // 返回值:输出人类可读的格式,自动在行尾添加换行符,失败返回 NULL。
1
2
3
4

示例:

int main(int argc, const char* argv[])
{
    time_t now = time(NULL);
    printf("%s",ctime(&now));
    struct tm *t = gmtime(&now);
    struct tm *y = localtime(&now);

    printf("%d:%d:%d\n",t->tm_hour,t->tm_min,t->tm_sec);
    printf("%s",asctime(t));
    printf("%d:%d:%d\n",y->tm_hour,y->tm_min,y->tm_sec);
    printf("%s",asctime(y));

    return EXIT_SUCCESS;
}
// 运行结果:
Sun Mar 26 15:14:39 2023
15:14:39
Sun Mar 26 15:14:39 2023
15:14:39
Sun Mar 26 15:14:39 2023
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 2.2.6 mktime()

mktime() 函数用于把一个 struct tm 结构转换为 time_t 值。

函数原型:

time_t mktime(struct tm *tm);
//          tm: struct tm 对象
//          返回值:time_t 时间值,失败返回 -1,并设置错误码
1
2
3

# 2.2.7 strftime()

类似 printf() ,通过可用的多个参数来定制产生的字符串。

size_t strftime( char *restrict str, size_t count,
                 const char *restrict format, const struct tm *restrict time );
                 // str: 目标字符串的指针
                 // count: 目标字符串可以接受的最大长度
                 // format: 格式字符串
                 // time: struct tm 结构
                 // 返回值: 若有空间,返回存入数组的字符数,否则返回 0。
1
2
3
4
5
6
7

37 种 ISO C 规定的转换说明:

使用示例:

int main(int argc, const char* argv[])
{
    time_t t;
    struct tm *tmp;
    char buf1[16];
    char buf2[64];

    time(&t);
    tmp = localtime(&t);
    if (strftime(buf1, sizeof(buf1), "time and date: %r, %a %b %d, %Y",tmp) == 0)
        printf("buffer length 16 is too small\n");
    else
        printf("%s\n",buf1);
    if (strftime(buf2, sizeof(buf2), "time and date: %r, %a %b %d, %Y",tmp) == 0)
        printf("buffer length 64 is too small\n");
    else
        printf("%s\n",buf2);

    return EXIT_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

运行结果:

$ ./a.out
buffer length 16 is too small
time and date: 03:35:26 PM, Sun Mar 26, 2023
1
2
3

# 2.2.8 clock()

clock() 函数返回从程序开始执行到当前的 CPU 时钟周期。一个时钟周期等于 CPU 频率的倒数,比如 CPU 的频率如果是 1G Hz,表示1秒内时钟信号可以变化 109 次,那么每个时钟周期就是 10-9 秒。

clock_t clock(void);
//      返回值:成功返回 cpu 时间周期
//             失败返回 -1
1
2
3

为了把这个值转换为秒,应该把它除以常量 CLOCKS_PER_SEC(每秒的时钟周期),这个常量也由time.h定义。

示例:

clock_t start = clock();
// 执行某些操作
clock_t end = clock();

long double seconds = (float)(end - start) / CLOCKS_PER_SEC;
1
2
3
4
5

# 2.2.9 difftime()

用来计算两个时间之间的差异。Unix 系统上,直接相减两个 time_t 值,就可以得到相差的秒数,但是为了程序的可移植性,最好还是使用这个函数。

double difftime( time_t time1, time_t time2 );
1

difftime()函数接受两个 time_t 类型的时间作为参数,计算 time1 - time2 的差,并把结果转换为秒。

注意它的返回值是 double 类型。

使用示例:

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

int main(void) {
  struct tm time_a = {
    .tm_year=82,
    .tm_mon=3,
    .tm_mday=12,
    .tm_hour=4,
    .tm_min=00,
    .tm_sec=04,
    .tm_isdst=-1,
  };

  struct tm time_b = {
    .tm_year=120,
    .tm_mon=10,
    .tm_mday=15,
    .tm_hour=16,
    .tm_min=27,
    .tm_sec=00,
    .tm_isdst=-1,
  };

  time_t cal_a = mktime(&time_a);
  time_t cal_b = mktime(&time_b);

  double diff = difftime(cal_b, cal_a);

  double years = diff / 60 / 60 / 24 / 365.2425;

  // 输出 1217996816.000000 seconds (38.596783 years) between events
  printf("%f seconds (%f years) between events\n", diff, years);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

上面示例中,折算年份时,为了尽量准确,使用了一年的准确长度 365.2425 天,这样可以抵消闰年的影响。

各个时间函数的关系: