我本想使用TIMESTAMP来存储日期+时间,但我了解到它有一个2038年的限制。为了使新手用户也能轻松理解,而不是批量提问,我更倾向于把问题分成小部分。所以我的问题如下:
- 什么是2038年问题?
- 为什么会发生这个问题?当它出现时会发生什么?
- 我们该如何解决这个问题?
- 是否存在其他替代方法,不会出现类似的问题?
- 对于已经使用TIMESTAMP的现有应用程序,当真正发生问题时,我们该怎么做以避免所谓的问题?
我本想使用TIMESTAMP来存储日期+时间,但我了解到它有一个2038年的限制。为了使新手用户也能轻松理解,而不是批量提问,我更倾向于把问题分成小部分。所以我的问题如下:
我已将此标记为社区维基,因此随意编辑。
"2038年问题(也称为Unix千年虫,类比于Y2K问题的Y2K38)可能会导致某些计算机软件在2038年之前或之后失败。该问题影响所有将系统时间存储为带符号32位整数并将此数字解释为自1970年1月1日UTC 00:00:00以来过去的秒数的软件和系统。"
超过2038年1月19日星期二UTC 03:14:07 的时间将“循环”并被内部存储为负数,这些系统将将其解释为1901年12月13日而不是2038年的时间。这是由于自UNIX纪元(1970年1月1日00:00:00 GMT)以来的秒数将超过计算机32位符号整数的最大值。
DATE
列类型。如果需要更高的精度,请使用 DATETIME
而不是 TIMESTAMP
。请注意, DATETIME
列不保存有关时区的信息,因此您的应用程序必须知道使用了哪个时区。在数据库中尽可能使用大型类型来存储日期:64位足够 - 在GNU C和POSIX / SuS中是long long类型,或在PHP或BCmath扩展中使用sprintf('%u'…)。
因此,MySQL DATETIME 的范围为1000-9999,但TIMESTAMP的范围只有1970-2038。如果您的系统存储出生日期、未来的日期(例如30年抵押贷款)或类似日期,那么您已经会遇到此错误。同样,如果这将成为一个问题,请不要使用TIMESTAMP。
尽管Web几乎还不是传统平台,但很少有PHP应用程序会在2038年之后存在。
以下是将数据库表列从TIMESTAMP
转换为DATETIME
的过程。该过程始于创建一个临时列:
# rename the old TIMESTAMP field
ALTER TABLE `myTable` CHANGE `myTimestamp` `temp_myTimestamp` int(11) NOT NULL;
# create a new DATETIME column of the same name as your old column
ALTER TABLE `myTable` ADD `myTimestamp` DATETIME NOT NULL;
# update all rows by populating your new DATETIME field
UPDATE `myTable` SET `myTimestamp` = FROM_UNIXTIME(temp_myTimestamp);
# remove the temporary column
ALTER TABLE `myTable` DROP `temp_myTimestamp`
资源
TIMESTAMP
,而应使用DATETIME
(请参见10.3.1. The DATETIME, DATE, and TIMESTAMP Types):
当您需要包含日期和时间信息的值时,使用
DATETIME
类型。支持的范围是'1000-01-01 00:00:00'
到'9999-12-31 23:59:59'
。
TIMESTAMP
数据类型的范围为'1970-01-01 00:00:01'
UTC到'2038-01-19 03:14:07'
UTC。
避免/修复这个问题,你可以对含有不在1970年到2038年之间的日期的列使用DATETIME
而非TIMESTAMP
。
需要注意的是:根据统计学来看,很有可能在2038年之前你的应用程序已经被重写了很多次^^因此,如果你不必处理未来的日期,也许在当前版本的应用程序中就不必担心这个问题了...
在谷歌上快速搜索即可解决:2038年问题
http://en.wikipedia.org/wiki/Year_2038_problem详细描述了问题。
总结如下:
1) + 2) 问题在于许多系统将日期信息存储为32位有符号整数,表示自1970年1月1日以来的秒数。最后可存储的日期是在2038年1月19日星期二UTC时间03:14:07。当这种情况发生时,整数将“回绕”,并存储为负数,被解释为1901年的日期。具体会发生什么取决于各个系统,但可以肯定的是,对任何一个系统来说都不会是好事!
对于只记录过去日期的系统,您可能暂时不需要担心!主要问题在于需要处理未来日期的系统。如果您的系统需要处理28年后的日期,则现在应该开始担心了!
3) 使用可用的替代日期格式之一,或使用64位系统并使用64位整数。或对于数据库,使用替代时间戳格式(例如,对于MySQL使用DATETIME)。
4) 参见第3条!
5) 参见第4条!!!;)
最近我在思考这些问题,并想分享我为新项目找到的解决方案。
在阅读了各种类似问题的回答后,我发现将 Unix 时间戳存储在 Bigint 列中是一个更好的解决方案。
Bigint 的范围将覆盖您从时间开始之前一直到年份 292277026596
,可以说是永远。
此外:
DEFAULT (unix_timestamp())
使用自动生成的时间戳。`created` decimal(18,2) NOT NULL DEFAULT (unix_timestamp(now(2))) COMMENT 'unix timestamp'
使用decimal(18,2)提供了绝对过度的时间范围,与bigint/datetime相同的存储空间,同时显示到2位小数。
存储基于数字的数量,9个数字=4字节有符号或无符号都无所谓。
您可以将范围限制在更实际的范围内,并且比datetime少得多,或者将精度增加到纳秒。您决定什么是重要的。
另一个优点是,如果在遥远的未来您达到了范围的极限,Mysql会告诉您。 不会发生环绕问题,而是会出现插入错误,可以轻松地再次更改表以添加另一个数字。
这对我来说非常合理,我强烈建议使用这种方法开始新的数据库。
由于我不想升级任何东西,因此我让我的后端(MSSQL)来完成这项工作,而不是PHP!
$qry = "select DATEADD(month, 1, :date) next_date ";
$rs_tmp = $pdo->prepare($qry);
$rs_tmp->bindValue(":date", '2038/01/15');
$rs_tmp->execute();
$row_tmp = $rs_tmp->fetch(PDO::FETCH_ASSOC);
echo $row_tmp['next_date'];
可能不是最有效的方法,但它可行。