Unix时间戳
2010-05-15 14:55:31 阿炯

本站赞助商链接,请多关照。 概念:Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp)是从1970年1月1日0时0分0秒(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。UNIX时间戳的0按照ISO 8601规范为:1970-01-01T00:00:00Z.一个小时表示为UNIX时间戳格式为:3600秒;一天表示为UNIX时间戳为86400秒,闰秒不计算(由于 UTC 包括了闰秒,但在 POSIX 时间中闰秒会被忽略以提供一种简便且兼容的计算时差的方法;因此 POSIX 时间转换后不一定是 UTC,尽管它也存在)。在大多数的UNIX系统中UNIX时间戳存储为32位,这样会引发2038年问题或Y2038。

时间     秒
1 分钟     60 秒
1 小时     3600 秒
1 天     86400 秒
1 周     604800 秒
1 月 (30.44 天)     2629743 秒
1 年 (365.24 天)     31556926 秒

为了实现垮平台在应用系统中记录时间的时候我们就可以使用记录UNIX时间戳的方法做到垮平台性。现在大多数的语言java、PHP、Perl等都支持直接取UNIX时间戳,将需要记录的时间记录为UNIX时间戳,这样就可以不同的数据库系统中的垮平 台性,对与时间的操作只要对时间戳操作就行了。下面简单介绍下相关处理的方法:
================================获取系统UNIX时间戳=========================
Perl     time
PHP     time()
Ruby     Time.now (or Time.new). To display the epoch: Time.now.to_i
Python     import time first, then time.time()
Java     long epoch = System.currentTimeMillis()/1000;
Microsoft .NET C#     epoch = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
VBScript/ASP     DateDiff("s", "01/01/1970 00:00:00", Now())
MySQL     SELECT unix_timestamp(now())
PostgreSQL     SELECT extract(epoch FROM now());
SQL Server     SELECT DATEDIFF(s, '19700101', GETDATE())
JavaScript     Math.round(new Date().getTime()/1000.0) getTime() returns time in milliseconds.
Unix/Linux     date +%s
Other OS's     Command line: perl -e "print time" (If Perl is installed on your system)

================================将时间转换成UNIX时间戳=======================
Perl     Use these Perl Epoch routines
PHP     mktime(hour, minute, second, month, day, year) More information
Ruby     Time.local(year, month, day, hour, minute, second, usec ) (or Time.gm for GMT/UTC input). To display add .to_i
Python     import time first, then int(time.mktime(time.strptime('2000-01-01 12:34:00', '%Y-%m-%d %H:%M:%S')))
Java     long epoch = new java.text.SimpleDateFormat ("dd/MM/yyyy HH:mm:ss").parse("01/01/1970 01:00:00");
VBScript/ASP     DateDiff("s", "01/01/1970 00:00:00", time field) More information
MySQL     SELECT unix_timestamp(time) Time format: YYYY-MM-DD HH:MM:SS or YYMMDD or YYYYMMDD More information
PostgreSQL     SELECT extract(epoch FROM date('2000-01-01 12:34'));
With timestamp: SELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE '2001-02-16 20:38:40-08');
With interval: SELECT EXTRACT(EPOCH FROM INTERVAL '5 days 3 hours');
SQL Server     SELECT DATEDIFF(s, '19700101', time field)
JavaScript     use the JavaScript Date object
Unix/Linux     date +%s -d"Jan 1, 1980 00:00:01"

================================将UNIX时间戳转换成时间=======================
Perl     Use these Perl Epoch routines
PHP     date(output format, epoch); Output format example: 'r' = RFC 2822 date More information
Ruby     Time.at(epoch)
Python     import time first, then time.gmtime(epoch) time is an array of year, month, day, hour, min, sec, day of week, day of year, DST More information
Java     String date = new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new java.util.Date (epoch*1000));
VBScript/ASP     DateAdd("s", epoch, "01/01/1970 00:00:00") More information
PostgreSQL     SELECT TIMESTAMP WITH TIME ZONE 'epoch' + epoch * INTERVAL '1 second';
MySQL     from_unixtime(epoch, optional output format) The default output format is YYY-MM-DD HH:MM:SS More information
SQL Server     DATEADD(s, epoch, '19700101')
JavaScript     use the JavaScript Date object
Linux     date -d @1190000000 (replace 1190000000 with your epoch, needs newer version of date)
Other OS's     Command line: perl -e "print scalar(localtime(epoch))" (If Perl is installed) Replace 'localtime' with 'gmtime' for GMT/UTC time.

北京时间 2023-11-15T06:13:20,UNIX 时间将进入 1700000000 纪元。


2038年问题的动画演示



time_t 32bits带来的2038问题

在C语言中使用32位time_t记录主机时间,本质上是用一个32位有符号整数来存储从1970年1月1日00:00:00 UTC(Unix纪元)到当前时刻经过的秒数。这个设计简单高效,但也带来了一个著名的2038年问题。它在32位系统中通常被定义为32位有符号整数,其最大值是2147483647(即2³¹-1)。这个数字对应的具体时间是2038年1月19日03:14:07 UTC。一旦超过这个时刻,time_t会溢出变为负数,导致时间计算完全错误。

当32位time_t溢出时,系统时间可能会:
跳回1901年(因为溢出后变成负数,对应1901年12月13日)
导致时间比较逻辑完全错误
文件时间戳异常
安全证书验证失效

代码示例
#include<stdio.h>
#include<time.h>
int main() {
// 获取当前时间戳(32位time_t)
time_t currentTime;
time(&currentTime);
printf("当前时间戳: %ld\n",(long)currentTime);

// 检查time_t的大小
printf("sizeof(time_t) = %zu 字节\n",sizeof(time_t));

// 演示2038年溢出问题
time_t future =2147483648;// 2038年1月19日03:14:08
printf("溢出后的时间: %s",ctime(&future));// 可能输出1901年日期
return 0;
}

解决方案
目前主流的解决方案包括:
1.使用64位time_t:64位系统上的C标准库通常将time_t定义为64位有符号整型,可以支持到约2920亿年后的时间。

2.检查系统位数
在编写可移植代码时,建议先检查sizeof(time_t)来确定系统位数。

3.使用更精确的时间结构
对于需要更高精度的场景,可以使用struct timespec(纳秒级)或struct timeval(微秒级)替代。

32位time_t在2026年的今天仍然广泛存在,但距离2038年溢出仅有约12年时间。对于新开发的系统,建议直接使用64位time_t或采用上述替代方案,以避免未来的兼容性问题。当时间到达2038年1月19日03:14:07 UTC时,该整数将溢出。针对这个问题,目前主要有以下几种解决方法,各有优劣:

1、根本解决方案:改用64位time_t
这是最彻底、最推荐的解决方案。将time_t从32位有符号整数改为64位有符号整数,时间范围可覆盖至约2920亿年后,彻底解决溢出问题。实现方式:
现代编译器(如VS2008+、GCC 8+)默认在64位系统上使用64位time_t
从Linux 5.6和glibc 2.32开始,新编译的32位Linux应用程序也能获得64位time_t
微软从VS2005开始,C运行库就使用64位time_t
缺点:会导致新旧代码ABI不兼容,已编译的静态库或动态库中若包含32位time_t,新程序无法直接调用。

2、兼容性解决方案:修改时间起点
通过修改时间戳的起点,将溢出时间向后推迟,这是一种“打补丁”式的方案。具体做法:
到2038年时,将时间起点改为2038年1月1日,可维持到2106年
到2106年时,再将时间起点改为2106年1月1日,可维持到2174年
以此类推,每68年一个周期
缺点:治标不治本,需要周期性维护,且涉及全局时间基准的修改,风险较高。

3、投机取巧方案:改用无符号32位整数
将time_t重新定义为32位无符号整数(uint32_t),这样溢出时间从2038年推迟到2106年2月7日。
优点:对现有程序的内存分布影响小,API兼容性好。
缺点:只是将问题推迟了68年,且无法处理1970年之前的时间(负数时间戳),并非长久之计。

4、应用层规避方案
在应用程序层面,通过代码逻辑规避溢出问题:
1.避免直接比较时间戳
将 if(time_end > time_start) 改为 if(time_end - time_start > 0),利用差值计算避免溢出影响

2.使用更高层API
如PHP中使用 DateTime 类替代 time() 函数,它不依赖底层time_t,能安全处理远超2038年的时间戳

3.边界情况处理
如某些游戏存档中,检测到时间戳为0或负数时,强制存储为1,避免逻辑错误

5、系统级解决方案:Linux内核的ABI设计
Linux内核从5.6版本开始,为32位系统设计了新的64位时间ABI,同时兼容老的32位ABI,让新旧程序二进制能同时运行。

优点:解决了二进制兼容性问题,是系统级的完整方案。
缺点:需要内核、glibc、文件系统等多方面协同修改,实施复杂度高。

6、实际应用中的注意点
数据库影响:MySQL的TIMESTAMP类型支持范围到2038年,使用该类型的应用可能面临宕机风险

文件系统影响:ext2、ext3、xfs等文件系统的时间戳也使用32位整数,2038年后无法正确记录文件山

嵌入式设备:大量嵌入式设备可能没有机会更新软件,2038年问题在这些设备上尤为严重

对于新开发的系统,直接采用64位time_t是最佳选择;对于存量系统,需要根据实际情况选择兼容性方案或应用层规避方案。同时要注意2038年问题不仅影响应用程序,还涉及文件系统、数据库等多个层面,需要全链路排查。