出现这个问题的原因是:Presto、Trino的驱动使用了joda这个库来处理时区的问题。但这个库的编写人似乎对java zone的格式没有太多经验。先看一下出错的代码:
com.facebook.presto.jdbc.internal.joda.time.DateTimeZone#forID
根据String类型的zoneId转成DateTimeZone。如果未设置使用默认的。如果设置了,则先判断是否是UTC的,是的话则直接使用UTC处理。然后使用Provider的getZone()方法获取。如果没有取到的话,根据zoneId是否是+,-开头,手动计算时区。这里的问题就在于,国内写代码时,大家一般都是这么写的:
TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("GMT+8")));
会导致上面的所有判断没有命中,直接进入最后的异常抛出。
解决办法:
1、通过下面的语句可以避免异常。如果程序中能够兼容“+08:00”这种时区格式,可以直接在代码中设置。这种设置是全局性的,一旦调整,整个程序都有影响。如果有逻辑不兼容这种格式,那么这种方法是不通用的。
TimeZone.setDefault(TimeZone.getTimeZone("+08:00"));
或者:TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
2、也可以试试不设置任何TimeZone,保持程序和环境默认。
3、最后还有一种,上面的代码中使用getProvider(), joda库本身只实现了两种,一个是UTC,顾名思义专门处理UTC时区;一个是ZoneInfoProvider,通过读取com.facebook.presto.jdbc.internal.joda.time/tz/data下的配置文件实现。看了一下里面是有shanghai的配置的。这个Provider是可以拓展的。我们可以增加一个自己的实现,来最小化数据库驱动对整个程序的影响。
示例代码:
DateTimeZone.setProvider(new Provider() {
@Override
public DateTimeZone getZone(String id) {
if (id.equals("UTC")) {
return DateTimeZone.UTC;
}
if (id.startsWith("GMT+08:00")) {
return DateTimeZone.forTimeZone(TimeZone.getTimeZone("+08:00"));
}
return null;
}
@Override
public Set<String> getAvailableIDs() {
return new TreeSet<String>() {
{
add("UTC");
add("GMT+8");
}
};
}
});
如果是Spring Boot程序,在main方法时调用即可。