时间戳 time since epoch 与时区 timezone 无关

你说的曾经没有我的故事 提交于 2020-05-05 21:08:42

时间戳和时区是两个概念,互不影响。同一时刻在不同时区获取的时间戳是相同的。比如,同一时刻,北京时间(东 8 区)和东京时间(东 9 区)是不同的,东京时间比北京时间快 1 个小时,这里提到的北京时间和东京时间都是本地时间。在同一时刻,这些本地时间的时间戳是相同的(之前,我就错误的认为时间戳分本地时间戳和 UTC 时间戳,不同时区的时间戳是不同的,直到有次和技术部的平台组对需求后,才明白原来我一直搞错了,惭愧惭愧!)。

什么是时间戳以及如何获取时间戳呢,可以参考 cplusplus timecppreference time

下面示例展示了将时间戳 time since epoch 分别转换成以 UTC 和本地时间表示的 calendar time 。由于我处于东 8 区,所以当前时间是 5 月 5 日,而 UTC 时间还是 5 月 4 日。

// gcc -std=gnu11 test.c
#include <stdio.h>
#include <time.h>

void print(struct tm *t, const char *desc)
{
    printf("%s: %d-%02d-%02d %02d:%02d:%02d wday:%d yday:%d isdst:%d\n",
            desc, 1900+t->tm_year, t->tm_mon, t->tm_mday,
            t->tm_hour, t->tm_min, t->tm_sec,
            t->tm_wday, t->tm_yday, t->tm_isdst
    );
}

int main() {
    time_t ts;
    time(&ts);
 
    struct tm utc_tm;         
    print(gmtime_r(&ts, &utc_tm), "UTC  ");
    printf("UTC  : %s", asctime(gmtime_r(&ts, &utc_tm)));
        
    struct tm local_tm;
    print(localtime_r(&ts, &local_tm), "LOCAL");
    printf("LOCAL: %s", asctime(localtime_r(&ts, &local_tm)));
    // Output:
    // UTC  : 2020-04-04 16:26:52 wday:1 yday:124 isdst:0
    // UTC  : Mon May  4 16:26:52 2020
    // LOCAL: 2020-04-05 00:26:52 wday:2 yday:125 isdst:0
    // LOCAL: Tue May  5 00:26:52 2020
}

时间戳在日常应用中有如下形式:

  • 调用 time 函数获取时间戳,然后根据使用 UTC 形式还是本地形式,调用 gmtime_rlocaltime_r 进行转换。若要获取字符串形式,可调用 asctime 函数或手动格式化(如上述 print 函数)。
  • 调用 mktime本地时间转换成时间戳。

这里有一点要小心,不要被绕进去,调用 time 获取时间戳,然后调用 gentime_r 产生 tm ,此时将 tm 再转成时间戳,这个时间戳和前面 time 返回的时间戳是不同的。如下示例。

// gcc -std=gnu11 test.c
#include <stdio.h>
#include <time.h>

void print(struct tm *t, const char *desc)
{
    printf("%s: %d-%02d-%02d %02d:%02d:%02d wday:%d yday:%d isdst:%d\n",
            desc, 1900+t->tm_year, t->tm_mon, t->tm_mday,
            t->tm_hour, t->tm_min, t->tm_sec,
            t->tm_wday, t->tm_yday, t->tm_isdst
    );
}

int main() {
    time_t ts;
    time(&ts);
 
    struct tm utc_tm;
    struct tm local_tm;
    print(gmtime_r(&ts, &utc_tm), "BEFORE UTC  ");
    printf("BEFORE LOCAL:%s", asctime(localtime_r(&ts, &local_tm)));
                                                           
    // 调用 mktime 此时 utc_tm 被认为表示的是本地时间
    time_t ts2 = mktime(&utc_tm);
    print(gmtime_r(&ts2, &utc_tm), "AFTER UTC   ");
    printf("AFTER LOCAL :%s", asctime(localtime_r(&ts2, &local_tm)));
    return 0;
    // Output:
    // BEFORE UTC  : 2020-04-04 16:48:03 wday:1 yday:124 isdst:0
    // BEFORE LOCAL:Tue May  5 00:48:03 2020
    // AFTER UTC   : 2020-04-04 08:48:03 wday:1 yday:124 isdst:0
    // AFTER LOCAL :Mon May  4 16:48:03 2020
}

由此可见,C 标准库其实是把本地时间转换成时间戳。项目中某个这种转换函数,由于没有文档,我一开始不了解函数参数是本地时间,错误传递了 UTC 时间,还产生了 BUG 。下次碰见类似的接口,即使没有文档,也可以根据 C 标准库假定传递的是本地时间。


了解了基本概念后,回头开头所说的时间戳与时区无关。下面通过示例来验证一下。

// gcc -std=gnu11 test.c
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main() {
    time_t ts;
    time(&ts);
    printf("UTC  : %s", asctime(gmtime(&ts)));
    printf("LOCAL: %s", asctime(localtime(&ts)));
    printf("Epoch: %ld\n", ts);

    struct tm local_tm = {
        .tm_year = 120, .tm_mon = 5, .tm_mday = 5,
        .tm_hour = 10, .tm_min = 0, .tm_sec = 0,
        .tm_isdst = 0,
    };
    time_t convert_local_ts = mktime(&local_tm);
    printf("CONVERT Epoch: %ld\n", convert_local_ts);
    printf("CONVERT UTC  : %s", asctime(gmtime(&convert_local_ts)));

    printf("\nchange timezone to tokyo:\n");
    putenv("TZ=Asia/Tokyo");
    printf("UTC  : %s", asctime(gmtime(&ts)));
    printf("Tokyo: %s", asctime(localtime(&ts)));

    time_t tokyo_ts;
    time(&tokyo_ts);
    printf("UTC  : %s", asctime(gmtime(&tokyo_ts)));
    printf("Tokyo: %s", asctime(localtime(&tokyo_ts)));
    printf("Epoch: %ld\n", tokyo_ts);

    struct tm tokyo_tm = {
        .tm_year = 120, .tm_mon = 5, .tm_mday = 5,
        .tm_hour = 11, .tm_min = 0, .tm_sec = 0,
        .tm_isdst = 0,
    };
    time_t convert_tokyo_ts = mktime(&tokyo_tm);
    printf("CONVERT Epoch: %ld\n", convert_tokyo_ts);
    printf("CONVERT UTC  : %s", asctime(gmtime(&convert_tokyo_ts)));
	return 0;
	// Output:
    // UTC  : Tue May  5 01:55:14 2020
    // LOCAL: Tue May  5 09:55:14 2020
    // Epoch: 1588643714
    // CONVERT Epoch: 1591322400
    // CONVERT UTC  : Fri Jun  5 02:00:00 2020
    // 
    // change timezone to tokyo:
    // UTC  : Tue May  5 01:55:14 2020
    // Tokyo: Tue May  5 10:55:14 2020
    // UTC  : Tue May  5 01:55:14 2020
    // Tokyo: Tue May  5 10:55:14 2020
    // Epoch: 1588643714
    // CONVERT Epoch: 1591322400
    // CONVERT UTC  : Fri Jun  5 02:00:00 2020
}

  • 观察输出,在 putenv("TZ=Asia/Tokyo"); 前的当前时间是 Tue May 5 09:55:14 2020 ,而之后的当前时间是 Tue May 5 10:55:14 2020 ,由于东京处于东九区,比北京时间快 1 个小时,所以可以确认切换时区成功。
  • 观察 tstokyo_ts 值相同,可以认为时间戳在切换时区前后是相同的。
  • 观察 convert_local_tsconvert_tokyo_ts 值相同,同样验证了时间戳在不同时区是一致的。不同时区的本地时间不同,但在同一时刻的 UTC 时间是一致的,也即时间戳是一致的。

  • 什么是闰秒 leaping second ?
  • 什么是夏令时 daylight saving time ?
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!