Java时区令人挠头

  • 本文关键字:时区 Java java timezone
  • 更新时间 :
  • 英文 :


我想用夏令时格式保存一些用户时区
我的目标是在执行代码时获得正确的GMT偏移量
为了找出我的最佳选择,我写了以下内容:

ArrayList<String> list = new ArrayList<String>();
list.add( "EST");
list.add( "EDT");
list.add( "America/New_York");
long now = System.currentTimeMillis();
for( String tzID: list) {
    TimeZone tz = TimeZone.getTimeZone( tzID);
    System.out.println( tzID + " now=" + tz.getOffset( now) / 3600000 + " / +182=" + tz.getOffset( now + ( 182 * 86400000)) / 3600000);
}

简而言之,现在和182天后给我补偿
9月3日执行,输出为

EST now=-5 / +182=-5
EDT now=0 / +182=0
America/New_York now=-4 / +182=-4

这是出乎意料的,有几个原因
1) 为什么美国/纽约没有给出-4/-5?,它不是应该对日期敏感吗
2) 为什么EDT==UTC?

java.time

该问题和接受的答案使用java.util日期时间API,这是2012年的正确做法。2014年3月,现代日期时间API作为Java 8标准库的一部分发布,该标准库取代了传统日期时间API,此后强烈建议切换到现代日期时间API java.time

使用java.time的解决方案

您可以使用ZonedDateTime,它会自动调整给定ZoneId的时区偏移。

演示:

import java.time.ZoneId;
import java.time.ZonedDateTime;
class Main {
    public static void main(String[] args) {
        ZoneId zone = ZoneId.of("America/New_York");
        ZonedDateTime now = ZonedDateTime.now(zone);
        ZonedDateTime after182Days = now.plusDays(182);
        System.out.println(zone + " now=" + now.getOffset() + " / +182=" + after182Days.getOffset());
    }
}

截至目前的输出

America/New_York now=-05:00 / +182=-04:00

在线演示

跟踪:日期时间了解有关现代日期时间API的更多信息。

不要使用三个字母的时区ID:Java 7 Timezone文档中的注意事项:

三个字母的时区ID

为了与JDK1.1.x兼容,其他一些三个字母的时区ID(如"PST"、"CTT"、"AST")是也得到支持。但是,不赞成使用它们,因为缩写经常用于多个时区(例如;可以是美国的";"中央标准时间";以及";中国标准时间"),和Java平台则只能识别它们中的一个。

您遇到的一个问题是182*86400000溢出。如果您使用

long now = System.currentTimeMillis();
for( String tzID: "EST,EDT,America/New_York".split(",")) {
    TimeZone tz = TimeZone.getTimeZone( tzID);
    System.out.println( tz.getDisplayName() + " now=" + tz.getOffset( now) / 36e5 
                     + " / +182=" + tz.getOffset( now + 182 * 86400000L) / 36e5);
}

打印

Eastern Standard Time now=-5.0 / +182=-5.0
Greenwich Mean Time now=0.0 / +182=0.0
Eastern Standard Time now=-4.0 / +182=-5.0

如果您查看javadoc和getTimeZone的源代码,您可以看到

 * @return the specified <code>TimeZone</code>, or the GMT zone if the given ID
 * cannot be understood.
public static synchronized TimeZone getTimeZone(String ID) {
return getTimeZone(ID, true);
}
private static TimeZone getTimeZone(String ID, boolean fallback) {
TimeZone tz = ZoneInfo.getTimeZone(ID);
if (tz == null) {
    tz = parseCustomTimeZone(ID);
    if (tz == null && fallback) {
    tz = new ZoneInfo(GMT_ID, 0);
    }
}
return tz;
}

简而言之,EDT无法识别,因此它变为GMT。

我怀疑这就是问题所在:

now + ( 182 * 86400000)

带括号的算术表达式溢出32位。你可能想要:

now + ( 182 * 86400000L)

然而,这仍然假设任何夏令时都将适用大约六个月,而现实世界中肯定不是这样。例如,看看圣保罗时区,它在10月和2月切换——所以如果你在9月运行代码,你最终会看到-3/-3。即使是夏令时大约每六个月开启/关闭一次的时区,你也很可能发现每年连续182天没有切换(几乎是根据定义,因为这不到半年)。

目前还不清楚你到底想做什么,但我怀疑你真的应该保存时区ID,例如"America/New_York"。几乎所有其他事情都是在找麻烦。

相关内容

  • 没有找到相关文章

最新更新