SimpleDateFormat setTimeZone失效原因及输出时区配置问题咨询
先来说说你遇到的第一个问题:为什么TimeZone.setDefault(TimeZone.getTimeZone("UTC"))能生效,但你尝试的TimeZone.setTimeZone(...)却不行?
其实这里有个API误解:TimeZone类根本没有静态的setTimeZone方法哦!你之前用的setDefault是修改全局默认时区,这个设置会影响所有依赖默认时区的操作——包括打印Date对象时的格式化逻辑,以及toTimestamp()方法的输出(因为Timestamp的toString也会用默认时区)。而你真正应该用的是给SimpleDateFormat实例单独设置时区的方法:sdf.setTimeZone(...),大概率是之前写错了方法名,导致没生效。
接下来看你更新后的代码问题:明明给SimpleDateFormat设置了CET时区,为什么解析后的Date输出还是UTC时间?
核心原因在这里:Date对象本身不存储任何时区信息,它本质上只是一个代表自1970-01-01 00:00:00 UTC以来的毫秒数的数值。当你直接打印Date或者调用toTimestamp()时,JVM会使用当前的默认时区来把这个毫秒数格式化成可读的时间字符串,而不是你给SimpleDateFormat设置的时区。
那你给SimpleDateFormat设置时区的作用是什么?它只影响解析和格式化字符串的过程:
- 解析时:它会把输入的带时区的字符串(比如
2018-03-19T06:00:00+01:00)转换成正确的毫秒数(这个毫秒数对应的UTC时间是2018-03-19 05:00:00,和你输入的+01:00时区的时间是等价的)。 - 格式化时:如果你用这个
sdf来把Date对象再转成字符串,它会用你设置的CET时区来输出,比如:println sdf.format(d) // 这会输出2018-03-19T06:00:00+01:00
如果想要让Date的输出(或者Timestamp的输出)显示为CET时区,你有两种方式:
- 临时修改全局默认时区,就像你最开始的代码那样,但这种方式不推荐,因为会影响整个应用的其他代码。
- 使用另一个
SimpleDateFormat实例,设置为CET时区,专门用来格式化Date对象:def dt = "2018-03-19T06:00:00+01:00" def parseFormat = "yyyy-MM-dd'T'HH:mm:ssX" SimpleDateFormat parseSdf = new SimpleDateFormat(parseFormat); // 解析时其实不需要设置时区,因为输入字符串已经带时区信息了 Date d = parseSdf.parse(dt); // 用另一个格式化器来输出CET时区的时间 def outputFormat = "EEE MMM dd HH:mm:ss zzz yyyy" SimpleDateFormat outputSdf = new SimpleDateFormat(outputFormat); outputSdf.setTimeZone(TimeZone.getTimeZone("CET")) println outputSdf.format(d) // 会输出Mon Mar 19 06:00:00 CET 2018 // 如果要输出Timestamp格式的CET时间,也需要用格式化器 println outputSdf.format(d.toTimestamp())
最后还要提醒一下:SimpleDateFormat是线程不安全的,在多线程环境下使用要格外小心。如果是Java 8及以上,更推荐使用java.time包下的API(比如ZonedDateTime、DateTimeFormatter),它们是线程安全的,而且时区处理更清晰。
内容的提问来源于stack exchange,提问作者Hary

