2038 年问题仍然发生在 Java 8 中日期反序列化的杰克逊身上 [重复]

Posted

技术标签:

【中文标题】2038 年问题仍然发生在 Java 8 中日期反序列化的杰克逊身上 [重复]【英文标题】:Year 2038 issue still happens with Jackson of date deserialization in Java 8 [duplicate] 【发布时间】:2017-12-06 10:52:29 【问题描述】:

简单地说,我有以下类来获取从远程响应接收到的 JSON 正文,以反序列化到 CreditCardDTO 内收到的日期 exp_date,例如 8/2010 的“0820”和 2/2040 的“0240”:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Date;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyOrder(alphabetic = true)
public class CreditCardDTO 
    private String brand;
    private Date expirationDate;

    @JsonProperty("brand")
    public String getBrand() 
        return brand;
    

    @JsonProperty("credit_card_type")
    public CreditCardDTO setBrand(String brand) 
        this.brand = brand;
        return this;
    

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
    @JsonProperty("expirationDate")
    public Date getExpirationDate() 
        return expirationDate;
    

    @JsonFormat(pattern = "MMyy")
    @JsonProperty("exp_date")
    public CreditCardDTO setExpirationDate(Date expirationDate) 
        System.out.println(expirationDate);
        this.expirationDate = expirationDate;
        return this;
    

如果是2038年之前一切正常的问题,但是一旦数据在那个关键日期之后,它仍然会发生,数据回到1941年,我搜索了这个问题,发现它应该不会发生在 Java 8 中:Why should a Java programmer care about year 2038 bug?,所以我想知道这里有什么问题!

Jackson 版本 2.8.0,肯定是 Java 8。

【问题讨论】:

您正在使用java.util.Date 类,它仍然属于旧的Java 函数集。要使用 Java 8 的优势,您需要使用 java.time 包中的类。 System.out.println(new java.util.Date(Long.MAX_VALUE)); give Sun Aug 17 08:12:55 CET 292278994 on Java 8. 所以你可以检查 Jackson 如何管理 Date 转换 如果我们从 Y2K 错误中学到了什么,那就是 不要使用两位数的年份格式进行数据传输或存储。你正在这样做。这意味着它必须猜测“40”是“2040”而不是“1940”。阅读SimpleDateFormat 的文档,了解它是如何决定的。并改用四位数的年份进行数据传输。 您在阅读 cmets 吗?那不是错误。这是“2 位数年份”转换的预期输出。一开始这是一个糟糕的设计,只是为了节省2个字符。再次阅读 cmets 并关注建议的研究(链接与否)。 如果您无法将到期日期格式更改为 4 位数年份,您可能必须自己检查日期并在某些必须弄清楚的情况下添加 100 年(不要只是这样做如果日期是过去,或者信用卡永远不会过期)。使用java.time API 可以更轻松地添加 100 年。如果你得到jackson-datatype-jsr310,我相信你可以和杰克逊一起使用它。 【参考方案1】:

虽然我不了解 Jackson,但我可以告诉您,您在处理此信用卡到期数据时采用了糟糕的方法。

首先,您选择自行开发,而不是查看现有的类和标准。始终寻找以前的工作;自己动手的方法应该是最后的手段,而不是首先。少些野蛮牛仔,多些以先例为指导的判断。

其次,您正在使用麻烦的旧日期时间类,它们是遗留的,现在被 java.time 类所取代。

YearMonth

要表示一年和一个月,请使用 Java 内置的 YearMonth 类。月份的编号很合理,1 月至 12 月为 1-12,与传统课程不同。

YearMonth ym = YearMonth.of( 2040 , 3 ) ; 

顺便说一句,您可能会发现 Month 枚举在您的工作中很方便。

YearMonth ym = YearMonth.of( 2040 , Month.MARCH ) ; 

将此YearMonth 班级用作您的成员,而不是使用特定日期。

当与今天这样的日期进行比较时,获取该日期的YearMonth

ZoneId z = ZoneId.of( "Asia/Amman" ) ;
LocalDate today = LocalDate.now( z ) ;
YearMonth ymToday = YearMonth.from( today ) ;

Boolean isExpired = ymToday.isAfter( ym ) ;

ISO 8601

ISO 8601 标准定义了许多实用的合理格式,用于将日期时间值表示为文本。

年月的标准格式是YYYY-MM。所以 2040 年 3 月是2040-03

java.time 类在解析或生成字符串时默认使用标准格式。

生成一个字符串。

ym.toString()  

2040-03

解析一个字符串。

YearMonth ym = YearMonth.parse( "2040-03" );

始终使用 4 位数的年份

对于存储和演示,始终使用四位数表示年份。无休止的混乱、错误和模棱两可不值得在纸上节省两个八位字节的内存/存储空间或半厘米的空间。

【讨论】:

感谢您的回复,我按照您所说的观点进行操作,正如我所说,来自远程 API 的回复不是我自己的问题,并且该开发人员足以拒绝我的更改请求这一年,当我找到使用YearMonth 或保持我自己的方式使用ISO 8601 并将表格限制为从现在起最多+20 年的解决方案时,似乎与Jackson 的解析使用SimpleDateFormat 来解析'yy'。

以上是关于2038 年问题仍然发生在 Java 8 中日期反序列化的杰克逊身上 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

64位MySQL的PHP​​ 2038年问题无法通过PHPMyAdmin插入日期

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

iphone4s上的日历为啥到2038年就没了?为啥日期最多只能改到2038年1月1日?

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

2038 ,程序员史上最大危机!

将系统时间调至2038年1月19日03:14:07会怎样