通过时区偏移获取时区

Get TimeZone by timezone offset

我需要一个时区,并且我有时区偏移。

比如我有偏移量=14400000。我想要一个时区(任何地方,任何城市)。我可以在 SimpleDateFormat 中使用这个时区,所以它会打印出这个时区的正确时间。

(我重构了一下):

private TimeZone getTimeZone(int offset) {
    final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
    String[] ids = TimeZone.getAvailableIDs(offset);
    if (ids == null || ids.length == 0) {
        return UTC_TIME_ZONE;
    }

    String matchingZoneId = ids[0];
    return TimeZone.getTimeZone(matchingZoneId);
}

我得到:

sun.util.calendar.ZoneInfo[id="Asia/Baku",offset=14400000,dstSavings=0,useDaylight=false,transitions=68,lastRule=null]

我不喜欢这种方法,因为 TimeZone 会调用 ZoneInfoFile getZoneIds(int var1) 在这里。这是反编译后的代码。

 public static String[] getZoneIds(int var0) {
    ArrayList var1 = new ArrayList();
    String[] var2 = getZoneIds();
    int var3 = var2.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        String var5 = var2[var4];
        ZoneInfo var6 = getZoneInfo(var5);
        if (var6.getRawOffset() == var0) {
            var1.add(var5);
        }
    }

    var2 = (String[])var1.toArray(new String[var1.size()]);
    Arrays.sort(var2);
    return var2;
}

您可以看到,它循环遍历现有对象以构建区域 ID 数组,而我只需要一个!

我的问题是,是否有更有效的方法?我只需要 ANY 时区 ID 或给定偏移量的 单个 时区。

您可以为任何偏移构造一个 TimeZone 实例,使用 SimpleTimeZone:

new SimpleTimeZone(offset, "My TimeZone");

TimeZone class 与您通常使用它的 class 一起早已过时; CalendarGregorianCalendarDateFormatSimpleDateFormat

我建议您开始使用 java.time,现代 Java 日期和时间 API。与它一起工作通常要好得多。然后你需要一个 ZoneId 对象作为你的时区。对于具有常量偏移量的 ZoneId,我们创建 ZoneOffset 的实例,ZoneId.

的两个子 class 之一
    int offsetMilliseconds = 14_400_000;
    int offsetSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(offsetMilliseconds);
    // to check that we haven’t lost any precision, convert back to milliseconds and compare
    if (TimeUnit.SECONDS.toMillis(offsetSeconds) != offsetMilliseconds) {
        throw new IllegalArgumentException("Millisecond precision not supported: " + offsetMilliseconds);
    }
    ZoneId tzid = ZoneOffset.ofTotalSeconds(offsetMilliseconds / 1000);

    System.out.println(tzid);
    // test the time zone we got
    System.out.println(ZonedDateTime.now(tzid));

在我的电脑上刚刚打印出来:

+04:00
2018-01-16T19:44:22.863760+04:00

A ZoneOffset 只有秒的精度,而不是毫秒,这对于你的 14 400 000 毫秒的情况来说很好。如果您有一些毫秒数不构成整数秒数,您可能希望四舍五入为整数秒而不是抛出异常,由您决定。

仅当您需要将老式 TimeZone 对象传递给您现在无法更改或不想更改的某些遗留 API 时,才将 ZoneId我们得到了以上:

    TimeZone oldfashionedTimeZone = TimeZone.getTimeZone(tzid);

这将为您提供 TimeZone,显示名称为 GMT+04:00,并且(当然)偏移 4 小时。但是请注意,如果 TimeZone 无法识别您要转换到的时区(如果您的偏移量是奇数秒,则可能是这种情况),它会默认为您提供 GMT(只是其中之一旧 classes 的问题)。所以你应该检查你得到的 TimeZone 对象,例如像这样:

    if (oldfashionedTimeZone.getRawOffset() != offsetMilliseconds) {
        throw new IllegalStateException("Conversion to legacy TimeZone object failed");
    }

tl;博士

不要做你想做的事。

没有意义,因为在某一时刻共享偏移量的区域可能不会在过去或未来的其他时刻共享该偏移量。

区域是偏移量的历史

是正确的。这里有更多的讨论。

I need a single timezone, and I have timezone offset.

不太可能。

一个offset-from-UTC is nothing more than a number of hours, minutes, and seconds ahead of, or behind, UTC.

时区更多。区域是特定区域的人们使用的偏移量过去变化的历史。区域还具有针对该区域偏移量的当前和未来更改的规则。因此 时区总是比单纯的偏移量更可取,但前提是确定 。在你的情况下,你不知道区域,不应该猜测;坚持使用偏移量。

给定的 偏移量无法将您无歧义且无错误地引导至特定时区

  • 歧义,因为许多区域可能巧合地共享相同的偏移量。例如,Europe/Gibralter & Europe/Oslo & Africa/Lagos 今天都共享相同的偏移量,比 UTC 早一小时。因此,只有 +01:00 的 UTC 偏移量,您怎么知道哪个是合适的?
  • 错误,因为没有日期,您无法知道时区的偏移量。例如,上面列出的三个区域今天在 2017-2018 年冬季共享相同的偏移量,但在夏季直布罗陀和奥斯陆遵守夏令时 (DST),这意味着它们将一个小时偏移 two[=比 UTC 早 119=] 小时,而拉各斯仍比 UTC 早 一个 小时。从历史上看,由于世界各地的政治家对重新定义区域都有一种奇怪的倾向,因此抵消可能会因 DST 以外的其他原因而发生变化。例如,委内瑞拉在十年内两次将其偏移量改变了半小时。作为近年来的另一个例子,俄罗斯和土耳其一直在改变他们对使用 DST 的想法,恢复到他们之前的静态偏移,或者更改为永久领先于他们之前的静态偏移。

请注意 ZoneRules class 如何为您提供特定区域的偏移量 只有在您提供时刻 (日期时间值)时作为参数传递。

ZoneOffset offset = ZoneId.of( "Africa/Tunis" )        // Instantiate a `ZoneId`.
                        .getRules()                    // Obtain a `ZoneRules` object from that `ZoneId` object.
                        .getOffset( Instant.now() ) ;  // Interrogate that `ZoneRules` for an offset-from-UTC in use at a specific moment in history.

I just need ANY timezone id or a single timezone of given offset.

这是不可取的。如上所述,在一个日期共享特定偏移量的时区可能不会在另一个日期共享该偏移量。每个区域都因偏移量变化的历史而异。所以随意选择一个区域是不明智的。

java.time

如另一个答案中所述,TimeZoneZoneInfo classes 现在是遗留的。它们是被 java.time classes 取代的麻烦的旧日期时间 classes 的一部分。

改为使用 ZoneOffset(与 UTC 的偏移量)和 ZoneId(时区)。

指定 proper time zone name in the format of continent/region, such as America/Montreal, Africa/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们 不是 真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Montreal" );

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

从哪里获得java.time classes?

  • Java SE 8, Java SE 9,及以后
    • 内置。
    • 标准 Java API 的一部分,带有捆绑实施。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6 and Java SE 7
  • Android
    • Android java.time classes.
    • 捆绑实施的更高版本
    • 对于较早的Android,ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.