一些时区的Java util日期格式的毫秒似乎不正确

4

据我所知,java.util.Date将日期储存为自1970年1月1日00:00以来的毫秒数...

因此,我尝试了下面这段代码:

public void testDateFormatBehavior()
{
    DateFormat dfNoDay = new SimpleDateFormat(
            "MMM d H:m:s zzz yyyy"
            );
    // This one should be correct as IST = GMT+5.30
    String expStrDateBegIST1 = "Jan 01 05:30:01 IST 1970";
    // Instead, this one seems to do the conversion to
    // Jan 01 00:00:00 GMT 1970
    String expStrDateBegIST2 = "Jan 01 02:00:01 IST 1970";
    String expStrDateBegUTC = "Jan 01 00:00:01 GMT 1970";
    String expStrDateBegCET = "Jan 01 01:00:00 CET 1970";
    // Should convert to Jan 01 06:00:00 GMT 1970 as CST = GMT-6
    String expStrDateBegCST = "Jan 01 00:00:00 CST 1970"; 
    // This is EST, which is GMT+6... 
    String expStrDateBegEST = "Jan 01 10:00:00 EST 1970"; 
    try {
        Date dBegIST1 = dfNoDay.parse(expStrDateBegIST1);
        Date dBegIST2 = dfNoDay.parse(expStrDateBegIST2);
        Date dBegUTC = dfNoDay.parse(expStrDateBegUTC);
        Date dBegCET = dfNoDay.parse(expStrDateBegCET);
        Date dBegCST = dfNoDay.parse(expStrDateBegCST);
        Date dBegEST = dfNoDay.parse(expStrDateBegEST);
        System.out.println("IST1 milliseconds: " + dBegIST1.getTime());
        System.out.println("IST2 milliseconds: " + dBegIST2.getTime());
        System.out.println("UTC milliseconds: " + dBegUTC.getTime());
        System.out.println("CET milliseconds: " + dBegCET.getTime());
        System.out.println("CST milliseconds: " + dBegCST.getTime());
        System.out.println("EST milliseconds: " + dBegEST.getTime());
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

输出结果:
IST1 milliseconds: 12601000
IST2 milliseconds: 1000
UTC milliseconds: 1000
CET milliseconds: 0
CST milliseconds: 21600000
EST milliseconds: 0

UTC毫秒线是正确的,因为我们指定了从1970年1月1日00:00:01开始计算的秒数。

CET是正确的。 CST也是正确的,因为这个毫秒数是在1970年1月1日之后6小时。

然而,IST转换很奇怪。

http://wwp.greenwichmeantime.com/to/ist/to-gmt/index.htm

IST似乎是GMT+5:30。但在我的Java代码中,它认为它是GMT+2:00。

此外,EST是不正确的。它认为EST是GMT+10:00,而不是GMT+6:00。GMT+10:00是AEST,而不是EST。 http://wwp.greenwichmeantime.com/time-zone/australia/time-zones/eastern-standard-time/

我做错了什么吗?

2个回答

5

TimeZone javadoc中解释:

三个字母的时区ID

为了与JDK 1.1.x兼容,还支持一些其他的三个字母的时区ID(例如“PST”、“CTT”、“AST”)。但是,它们的使用已被弃用,因为相同的缩写通常用于多个时区(例如,“CST”可以是美国的“中央标准时间”和中国的“中国标准时间”),而Java平台只能识别其中的一个。


问题在于您假定时区缩写始终指定特定的时区。

“IST”是有歧义的:印度标准时间/爱尔兰标准时间/以色列标准时间。同样,“EST”可以表示美国东部标准时间或澳大利亚东部标准时间。


Joda-time的时区列表显示了明确的Olson名称和当前时区偏移量。 使用Olson名称来标识时区比使用java.util类更好,并且建议使用Joda-time。

我应该使用Java日期和时间类还是选择像Joda Time这样的第三方库?讨论了许多人选择使用Joda-time的原因。


仅测试IST日期。如果您设置了dfNoDay.setTimeZone(TimeZone.getTimeZone("IST")),则可以检索到正确的时间。 - Sotirios Delimanolis
1
@SotiriosDelimanolis,当您使用模糊的标识符时,没有单一的“正确”时间。 - Mike Samuel
当然可以,但是为什么SimpleDateFormat使用两种不同的方式来获取和设置TimeZone呢? - Sotirios Delimanolis
1
@SotiriosDelimanolis,我不知道。很久以前我就停止使用java.util的日期相关内容了,因为我遇到了太多奇怪的问题,现在也记不清细节了。 - Mike Samuel
好的,虽然解释和javadoc参考很好。 - Sotirios Delimanolis
1
请注意,答案链接的Joda-Time时区列表略有过时。该页面描述了如何运行代码以查看当前列表。 - Basil Bourque

0

简而言之

ZonedDateTime.parse(   // Parse string as `ZonedDateTime` object.
    "1969-12-31T19:00-05:00[America/New_York]"   // This zone on that date is five hours behind UTC. 
)                      // So last day of 1969 at 7 PM there is simultaneously first moment of January 1, 1970 in UTC (00:00:00).
.toInstant()           // .toString() --> 1970-01-01T00:00:00Z
.toEpochMilli()        // --> zero seconds since epoch, as we are *at* the epoch.

0

细节

Samuel的回答是正确的。

避免使用旧日期时间类

现代方法使用java.time类来替代麻烦的旧日期时间类,如Date

时区

continent/region的格式指定适当的时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。永远不要使用三到四个字母的缩写,如ESTIST,因为它们不是真正的标准化时区,也不是唯一的!

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;  

ISO 8601

在将日期时间值序列化为文本时,请使用标准的 ISO 8601 格式。

java.time 类默认使用 ISO 8601 格式进行解析/生成字符串,因此无需指定格式化模式。

ZonedDateTime 类聪明地扩展了标准格式,通过在方括号中附加时区名称来显示时间区域。

String input = "1970-01-01T05:30:01+05:30[Asia/Kolkata]" ;  // "Jan 01 05:30:01 IST 1970"
ZonedDateTime zdt = ZonedDateTime.parse( input ) ;
String output = zdt.toString() ;  // Generate string in standard ISO 8601 format.

1970-01-01T05:30:01+05:30[亚洲/加尔各答]

为了通过UTC的挂钟时间观察同一时刻,请提取一个Instant。我们预计是UTC当天的第二秒,因为在那个日期,印度时间比UTC提前五个半小时。

Instant instant = zdt.toInstant() ;

instant.toString(): 1970-01-01T00:00:01Z

我建议避免将时间作为从纪元开始的计数,因为这种方式存在歧义和容易出错。但如果您坚持使用,可以获取自1970年1月1日UTC(1970-01-01T00:00:00Z)第一时刻以来的毫秒数。

在这种情况下,我们期望得到一千毫秒(1秒)的结果。

long millisSinceEpoch = instant.toEpochMilli() ;

1000

查看北美东海岸时区的某一时刻:

String input = "1970-01-01T10:00:00-05:00[America/New_York]";  // "Jan 01 10:00:00 EST 1970"
ZoneId z = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.parse( input );
Instant instant = zdt.toInstant();
long millisSinceEpoch = instant.toEpochMilli();

zdt.toString(): 1970-01-01T10:00-05:00[美国/纽约]

instant.toString(): 1970-01-01T15:00:00Z

millisSinceEpoch: 54000000

或者在同一时区的另一个时刻。这里我们尝试使用纪元参考日期前一天晚上的7点钟。当时的美国/纽约时区比协调世界时提前五个小时。因此,当调整为协调世界时时,我们正好落在午夜的时刻,即纪元参考日期的第一个时刻,1970-01-01T00:00:00Z。这意味着1969-12-31T19:00-05:00[美国/纽约]距离纪元开始时间为零秒。

String input = "1969-12-31T19:00-05:00[America/New_York]" ;
ZoneId z = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.parse( input );
Instant instant = zdt.toInstant();
long millisSinceEpoch = instant.toEpochMilli();

zdt.toString(): 1969-12-31T19:00-05:00[美国/纽约] instant.toString(): 1970-01-01T00:00:00Z millisSinceEpoch: 0

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了老旧的传统日期时间类,如{{link3:java.util.Date}}、{{link4:Calendar}}和{{link5:SimpleDateFormat}}。

Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。

想要了解更多,请参阅 Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是 JSR 310

使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序,您可以直接与数据库交换 java.time 对象,无需使用字符串或 java.sql.* 类。

如何获取 java.time 类?

ThreeTen-Extra 项目通过添加额外的类扩展了 java.time。该项目是 java.time 可能未来增加内容的试验场。您可能会在这里找到一些有用的类,例如 Interval, YearWeek, YearQuarter更多


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