Unix时间戳-日期转换

Unix时间戳:Unix 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。此值也和时区无关。一个Unix 时间戳,在不同时区对应不同时区日期时间。

1、获取Unix时间戳

在linux系统中使用time(NULL)获取到的就是Unix时间戳(单位:秒,与时区无关,与夏令时无关)。

time(NULL); // 获取unix时间戳,与时区无关

同样gettimeofday获取的也是Unix时间戳(单位:微妙,与时区无关,与夏令时无关)。

    uint64_t intervals = 0;
    timeval tv;
    gettimeofday(&tv, NULL);
    intervals = tv.tv_sec * 1000000 + tv.tv_usec;

注意C/C++与golang不同,golang中使用如下方式获取Unix时间戳(单位:秒),不受夏令时影响

time.Now().Unix()

2、Unix时间戳与日期

在编程中全部使用unix时间戳一般不会造成混乱(注意夏令时影响),容易出问题的地方在于与日期的转换。

Unix时间戳与时区无关,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。因此,严格来说,不管你处在地球上的哪个地方,任意时间点的时间戳都是相同的。Unix时间戳对应的日期就是格林威治所在时区的日期,即0时区的日期。

1)Unix时间戳转格林尼治标准时间(GMT):

    time_t time = time(NULL);
    struct tm  utc_tm;
    gmtime_r(&time, &utc_tm); // 生成的日期为0时区日期。时区为0

2)Unix时间戳转本地日期:

    time_t time = time(NULL);
    struct tm  local_tm;
    localtime_r(&time, &local_tm); // 转换为本地日期时间,携带本地时区,受夏令时影响

struct tm结构体定义如下,

struct tm {
   int         tm_sec;    /* Seconds          [0, 60] */
   int         tm_min;    /* Minutes          [0, 59] */
   int         tm_hour;   /* Hour             [0, 23] */
   int         tm_mday;   /* Day of the month [1, 31] */
   int         tm_mon;    /* Month            [0, 11]  (January = 0) */
   int         tm_year;   /* Year minus 1900 */
   int         tm_wday;   /* Day of the week  [0, 6]   (Sunday = 0) */
   int         tm_yday;   /* Day of the year  [0, 365] (Jan/01 = 0) */
   int         tm_isdst;  /* Daylight savings flag */

   long        tm_gmtoff; /* Seconds East of UTC */
   const char *tm_zone;   /* Timezone abbreviation */
};

tm_gmtoff, tm_zone:
    Since glibc 2.20:
        _DEFAULT_SOURCE
    glibc 2.20 and earlier:
        _BSD_SOURCE

3)本地日期转Unix时间戳:

输入日期不变,在不同时区中,mktime返回值不同,受夏令时影响。另外与tm_gmtoff设置无关。

mktime返回值的意义:当前时区的日期时间为xxxx-xx-xx xx:xx:xx时,返回对应的unix时间戳(注意unix时间戳和时区无关,可以这样理解:unix时间戳和0时区日期时间一一对应,0时区日期时间在不同时区对应的日期时间不同)。

例如输入为:2024-04-18 12:30:20

东八区(北京时间)结果为:1713414620

西五区(纽约时间)结果为:1713457820

两者差距为12小时(本来应该差13小时,但是由于夏令时的影响,西五区(纽约时间)实为西四区时间)。

void data_time_string_to_int(const char* date, int* year, int* month, int* day, int* hour, int* minute, int* second)
{
    if (14 == strlen(date)) { //20200707213148
        if(sscanf(date, "%4d%2d%2d%2d%2d%2d",
            year, month, day, hour, minute, second));
    } else if (19 == strlen(date)) { //2020-07-07 21:33:15

        if(sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d",
            year, month, day, hour, minute, second));
    }
}

time_t date_2_time(const char* date)
{
    int year = 1900, month = 1, day = 0, hour = 0, minute = 0, second = 0;
    data_time_string_to_int(date, &year, &month, &day, &hour, &minute, &second);
    struct tm tmDate = { 0 };
    //开始时间
    tmDate.tm_year = year - 1900;
    tmDate.tm_mon = month - 1;
    tmDate.tm_mday = day;
    tmDate.tm_hour = hour;
    tmDate.tm_min = minute;
    tmDate.tm_sec = second;
    // -1:自动判断系统是否处于夏令时状态,如果是受系统夏令时影响。如果设置为0,则不受夏令时影响
    tmDate.tm_isdst = -1; 
    // mktime的结果为系统时区对应的UTC时间,受夏令时影响
    return mktime(&tmDate); 
}

4)RFC3339格式(2018-06-19T14:28:00+08:00)转Unix时间戳


void data_time_string_to_int_rfc3339(const char* date, int* year, int* month, int* day, 
                                               int* hour, int* minute, int* second, int* msec, float* timeZone)
{
    if (strlen(date) == 14)	{
        if (sscanf(date, "%4d%2d%2d%2d%2d%2d",
                    year, month, day, hour, minute, second));

        *msec = 0;
        *timeZone = get_time_zone();
    } else if (strlen(date) == 19) {
        if (sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d",
                    year, month, day, hour, minute, second));

        *msec = 0;
        *timeZone = get_time_zone();
    } else {
        if (sscanf(date, "%4d-%2d-%2dT%2d:%2d:%2d",
                    year, month, day, hour, minute, second));

        *msec = 0;

        //尝试解析2018-06-19T14:28:00+08:00
        const char* dot = strstr(date + 12, ".");
        const char* plus = strstr(date + 12, "+");
        const char* minus = strstr(date + 12, "-");
        const char* zoreB = strstr(date + 12, "Z");
        const char* zoreb = strstr(date + 12, "z");
        if (dot) {
            char ms[8] = { 0 };
            if (strncpy_s(ms, sizeof(ms), dot + 1, 3));
            *msec = atoi(ms);
        }
        // +08:00/Z
        if (zoreB || zoreb) {
            *timeZone = 0;
            return;
        }
		const char* zone_opt = (NULL != plus ? plus : minus);
        if (NULL == zone_opt || strlen(zone_opt) < 6) {
			printf("not support, %s\n", date);
			return;
		}
		const char* zone_colon = strstr(zone_opt + 1, ":");
		if (NULL == zone_colon || strlen(zone_colon) < 3) {
			printf("not support, %s\n", date);
			return;
		}
			
		char zone[8] = { 0 };
		strncpy_s(zone, sizeof(zone), zone_opt + 1, 2);
		*timeZone = atof(zone);
		strncpy_s(zone, sizeof(zone), zone_colon + 1, 2);
		*timeZone += (atof(zone) / 60);
   
		*timeZone *= ((NULL == minus) ? 1.0 : -1.0);
    }
}

// 获取真实时区,不受夏令时干扰
float get_time_zone()
{
    time_t timeUtc = 0;
    struct tm dateTmTime;
    float timeZone = 0.0;
    // utc时间为0,对应1970年1月1日 0时0分0秒,北半球为冬天不需要考虑夏令时,但是南半球需要考虑夏令时。
    dateTmTime.tm_isdst = 0;
    localtime_r(&timeUtc, &dateTmTime);
    timeZone = (dateTmTime.tm_hour > 12) ? (dateTmTime.tm_hour -= 24) : dateTmTime.tm_hour;
    if (dateTmTime.tm_min > 0) {
        // 世界上部分国家使用非整时区,例如:伊朗用东3.5时区,阿富汗用东4.5时区,印度、尼泊尔用东5.5时区,新西兰的查塔姆群岛用西12.75时区(-12:45),等等
        timeZone += 1.0 * dateTmTime.tm_min / 60;
    }
    // 如果存在夏令时需要减去夏令时偏移
    if (dateTmTime.tm_isdst) {
        timeZone -= 1;
    }
    return timeZone;
}

time_t rfc3339_2_utc_timestamp(const char* date)
{
    int year = 1900, month = 1, day = 0, hour = 0, minute = 0, second = 0, msec = 0; 
    float time_zone = 0;
    data_time_string_to_int_rfc3339(date, &year, &month, &day, &hour, &minute, &second, &msec, &time_zone);
    struct tm tmDate = { 0 };
    // 获取本地真实时区
    float local_zone = get_time_zone();
    // 1.将给定时间作为本时区时刻,真实的时间增加了offset
    int offset = (time_zone - local_zone) * 3600;

    tmDate.tm_year = year - 1900;
    tmDate.tm_mon = month - 1;
    tmDate.tm_mday = day;
    tmDate.tm_hour = hour;
    tmDate.tm_min = minute;
    tmDate.tm_sec = second;
    tmDate.tm_isdst = 0; // 0:tm_isdst不受夏令时干扰
    // 2.mktime的结果为真实时区对应的UTC时间
    uint64_t timeStamp = mktime(&tmDate);      
    // 3.减去多出的offset,获得真实时刻对应的UTC时间 
    timeStamp = timeStamp - offset;

    return timeStamp;
}

注意Unix时间戳本身不会造成混乱,造成混乱的是不带时区的日期时间。

3、linux下夏令时的实现

将linux时区设置为美国西五区,夏令时生效时显示位于西四区。此时mktime(tm_isdst=-1)返回的时间戳意义为:当西四区的日期是xxxx-xx-xx xx:xx:xx时,对应的unix时间戳

参照:

        时区、时间戳、 时区、格林威治(GMT)、协调世界时(UTC)的关系 - 简书 (jianshu.com)

        时间戳(Unix timestamp)转换工具 - 在线工具 (tool.lu)

        tm(3type) - Linux manual page (man7.org)

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值