SimpleDateFormat 在不同时区 JVM 中的行为不同

Posted

技术标签:

【中文标题】SimpleDateFormat 在不同时区 JVM 中的行为不同【英文标题】:SimpleDateFormat behaves differently in different timezones JVM 【发布时间】:2012-12-14 01:33:30 【问题描述】:

我正在按照下面的代码在指定的 dateTime 上使用指定的时区创建一个 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));
    

现在 parsedDate 对象的行为有所不同 当我的 jvm 服务器在 IST 中运行时 parsedDate.getTime() -- 1356409800000 parsedDate.toString() -- 2012 年 12 月 25 日星期二 10:00:00 IST 在 GMT --- 12/25/2012 04:30:00 GMT当我的 jvm 服务器在 EST 中运行时 parsedDate.getTime() -- 1356422400000 parsedDate.toString() -- 2012 年 12 月 25 日星期二 03:00:00 EST 格林威治标准时间 --- 2012 年 12 月 25 日 08:00:00 格林威治标准时间

我的两个系统时间是同步的 2012 年 12 月 24 日星期一 10:30:04 EST 2012 年 12 月 24 日星期一 21:00:48 IST 我期望在两台机器上我应该得到相同的 GMT 时间。 这里出了什么问题?

【问题讨论】:

我们能看到完整的代码吗?您的代码现在没有显示任何 System.outs 只有当它们的 GMT 时间相同时,机器才会同步。尝试在两台机器上使用 System.currentTimeMillis()。无论时区设置如何,这将为您提供 GMT 时间。如果这些不一样,您必须修复它。 BTW 三字母代码不是唯一的。美国、欧洲、巴西和澳大利亚都有 EST。 @PeterLawrey 实际上它不是两台不同的机器。我刚刚更改了单机的时区并重新启动了我的应用程序。 因此,如果您使用 setTimeZone 设置 timezoen 而不是将其附加到 String 并尝试将其解析回来,它应该忽略您的默认时区并使用您给它的任何内容。 【参考方案1】:

我认为问题在于您正在尝试解析 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

【讨论】:

【参考方案2】:

短名称不是识别时区的好方法,因为它不是唯一的; the Javadoc for java.util.TimeZone 举例说明“‘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)”的情况下处理夏令时? @KanagaveluSugumar:因为您已将timeZone 传递给您的dateFormatLocal 实例,它已经知道有关夏令时的所有信息,因此它会在适当的时候推断它。 (在可能的范围内。夏令时的一个后果是某些时间戳确实是模棱两可的,因为它们在 DST 转换之前不久发生,然后在转换之后不久再次发生。但您绝对无能为力。)【参考方案3】:

这里有一个简短的Groovy 脚本来演示如何使用缩写的时区名称是一个问题,并且结果会因本地环境而异,尤其是本地默认的TimeZone

假设您要解析 2015-02-20T17:21:17.190EST,并且您考虑美国东海岸意义上的 EST = 东部标准时间,即纽约市时间。因此,您希望纪元时间恰好是 1424470877190GMT: 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" : ":"))

输出:

Eastern Standard Time (New South Wales):10 1424413277190: 1424470877190: CORRECT Eastern Standard Time:-5 1424413277190: 1424470877190: CORRECT Eastern Standard Time:-5 1424470877190: CORRECT 1424470877190: CORRECT

在上面的输出中,意外/不正确的日期时间是 1424413277190 = GMT: Fri, 20 Feb 2015 06:21:17.190 GMT,比解析的 EST 时间早 11 小时(在本例中是 ESTAustralia/Sydney 变体),因为夏令时适用于该日期。

因此您可以看到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 的效果,正如最初预期的那样。

【讨论】:

以上是关于SimpleDateFormat 在不同时区 JVM 中的行为不同的主要内容,如果未能解决你的问题,请参考以下文章

SimpleDateFormat 解析丢失时区 [重复]

设置时区的 SimpleDateFormat 获得正确的值但区域错误

Java SimpleDateFormat时间解析时区问题

SimpleDateFormat 与时区

Calendar日期类详解SimpleDateFormat时区Date夏令时常用方法,日期差获取当前时间

遇到的问题---java---使用SimpleDateFormat进行时区加8小时后出现24点的数据,导致时间异常无法入库