共计 3150 个字符,预计需要花费 8 分钟才能阅读完成。
最近线上遇到一个比拟奇怪的问题,查下来还是挺有意思的,奇怪的常识又减少了。
起
线上服务有一个性能是从身份证号判断是否成年,具体做法是从身份证号截取出出生年月,而后判断是否大于 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)
这几天到底有什么不一样,才会如此的非凡?这所有的背地到底是兽性的扭曲,还是道德的沦丧?
异样抛出的地位在 joda
中DateTimeParserBucker
类的 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/Shanghai
。getOffsetFromLocal
办法返回的是一个偏移量,本地工夫减去这个偏移量就能够失去 UTC
工夫。getOffset
办法返回的也是偏移量,UTC
工夫加上这个偏移量就能够失去一个本地工夫。在下面 joda
的判断中,先调用 getOffsetFromLocal
从本地工夫 millis
计算一个偏移量 offset
,millis
减去 offset
后就是一个 UTC
工夫;再调用 getOffset
从UTC
工夫计算出一个偏移量,失常来讲,这两个偏移量应该是统一的,如果不统一就会抛出异样。
在 19470415
这个例子中,getOffsetFromLocal
计算结果是 28800000
毫秒,即 8
个小时;getOffset
办法计算结果则是 32400000
毫秒,即 9 个小时。这 1 个小时又是从何而来?
转
看下 getOffset
办法的正文:
getOffset
返回的偏移量可能因为夏令时或政策起因而不同!
持续翻看 joda
的代码,在 org.joda.time.tz.src
中蕴含了一组时区相干的代码,这是来自 iana
的时区数据内容。iana Time Zone Database
能够在这里下载。
咱们找到 joda
中asia
的时区数据,代码地址。以 Shanghai
为关键字,搜寻到这么一组数据:
这组日期看着有点眼生啊,这不就是之前日期解析异样那组日期吗?
细看后面的正文,大略意思就是历史上因为采纳过夏令时,或者和平等因素,导致某些历史上的某些时间段,Asia/Shanghai
工夫与 UTC
工夫的时区距离可能不同。这段正文很有意思,援用连贯也能看到一些有意思的故事,比方 1919 年天津已经短暂的实施过夏令时制度,给老百姓折腾的够呛,起初又改回来了。
iana
的时区数据怎么读呢?具体规定能够参考这里。最初一列的 D
示意 CDT
,即夏令时;S
示意 CST
,即规范时。大略猜想下这个数据的读法,以咱们服务触发的异样例子1947 年 04 月 15 日
为例,在 4 月 15 日的 0 点 0 分,采纳夏令时,工夫调整为 1 点。于是,相较于 UTC
工夫,多出了一个小时。
合
终于,咱们晓得这多的一个小时从何而来。晓得假相的我,真的想骂街。
题外话:夏令时真是个绝绝子的创造,如果是为了节约能源,明明批改作息就能解决的事,改来改去搞的很多货色都错乱了。