最近线上遇到一个比拟奇怪的问题,查下来还是挺有意思的,奇怪的常识又减少了。

线上服务有一个性能是从身份证号判断是否成年,具体做法是从身份证号截取出出生年月,而后判断是否大于18岁。性能比较简单,始终也没什么问题,直到昨天遇到一个日期解析的异样。

org.joda.time.IllegalInstantException: Cannot parse "19470415": Illegal instant due to time zone offset transition (Asia/Shanghai) at org.joda.time.format.DateTimeParserBucket.computeMillis(DateTimeParserBucket.java:473) at org.joda.time.format.DateTimeParserBucket.computeMillis(DateTimeParserBucket.java:411) at org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:928)......

19470415这个字符串格局正确,但用joda解析的时候就抛了异样。如下是解析的代码:

DateTime birthTime = DateTimeFormat.forPattern("yyyyMMdd").parseDateTime(birth);DateTime adultTime = DateTime.now().plusYears(-18);return adultTime.compareTo(birthTime) == 1;

从异样信息上看,预计是和时区无关。网上搜寻了下,在joda的文档中曾经写明了如何解决:

DateTime换成LocalDateTime后果然解决了。

LocalDateTime birthTime = DateTimeFormat.forPattern("yyyyMMdd").parseLocalDateTime(birth);LocalDateTime adultTime = LocalDateTime.now().plusYears(-18);return adultTime.compareTo(birthTime) == 1;

解决后我开始尝试探索这背地的深层起因。

我尝试从1990年1月1日到今日,一一尝试进行日期的解析,后果也很奇怪,只有几天会呈现这种异样:

19400601 parse fail, Cannot parse "19400601": Illegal instant due to time zone offset transition (Asia/Shanghai)19410315 parse fail, Cannot parse "19410315": Illegal instant due to time zone offset transition (Asia/Shanghai)19420131 parse fail, Cannot parse "19420131": Illegal instant due to time zone offset transition (Asia/Shanghai)19460515 parse fail, Cannot parse "19460515": Illegal instant due to time zone offset transition (Asia/Shanghai)19470415 parse fail, Cannot parse "19470415": Illegal instant due to time zone offset transition (Asia/Shanghai)19480501 parse fail, Cannot parse "19480501": Illegal instant due to time zone offset transition (Asia/Shanghai)19490501 parse fail, Cannot parse "19490501": Illegal instant due to time zone offset transition (Asia/Shanghai)

这几天到底有什么不一样,才会如此的非凡?这所有的背地到底是兽性的扭曲,还是道德的沦丧?

异样抛出的地位在jodaDateTimeParserBucker类的computeMillis办法:

......if (iOffset != null) {    millis -= iOffset;} else if (iZone != null) {    int offset = iZone.getOffsetFromLocal(millis);    millis -= offset;    if (offset != iZone.getOffset(millis)) {        String message = "Illegal instant due to time zone offset transition (" + iZone + ')';        if (text != null) {            message = "Cannot parse \"" + text + "\": " + message;        }        throw new IllegalInstantException(message);    }}......

millis是依据日期字符串计算出的一个工夫戳,iZone示意时区,不设置的话则用机器设置的时区,在上例中是Asia/ShanghaigetOffsetFromLocal办法返回的是一个偏移量,本地工夫减去这个偏移量就能够失去UTC工夫。getOffset办法返回的也是偏移量,UTC工夫加上这个偏移量就能够失去一个本地工夫。在下面joda的判断中,先调用getOffsetFromLocal从本地工夫millis计算一个偏移量offsetmillis减去offset后就是一个UTC工夫;再调用getOffsetUTC工夫计算出一个偏移量,失常来讲,这两个偏移量应该是统一的,如果不统一就会抛出异样。

19470415这个例子中,getOffsetFromLocal计算结果是28800000毫秒,即8个小时;getOffset办法计算结果则是32400000毫秒,即9个小时。这1个小时又是从何而来?

看下getOffset办法的正文:

getOffset返回的偏移量可能因为夏令时或政策起因而不同!

持续翻看joda的代码,在org.joda.time.tz.src中蕴含了一组时区相干的代码,这是来自iana的时区数据内容。iana Time Zone Database能够在这里下载。

咱们找到jodaasia的时区数据,代码地址。以Shanghai为关键字,搜寻到这么一组数据:

这组日期看着有点眼生啊,这不就是之前日期解析异样那组日期吗?

细看后面的正文,大略意思就是历史上因为采纳过夏令时,或者和平等因素,导致某些历史上的某些时间段,Asia/Shanghai工夫与UTC工夫的时区距离可能不同。这段正文很有意思,援用连贯也能看到一些有意思的故事,比方1919年天津已经短暂的实施过夏令时制度,给老百姓折腾的够呛,起初又改回来了。

iana的时区数据怎么读呢?具体规定能够参考这里。最初一列的D示意CDT,即夏令时;S示意CST,即规范时。大略猜想下这个数据的读法,以咱们服务触发的异样例子1947年04月15日为例,在4月15日的0点0分,采纳夏令时,工夫调整为1点。于是,相较于UTC工夫,多出了一个小时。

终于,咱们晓得这多的一个小时从何而来。晓得假相的我,真的想骂街。


题外话:夏令时真是个绝绝子的创造,如果是为了节约能源,明明批改作息就能解决的事,改来改去搞的很多货色都错乱了。