我想用夏令时格式保存一些用户时区
我的目标是在执行代码时获得正确的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"。几乎所有其他事情都是在找麻烦。