我需要一个时区,而且我有时区偏移量。
例如,我有偏移量 =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 数组,而我只需要一个!
我的问题,有没有更有效的方法?我只需要任何时区 ID 或给定偏移量的单个时区。
tl;博士
不要做你正在尝试的事情。
这没有意义,因为在过去或将来的其他时刻共享偏移的区域可能不会共享该偏移。
区域是偏移的历史记录
Ole V.V.的回答是正确的。这里有更多的讨论。
我需要一个时区,并且我有时区偏移量。
真的不可能。
UTC 偏移量只不过是 UTC 之前或之后的小时、分钟和秒数。
时区远不止于此。区域是特定区域用户使用的偏移量过去变化的历史记录。区域还具有针对该区域偏移量的当前和将来更改的规则。因此,时区总是比单纯的偏移量更可取,但只有在确定的情况下。在您的情况下,您不知道区域,也不应该猜测;只需坚持使用偏移量即可。
给定的偏移量无法将您引导到特定时区而不会产生歧义和错误。
- 歧义,因为许多区域可能因巧合而共享相同的偏移量。例如,
Europe/Gibralter
&Europe/Oslo
&Africa/Lagos
今天共享相同的偏移量,比 UTC 早一小时。因此,只有 UTC 偏移量为+01:00
您如何知道哪个合适? - 错误,因为如果没有日期,您将无法知道哪个偏移量适用于时区。例如,上面列出的三个区域在2017-2018年冬季共享相同的偏移量,但在夏季,直布罗陀和奥斯陆遵守夏令时(DST),这意味着它们将一个小时偏移到比UTC早两个小时,而拉各斯仍然比UTC早一小时。从历史上看,抵消可能由于夏令时以外的其他原因而发生变化,因为世界各地的政治家都有重新定义区域的奇怪倾向。例如,委内瑞拉十年来两次将其偏移量移动了半小时。作为近年来的另一个例子,俄罗斯和土耳其一直在改变他们的想法,即使用DST,恢复到以前的静态偏移量,或者更改为永久保持以前的静态偏移量。
请注意,仅当您提供作为参数传递的时刻(日期时间值)时,ZoneRules
类才能为您提供特定区域的偏移量。
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.
我只需要任何时区 ID 或给定偏移量的单个时区。
这是不可取的。如上所述,在一个日期共享特定偏移量的时区可能不会在另一个日期共享该偏移量。每个区域因偏移量变化的历史而异。因此,随意选择一个区域是不明智的。
java.time
正如另一个答案中所解释的,TimeZone
和ZoneInfo
类现在是遗产。它们是麻烦的旧日期时间类的一部分,这些类被java.time类所取代。
请改用ZoneOffset
(UTC 偏移量)和ZoneId
(时区)。
以continent/region
的格式指定正确的时区名称,例如America/Montreal
、Africa/Casablanca
或Pacific/Auckland
。切勿使用 3-4 个字母的缩写,例如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
ZoneId z = ZoneId.of( "America/Montreal" );
关于 java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧传统日期时间类,如java.util.Date
、Calendar
和SimpleDateFormat
。
Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。
要了解更多信息,请参阅 Oracle 教程。并搜索堆栈溢出以获取许多示例和解释。规范为 JSR 310。
从哪里获得java.time类?
Java SE- 8、Java SE 9及更高版本
- 内置。
- 具有捆绑实现的标准 Java API 的一部分。
- Java 9添加了一些小功能和修复。
Java SE 6 和 Java SE 7 大部分 - java.time 功能在ThreeTen-Backport中向后移植到 Java 6 和 7。
Android- 更高版本的 java.time 类的 Android 捆绑实现。
- 对于早期的Android,ThreeTenABP项目适应了ThreeTen-Backport(如上所述)。请参阅如何使用ThreeTenABP...。
ThreeTen-Extra项目通过额外的类扩展了java.time。这个项目是未来可能添加到java.time的试验场。你可以在这里找到一些有用的类,如Interval
、YearWeek
、YearQuarter
等。
您可以使用 SimpleTimeZone 为任何偏移量构造一个时区实例:
new SimpleTimeZone(offset, "My TimeZone");
TimeZone
类与您通常使用它的类一起早已过时;Calendar
、GregorianCalendar
、DateFormat
和SimpleDateFormat
。
我建议您开始使用java.time
,即现代Java日期和时间API。通常,使用起来要好得多。然后,您将需要一个时区的ZoneId
对象。对于具有常量偏移量的ZoneId
,我们创建一个ZoneOffset
的实例,ZoneId
的两个子类之一。
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
ZoneOffset
只有秒的精度,而不是毫秒,这对于 14 400 000 毫秒的情况很好。如果你的毫秒数不构成整数秒,你可能希望四舍五入到整数秒,而不是抛出一个异常,这是你的决定。
仅当您需要将老式的TimeZone
对象传递给某个您无法更改或不想更改的旧 API 时,才转换我们上面得到的ZoneId
:
TimeZone oldfashionedTimeZone = TimeZone.getTimeZone(tzid);
这将为您提供一个显示名称为GMT+04:00
的TimeZone
,并且(当然)偏移量为 4 小时。但是请注意,如果TimeZone
无法识别您尝试转换为的时区(如果您的偏移量是奇数秒,则可能出现这种情况),它将默认为您提供 GMT(只是旧类的问题之一)。因此,您可能应该检查您获得的TimeZone
对象,例如:
if (oldfashionedTimeZone.getRawOffset() != offsetMilliseconds) {
throw new IllegalStateException("Conversion to legacy TimeZone object failed");
}