PHP & mySQL:2038 年错误:它是啥?如何解决?

Posted

技术标签:

【中文标题】PHP & mySQL:2038 年错误:它是啥?如何解决?【英文标题】:PHP & mySQL: Year 2038 Bug: What is it? How to solve it?PHP & mySQL:2038 年错误:它是什么?如何解决? 【发布时间】:2011-01-02 00:36:52 【问题描述】:

我正在考虑使用 TIMESTAMP 来存储日期+时间,但我读到它有 2038 年的限制。我宁愿将问题分解成小部分,以便新手用户也容易理解,而不是大量提出我的问题。所以我的问题:

    2038 年问题到底是什么? 为什么会发生,发生时会发生什么? 我们如何解决? 是否有任何可能的替代方法来使用它,并且不会造成类似的问题? 我们可以对现有的使用 TIMESTAMP 的应用程序做些什么来避免所谓的问题,当它真正发生时?

提前致谢。

【问题讨论】:

还有 28 年的时间。你还在使用 1982 年以来的任何计算机相关技术吗?不太可能。所以不用担心,因为到 2038 年它可能不再是问题了。 Gordon,我做了一个预测应用程序。我每个月存了多少钱,然后可以估计我什么时候成为百万富翁。就我而言,28 年并不算多,而且我敢肯定我现在不是唯一一个遇到这个问题的人(我通过使用 64 位数字表示时间戳来解决它)。 @Emil 现在使用 64 位整数,您发现自己是解决具体问题的简单方法。不适用于(或需要)每个人,但适用于您的用例。关键是,如果 OP 没有具体问题,比如预测,那么这可能是一个有趣的话题,但他不必担心,因为在一般层面上解决这个问题不是 php(注意标签)问题。只是我的 2c。 到 2038 年,解析字符串“YYYY-MM-DD HH:MM:SS:mmmm...”将是您梦寐以求的最便宜的操作。到 2038 年,32 位将被淘汰。我怀疑我们所知道的 Unix 时间戳届时是否会存在,如果确实如此,我们的 256 位系统将处理远远超过 4096 位系统在快乐餐中提供的日期。 戈登,9 年过去了。 TIMESTAMPS 仍在使用。我们仍然使用 28 年前的技术。它被称为万维网。 【参考方案1】:

我已将此标记为社区 wiki,因此您可以随意编辑。

2038 年问题到底是什么?

“2038 年问题(也称为 Unix Millennium Bug,类似于 Y2K 问题的 Y2K38)可能会导致某些计算机软件在 2038 年之前或之后发生故障。该问题会影响所有将系统时间存储为一个带符号的 32 位整数,并将此数字解释为自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数。”


为什么会发生,发生时会发生什么?

2038 年 1 月 19 日星期二 03:14:07 UTC 之后的时间将“环绕”并在内部存储为负数,这些系统会将其解释为 12 月 13 日的时间, 1901 年而不是 2038 年。这是因为自 UNIX 纪元(格林威治标准时间 1970 年 1 月 1 日 00:00:00)以来的秒数将超过计算机 32 位有符号整数的最大值。


我们如何解决?

使用长数据类型(64 位就足够了) 对于 mysql(或 MariaDB),如果您不需要时间信息,请考虑使用 DATE 列类型。如果您需要更高的准确度,请使用DATETIME 而不是TIMESTAMP。请注意,DATETIME 列不存储有关时区的信息,因此您的应用程序必须知道使用了哪个时区。 Other Possible solutions described on Wikipedia 等待 MySQL 开发人员修复十年前报告的 this bug。

是否有任何可能的替代方法来使用它,并且不会造成类似的问题?

尽可能使用大型类型在数据库中存储日期:64 位就足够了 - 在 GNU C 和 POSIX/SuS 中使用 long long 类型,或者在 PHP 中使用 sprintf('%u'...) 或 BCmath 扩展。


尽管我们还没有到 2038 年,但有哪些潜在的破坏性用例?

所以 MySQL DATETIME 的范围是 1000-9999,但 TIMESTAMP 的范围只有 1970-2038。如果您的系统存储了生日、未来的远期日期(例如 30 年的抵押贷款)或类似的,那么您已经遇到了这个错误。同样,如果这会成为问题,请不要使用 TIMESTAMP。


我们可以对现有的使用 TIMESTAMP 的应用程序做些什么来避免所谓的问题,当它真正发生时?

很少有 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`

资源

Year 2038 Problem (Wikipedia) The Internet Will End in 30 Years

【讨论】:

太棒了。对我来说,你的回答是最完整的。非常感谢。 在MySQL中,如果以后的版本将TIMESTAMP的底层存储数据类型改成64位,那你的代码就不用改成DATETIME了吧?我只是不认为阻止任何人使用 TIMESTAMP 是正确的做法,因为该数据类型确实有其用途。 MySQL 的签名BIGINT 字段似乎可以很好地存储时间戳,我已经在 MySQL 5.5 上进行了一些本地测试,确认它可以工作。显然使用有符号会比无符号更好,因为您也可以表示过去的日期。有什么理由不使用 BIGINT 作为时间戳? MySQL(和 MariaDB)不使用 64 位整数在 64 位系统上存储时间戳是绝对荒谬的。使用 DATETIME 不是一个适当的解决方案,因为我们不知道时区。这是reported way back in 2005,但仍然没有可用的修复程序。 我对“很少有 PHP 应用程序在 2038 年仍然存在”表示异议……这听起来像是一个没有根据的意见。更不用说这个答案可能会在 5 年后仍然出现在网络上……10 年后……也许 15 年后【参考方案2】:

当使用 UNIX Timestamps 存储日期时,您实际上使用的是 32 位整数,它记录自 1970-01-01 以来的秒数;见Unix Time

那个 32 位的数字会在 2038 年溢出。这就是 2038 年的问题。

为了解决这个问题,您不能使用 32 位 UNIX 时间戳来存储您的日期——这意味着,在使用 MySQL 时,您不应该使用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。

(可能) 您可以对您的应用程序做的最好的事情是避免/解决该问题,不要使用TIMESTAMP,而是将DATETIME 用于必须包含不在 1970 年之间的日期的列和 2038 年。

不过,有个小提示:很有可能(从统计上讲)您的应用程序将在 2038 年之前被重写很多次 ^^ 所以也许,如果您不这样做'不必处理未来的日期,您不必处理当前版本的应用程序的问题......

【讨论】:

+1 获取信息。这里的尝试是从一开始就调整最佳实践,以便以后不必担心问题。因此,尽管现在 2038 听起来可能不是问题,但我只想从一开始就遵循最佳实践并为我创建的每个应用程序遵循它。希望这是有道理的。 是的,这是有道理的,我明白你的意思。但是“最佳实践”也可能意味着“满足需求”——如果您知道时间戳就足够了,则无需使用日期时间(例如,那些需要更多内存来存储;如果你有数百万条记录);;我有一些应用程序,其中我对某些列(例如,包含当前日期的列)使用时间戳,对其他一些列(例如,包含过去/未来日期的列)使用日期时间 我看到您的程序也很有意义并且运行良好,尤其是在涉及存储空间时。如果可以的话,请问您应用程序中的 TIMESTAMP 列通常使用什么结构和长度?谢谢。 好吧,当我想使用 TIMESTAMP 时,它是因为/因为我的“日期/时间”数据介于 1970 年和 2038 年之间——因此,我使用 MySQL TIMESTAMP 数据类型。跨度> 感谢您的信息。从您的回复中,我了解到只需将该列声明为 TIMESTAMP 就足够了,我们不需要为其提供任何长度(与我们提供长度的 int 或 var 不同)。我说的对吗?【参考方案3】:

在 Google 上快速搜索即可:Year 2038 problem

    2038 年问题(也称为 Unix Millennium Bug,类似于 Y2K 问题的 Y2K38)可能会导致某些计算机软件在 2038 年之前或之后出现故障 该问题影响所有将系统时间存储为带符号 32 位整数的软件和系统,并将此数字解释为自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数。以这种方式表示的是 2038 年 1 月 19 日星期二的 03:14:07 UTC。超过这一刻的时间将“环绕”并在内部存储为负数,这些系统将其解释为 1901 年而不是 2038 年的日期李> 对于现有 CPU/OS 组合、现有文件系统或现有二进制数据格式,此问题没有简单的解决方法

【讨论】:

【参考方案4】:

http://en.wikipedia.org/wiki/Year_2038_problem 有大部分细节

总结:

1) + 2) 问题是许多系统将日期信息存储为 32 位有符号整数,等于自 1970 年 1 月 1 日以来的秒数。可以像这样存储的最新日期是 2038 年 1 月 19 日星期二 03:14:07 UTC。发生这种情况时,int 将“环绕”并存储为负数,该负数将被解释为 1901 年的日期。那么究竟会发生什么,因系统而异,但足以说明这可能对他们中的任何一个都没有好处!

对于只存储过去日期的系统,那我想你暂时不用担心!主要问题在于使用未来日期的系统。如果您的系统需要使用 28 年后的日期,那么您现在应该开始担心了!

3) 使用其中一种可用的替代日期格式或迁移到 64 位系统并使用 64 位整数。或者对于数据库使用另一种时间戳格式(例如对于 MySQL 使用 DATETIME)

4) 见 3!

5) 见 4!!! ;)

【讨论】:

您的第 4 点和第 5 点让我想起了“参考电话”。这让我的脸上露出了笑容。感谢并为此 +1。【参考方案5】:

兄弟们,如果您需要使用 PHP 来显示时间戳,这是最好的 PHP 解决方案,无需更改 UNIX_TIMESTAMP 格式。

使用 custom_date() 函数。在其中,使用 DateTime。 Here's the DateTime solution.

只要您将 UNSIGNED BIGINT(8) 作为数据库中的时间戳。 只要你有 PHP 5.2.0 ++

【讨论】:

【参考方案6】:

因为我不想升级任何东西,所以我让我的后端 (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'];

可能不是一种有效的方法,但它确实有效。

【讨论】:

以上是关于PHP & mySQL:2038 年错误:它是啥?如何解决?的主要内容,如果未能解决你的问题,请参考以下文章

php解决时间超过2038年

PHP Y2K38 (2038年) 问题

不用到 2038 年,MySQL 的 TIMESTAMP 就能把我们系统搞崩

到2038年1月19日那天,Unix时钟会失效吗?

MySQL的时间戳2038年问题还有16年,最好在设计上的时候使用datetime就可以了,不要使用时间戳字段了,即使用了也不要用int类型进行映射,使用long类型映射即可

洛谷 P2655 2038年问题