SimpleDateFormat在不同的时区JVM中表现不同。

6

我正在按照以下代码来创建一个在指定日期时间和指定时区的Date对象。
注意:我没有为jvm设置任何时区;但是我用不同的linux服务器时区测试了这个代码。

    String date = "20121225 10:00:00";
    String timeZoneId = "Asia/Calcutta";
    TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

    DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
                //This date object is given time and given timezone
    java.util.Date parsedDate = dateFormatLocal.parse(date + " "  
                     + timeZone.getDisplayName(false, TimeZone.SHORT));

    if (timeZone.inDaylightTime(parsedDate)) {
        // We need to re-parse because we don't know if the date
        // is DST until it is parsed...
        parsedDate = dateFormatLocal.parse(date + " "
                + timeZone.getDisplayName(true, TimeZone.SHORT));
    }

现在解析后的日期对象表现不同了
当我的jvm服务器运行在IST时
parsedDate.getTime() -- 1356409800000
parsedDate.toString() -- Tue Dec 25 10:00:00 IST 2012
在GMT中 --- 12/25/2012 04:30:00 GMT
当我的jvm服务器运行在EST时
parsedDate.getTime() -- 1356422400000
parsedDate.toString() -- Tue Dec 25 03:00:00 EST 2012
在GMT中 --- 12/25/2012 08:00:00 GMT

我的两个系统时间是同步的
Mon Dec 24 10:30:04 EST 2012
Mon Dec 24 21:00:48 IST 2012
我希望在两台机器上都获得相同的GMT时间。
这里出了什么问题?


我们能看到完整的代码吗?你现在的代码没有显示任何System.outs。 - kosa
只有当机器具有相同的GMT时间时,它们才能同步。请尝试在两台机器上使用System.currentTimeMillis()。这将为您提供GMT时间,而不考虑时区设置。如果它们不同,您必须修复它。 - Peter Lawrey
3
顺便提一下,三个字母的代码并不是唯一的。美国、欧洲、巴西和澳大利亚都有一个名为EST的时区。 - Peter Lawrey
在这种情况下,GMT时间应该是相同的。我怀疑EST在这种情况下是美国的时间。 - Peter Lawrey
2
因此,如果您使用setTimeZone而不是将其附加到字符串并尝试将其解析回来,则应忽略默认时区并使用您提供的任何时区。 - Peter Lawrey
显示剩余2条评论
3个回答

4

我认为问题在于您试图解析IST,但它的含义取决于您默认的“位置”。

Time Zone Abbreviation  Zone Description    Relative UTC
IST     Irish Summer Time   UTC+01
IST     Israeli Standard Time   UTC+02
IST     Iran Standard Time  UTC+0330
IST     Indian Standard Time    UTC+0530

如果您的位置是印度,它会按照您的期望处理IST,但如果您使用美国,则会猜测不同的时区。
解决方案是不使用三个字母的时区并显式设置它们。
String date = "20121225 10:00:00";
String timeZoneId = "Asia/Calcutta";
TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
dateFormatLocal.setTimeZone(timeZone);

Date parsedDate = dateFormatLocal.parse(date);

http://www.worldtimezone.com/wtz-names/wtz-ist.html


3
短名称不是识别时区的好方法,因为它不是唯一的;Java.util.TimeZone的Javadoc 给出了一个例子:“'CST' 可能是美国的 '中央标准时间' 和中国的 '中国标准时间'”。更普遍地说,与其将时区作为字符串传递,以便您的 DateFormat 对其进行解析,不如通过使用已有的 TimeZone 实例告诉您的 DateFormat 时区是什么。
    DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    dateFormatLocal.setTimeZone(timeZone);
    java.util.Date parsedDate = dateFormatLocal.parse(date);

(这也将自动处理夏令时,尽可能地处理。)

我目前正在尝试您的解决方案。请问在您的答复中,如何处理夏令时而不必在最大程度上检查"timeZone.inDaylightTime(parsedDate)"? - Kanagavelu Sugumar
@KanagaveluSugumar:因为您已将timeZone传递到您的dateFormatLocal实例中,所以它已经知道关于夏令时的一切,如果适合的话,它会自动推断。 (在可能的范围内。夏令时的一个结果是某些时间戳确实是模棱两可的,因为它们发生在DST转换之前不久,然后再次在转换之后不久。但你绝对不能对此做任何事情。) - ruakh

1
这是一个快速的Groovy脚本示例,演示了使用缩写时区名称可能会导致问题,结果将根据本地环境,特别是本地默认TimeZone而异。
假设您想解析2015-02-20T17:21:17.190EST,并且您认为EST=美国东海岸意义下的东部标准时间,即纽约时间。因此,您预期时期应该正好是1424470877190,或者是GMT:Fri,20 Feb 2015 22:21:17.190 GMT,因为这个ESTGMT-0500。这是一个测试脚本,展示了当前默认TimeZone如何影响对EST的解释。
import java.util.*
import java.text.*

SimpleDateFormat sdf

TimeZone.setDefault(TimeZone.getTimeZone("Australia/Sydney"))
printDefaultTimeZone()

sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz")

checkTime(sdf.parse("2015-02-20T17:21:17.190EST").getTime())
checkTime(sdf.parse("2015-02-20T17:21:17.190-0500").getTime())

TimeZone.setDefault(TimeZone.getTimeZone("EST"))
printDefaultTimeZone()

// same SDF

checkTime(sdf.parse("2015-02-20T17:21:17.190EST").getTime())
checkTime(sdf.parse("2015-02-20T17:21:17.190-0500").getTime())

printDefaultTimeZone()

// new SDF
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz")

checkTime(sdf.parse("2015-02-20T17:21:17.190EST").getTime())
checkTime(sdf.parse("2015-02-20T17:21:17.190-0500").getTime())

void printDefaultTimeZone() {
    println(TimeZone.getDefault().getDisplayName() + ":" +     TimeZone.getDefault().getRawOffset() / 3600 / 1000)
}

void checkTime(long time) {
    println(time + (time == 1424470877190L ? ": CORRECT" : ":"))
}

输出:

东部标准时间(新南威尔士):10 1424413277190: 1424470877190: 正确 东部标准时间:-5 1424413277190: 1424470877190: 正确 东部标准时间:-5 1424470877190: 正确 1424470877190: 正确

在上面的输出中,意外/不正确的日期时间是1424413277190 = GMT: Fri, 20 Feb 2015 06:21:17.190 GMT,比解析的EST时间(在这种情况下是Australia/Sydney变体的EST)早了11小时,因为夏令时适用于那个日期。

因此,您可以看到EST的解释取决于其构造时的默认TimeZone。在前两个转换批次中,默认TimeZoneAustralia/Sydney,它本身被缩写为EST,因此Java将缩写解释为这样。直到在更改默认TZ后构建新的SDF之后,我们才看到缩写按预期应用。
作为设置默认时区的替代方案,您还可以在SDF上设置一个Calendar实例:
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz")
sdf.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("America/New_York"), new Locale("en_US")))

这将使EST按最初预期进行解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接