国际业务往往比国内业务复杂很多,其中一点就是多时区,2019.11.3号时区切换时正好踩了一把坑,该篇文章记录下问题,并给出多时区下时间操作比较合理的做法。
由于线上服务器采用的都是 America/Los_Angeles
时区,因此会涉及夏令时,夏令时的意思是在 2019-11-3 02:00:00
时会回拨1小时到 2019-11-3 01:00:00
,那么夏令时会带来哪些问题?
2019-11-3 01:30:00
就无法转换为一个具体的unix timestamp,因为无法确定该时间点位于回拨前还是回拨后。 Los_Angeles
这一天实际上有25个小时。
问题复现代码如下所示,执行时需要把本地时间调整为 America/Los_Angeles
。
/** * 错误的示例 * 本地时间为LA时区 */ @Test public void test() throws ParseException { // 字符串一般都隐含时区问题,这里假定这个字符串为GMT+8时区 String gmt8Date = "20191104"; // 得到东八区下该时间戳,此时时间戳对应的为东八区 2019-11-04 00:00:00 FastDateFormat ymd = FastDateFormat.getInstance("yyyyMMdd", TimeZone.getTimeZone("GMT+8")); Date gmtDateInstance = ymd.parse(gmt8Date); // 时间减一,此时会受到本地时间影响, LA时区下20191103这一天有25个小时 Date date = DateUtils.addDays(gmtDateInstance, -1); // format出来结果为20191102 String preDate = ymd.format(date); }
问题的本质原因是我们大多数时候默认一天有24个小时,然而夏令时切换当天一天有25个小时,同样冬令时切换当天,一天会有23个小时,而出现问题的代码是 DateUtils.addDays(gmtDateInstance, -1)
,减1天, 需要判断当前一天到底多少个小时
。
找到原因了,自然很好解决,时间的加减需要感知到具体时区信息,解决方案是使用JDK8的ZoneDateTime。
public Date addDay(Date date, int day) { Instant instant = ZonedDateTime.ofInstant(date.toInstant(), this.pattern.getZone()) .plusDays(day) .toInstant(); return new Date(instant.toEpochMilli()); }
ZoneDateTime
在构建时已经包含了时区信息,因此加减会根据当前时间来判断具体的变化值。更多的代码可以参开我Github: DateFormat.java
JDK8已经相当普及,其增加的 java.time
相当优秀,新代码建议应该抛弃掉Date类,转抱Java8 Time,顺便这里分享下个人的Java8 Time笔记,希望对你有帮助.