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插入日期
iphone4s上的日历为啥到2038年就没了?为啥日期最多只能改到2038年1月1日?