首页 > 解决方案 > JRE 是否支持 posix TZ 描述而不是 TZ 名称?

问题描述

当操作系统使用 POSIX time zone description而不是 time zone name时,Java 似乎不会应用 DST 偏移量。JRE 不支持使用 TZ 描述还是这种行为是错误?

更多细节...

我正在开发基于 Linux (Debian) 的系统,其中 TZ 环境变量设置为 POSIX 格式的 TZ,STD+7DST+6,M3.2.0/02:00:00,M11.1.0/02:00:00而不是 TZ 名称,例如America/Denver. (见TZ 变量

虽然这似乎适用于date相关系统工具,但当我尝试在 java 应用程序中查找时间时,它似乎没有针对 DST 正确调整。这会导致 DST 生效的一年中部分时间的时间错误。

我已经在几个不同的系统上对此进行了测试,并且在每个系统上都看到了相同的结果(测试结果如下)

这种行为实际上出现在使用Quartz Scheduler的应用程序中,但我已经能够使用以下SSCCE重现该问题:

import java.util.Date;
import java.util.TimeZone;

public class CheckTime {
    public static void main(String[] args) {
        TimeZone tz = TimeZone.getDefault();
        Date now = new Date();
        System.out.println("Current time " + now.toString());
        System.out.println("Current time zone " + tz.getDisplayName());
        System.out.println("Current time zone in DST? " + tz.inDaylightTime(now));
    }
}

Ubuntu 18.04.2 LTS(仿生)

$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-8u222-b10-1ubuntu1~18.04.1-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)

$ export TZ="STD+7DST+6,M3.2.0/02:00:00,M11.1.0/02:00:00"
$ date
Fri Aug  9 07:02:27 DST 2019
$ java CheckTime
Current time Fri Aug 09 06:02:30 GMT-07:00 2019
Current timezone GMT-07:00
Current timezone in DST? false

$ export TZ="America/Denver"
$ date
Fri Aug  9 07:03:29 MDT 2019
$ java CheckTime
Current time Fri Aug 09 07:03:32 MDT 2019
Current timezone Mountain Standard Time
Current timezone in DST? true

自定义 ARM32hf 构建,基于 debian(内核 4.1.0-altera)

$ java -version
openjdk version "1.8.0_162"
OpenJDK Runtime Environment (Zulu Embedded 8.27.0.91-linux-aarch32hf) (build 1.8.0_162-b91)

$ export TZ="STD+7DST+6,M3.2.0/02:00:00,M11.1.0/02:00:00"
$ date
Fri Aug  9 07:06:59 DST 2019
$ java CheckTime
Current time Fri Aug 09 06:07:03 GMT-07:00 2019
Current time zone GMT-07:00
Current time zone in DST? false

$ export TZ="America/Denver"
$ date
Fri Aug  9 07:09:45 MDT 2019
$ java CheckTime
Current time Fri Aug 09 07:09:49 MDT 2019
Current time zone Mountain Standard Time
Current time zone in DST? true

Raspbian 9.9(拉伸)

$ java -version
java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)

$ export TZ="STD+7DST+6,M3.2.0/02:00:00,M11.1.0/02:00:00"
$ date
Fri Aug  9 07:12:52 DST 2019
$ java CheckTime
Current time Fri Aug 09 06:12:57 GMT-07:00 2019
Current time zone GMT-07:00
Current time zone in DST? false

$ export TZ="America/Denver"
$ date
Fri Aug  9 07:13:42 MDT 2019
$ java CheckTime
Current time Fri Aug 09 07:13:44 MDT 2019
Current time zone Mountain Standard Time
Current time zone in DST? true

编辑

奇怪的是,我在 Mac 上看到的结果略有不同。Java 仍然没有报告系统处于 DST,但它至少报告了正确的本地时间。

MacOS 10.14.6(莫哈韦沙漠)

$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment Corretto-8.212.04.2 (build 1.8.0_212-b04)
OpenJDK 64-Bit Server VM Corretto-8.212.04.2 (build 25.212-b04, mixed mode)

$ export TZ="STD+7DST+6,M3.2.0/02:00:00,M11.1.0/02:00:00"
$ date
Fri Aug  9 09:47:14 DST 2019
$ java CheckTime
Current time Fri Aug 09 09:47:18 GMT-06:00 2019
Current timezone GMT-06:00
Current timezone in DST? false

$ export TZ="America/Denver"
$ date
Fri Aug  9 09:49:04 MDT 2019
$ java CheckTime
Current time Fri Aug 09 09:49:08 MDT 2019
Current timezone Mountain Standard Time
Current timezone in DST? true

$java -version
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

$ export TZ="STD+7DST+6,M3.2.0/02:00:00,M11.1.0/02:00:00"
$ date
Fri Aug  9 10:19:23 DST 2019
$java CheckTime
Current time Fri Aug 09 10:19:40 GMT-06:00 2019
Current timezone GMT-06:00
Current timezone in DST? false

$ export TZ="America/Denver"
$ date
Fri Aug  9 10:21:03 MDT 2019
$ java CheckTime
Current time Fri Aug 09 10:21:07 MDT 2019
Current timezone Mountain Standard Time
Current timezone in DST? true

标签: javalinuxdatetime

解决方案


这不是一个错误。这是一个特点。

根据javadoc for ZoneId,不支持用于指定区域的 POSIX 语法:

时区 ID

ID 在系统内是唯一的。身份证分为三种。

最简单的 ID 类型来自 ZoneOffset。这由“Z”和以“+”或“-”开头的 ID 组成。

下一种 ID 是带有某种前缀形式的偏移式 ID,例如“GMT+2”或“UTC+01:00”。可识别的前缀是“UTC”、“GMT”和“UT”。偏移量是后缀,将在创建过程中进行规范化。可以使用 normalized() 将这些 ID 标准化为 ZoneOffset。

第三种 ID 是基于区域的 ID。基于区域的 ID 必须包含两个或多个字符,并且不能以“UTC”、“GMT”、“UT”、“+”或“-”开头。基于区域的 ID 由配置定义,请参阅 ZoneRulesProvider。该配置侧重于提供从 ID 到底层 ZoneRules 的查找。

时区规则由政府定义并经常更改。有许多组织(这里称为组)监控时区变化并对其进行整理。默认组是 IANA 时区数据库 (TZDB)。其他组织包括 IATA(航空业机构)和微软。

每个组为其提供的区域 ID 定义自己的格式。TZDB 组定义了诸如“Europe/London”或“America/New_York”之类的ID。TZDB ID 优先于其他组。

也可以看看:

如果您编写了一些代码来解析该语法,您应该能够使用这些数据来构造一个SimpleTimeZone( javadoc )。不幸的是,这迫使您继续使用旧的(“大部分已弃用”)Date类和朋友。

新的(在 Java 8 中)java.time.*类似乎没有一种简单的方法来ZoneId根据一组规则构建您自己的类。(也许可以通过实现你自己的ZoneRuleProviderjavadoc)来完成,但它看起来很复杂。)

所以(IMO)你最好让你的操作系统使用标准的 TZDB 区域 ID。


你评论说:

在这个特定的系统上,“/etc/localtime”和“/etc/timezone”都不存在。

如果您正在运行 Ubuntu Bionic,“/etc/localtime”应该存在。它应该是“/usr/share/zoneinfo”树中二进制时区文件的符号链接。请参阅https://linuxize.com/post/how-to-set-or-change-timezone-on-ubuntu-18-04/。或者问题可能是系统被故意配置为不知道其本地时区。


推荐阅读