繁体   English   中英

Java中的时区问题

[英]Time zone issue in Java

我正在使用这个简单的代码来获取系统中设置的当前时区的时区 ID。

public class Main {

    public static void main(String[] args)
    {   TimeZone timezone = TimeZone.getDefault();
        System.out.println(timezone.getID());
    }
}

我使用tzutil /g来确定时区,使用tzutil /s来设置时区。 我观察到当时区设置为东部标准时间时,上面的代码返回 id 为America/New_York但是当它设置为东部标准时间_dstoff 时,我得到的 id 为GMT-05:00 ,这是不可取的。 我想要continent/city形式的输出。

我怎么会得到那个?

我的第一个答案是您不应该关心 JVM 的默认时区设置。 无论如何,您的 Java 代码不应依赖它。 可以随时从程序的其他部分或在同一 JVM 中运行的其他程序更改默认设置,因此很脆弱。 而是在时区敏感操作中指定显式时区。

如果您仍然想依赖默认值,如果碰巧是您认为不受欢迎的GMT-05:00 ,这里有一个技巧可以将其设置为某个大陆/城市时区。

    ZoneId defaultZoneId = ZoneId.systemDefault();
    ZoneId desiredTimeZone = null;
    if (defaultZoneId.equals(ZoneId.of("GMT-05:00"))) {
        // Pick a date in summer
        LocalDate dateInSummer = Year.now().atMonthDay(MonthDay.of(Month.JULY, 7));
        DateTimeFormatter zoneFormatter = DateTimeFormatter.ofPattern("zzzz", Locale.ENGLISH);
        for (String zid : ZoneId.getAvailableZoneIds()) {
            ZoneId zone = ZoneId.of(zid);
            String timeZoneName = dateInSummer.atStartOfDay(zone).format(zoneFormatter);
            if (zid.startsWith("America/") && timeZoneName.equals("Eastern Standard Time")) {
                desiredTimeZone = zone;
                break;
            }
        }
    }

    System.out.println("Time zone: " + desiredTimeZone);

在我的 JDK 11 上,我得到了:

时区:美洲/巴拿马

不幸的是,要将其设置为默认值,我们必须通过设计不佳且早已过时的TimeZone类:

    TimeZone.setDefault(TimeZone.getTimeZone(desiredTimeZone));

还有一个理由警告不要使用美国/巴拿马或其他时区 ID 而不是 GMT-05:00:虽然巴拿马自 1908 年的某个时间以来一直使用 GMT/UTC 的偏移量 -05:00,但它并没有总是如此,因此对于历史日期,您会因使用它而得到错误的时间。

根据 timeanddate.com,全年使用东部标准时间(无夏令时/夏令时)的地方是:

  • 努纳武特 - 仅限南安普敦岛(珊瑚港)(加拿大)(美国/Coral_Harbour 时区)
  • 金塔纳罗奥州(墨西哥)(美国/坎昆时区)
  • 开曼群岛(美洲/开曼)
  • 牙买加(美国/牙买加)
  • 巴拿马(美洲/巴拿马)

我的 Java 还列出了 America/Atikokan(也在加拿大)。

该列表不包括美国的任何地方。 我相信以上都没有一直使用偏移量-05:00 也就是说,它们中的每一个在历史上的某个时刻都有不同的偏移量,Java 将对历史日期使用不同的偏移量。

那么使用哪个时区?

编辑: Andreas 在评论中指出 Etc/GMT+5 和 SystemV/EST5 都没有我提到的关于将偏移应用于可能出乎意料的历史日期的问题。 所以你可以考虑其中之一。 在我看来,每个人的优点是:

                                    America/Xxx Etc/GMT+5 SystemV/EST5 GMT-05:00
Official IANA time zone?                Yes        Yes
With slash?                             Yes        Yes        Yes
Continent/city?                         Yes
Good for historical dates?                         Yes        Yes         Yes
Prints as EST/Eastern Standard Time?    Yes                   Yes

(您的起点 GMT-05:00 仅用于比较。当然,哪些对历史日期非常有用取决于您对这些日期的要求。)

链接

要查找一直是GMT-05:00 ,又名Eastern Standard Time_dstoff ,请检查所有ZoneId规则以找到具有以下内容的时区:

  • -5 小时的偏移

  • 没有过渡(过去的时区变化)

  • 无转换规则(当前和未来时区更改)

for (String id : ZoneId.getAvailableZoneIds()) {
    ZoneRules rules = ZoneId.of(id).getRules();
    if (rules.getTransitions().isEmpty() && rules.getTransitionRules().isEmpty()
                && rules.getOffset(Instant.now()).getTotalSeconds() == 5 * -3600) {
        System.out.println(id);
    }
}

输出(Windows 上的 OpenJDK 13.0.2)

Etc/GMT+5
SystemV/EST5

因此,如果您需要将时区从GMT-05:00更改为带有/的时区,请使用这两者之一。 Etc/GMT+5似乎合适。

如果你想自动化它,所以它会在默认时区不包含/时尝试修复它,那么这样的事情可能会起作用:

if (! ZoneId.systemDefault().getId().contains("/")) {
    int offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()).getTotalSeconds();
    String candidate = null;
    for (String id : ZoneId.getAvailableZoneIds()) {
        ZoneRules rules = ZoneId.of(id).getRules();
        if (rules.getTransitions().isEmpty() && rules.getTransitionRules().isEmpty()
                       && rules.getOffset(Instant.now()).getTotalSeconds() == offset) {
            if (id.startsWith("Etc/GMT")) {
                candidate = id;
                break;
            }
            if (candidate == null)
                candidate = id;
        }
    }
    if (candidate != null)
        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of(candidate)));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM