Java日期时区打印不同年份的不同时区,需要解决方法

19

在测试我的应用程序时,我遇到了一个奇怪的问题。当我输入一个1945年之前的日期时,它会改变时区。

我有一个简单的程序来展示这个问题。

public static void main(String[] args) {
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ");
    Calendar calendar = Calendar.getInstance();

    System.out.println("**********Before 1945");
    calendar.set(1943, Calendar.APRIL, 12, 5, 34, 12);
    System.out.println(format.format(calendar.getTime()));
    System.out.println(calendar.getTime());

    System.out.println("**********After 1945");
    calendar.set(1946, Calendar.APRIL, 12, 5, 34, 12);
    System.out.println(format.format(calendar.getTime()));
    System.out.println(calendar.getTime());
}
我收到的输出如下:-
**********Before 1945
1943-04-12 05:34:12+0630
Mon Apr 12 05:34:12 IDT 1943

**********After 1945
1946-04-12 05:34:12+0530
Fri Apr 12 05:34:12 IST 1946

对于第一个,我得到的是+0630IDT,而对于第二个,我得到了+0530IST,这是预期的。

编辑:

在查看 @Elliott Frisch 的答案后,我尝试了1942年之前的日期:

calendar.set(1915, Calendar.APRIL, 12, 5, 34, 12);
System.out.println(format.format(calendar.getTime()));
System.out.println(calendar.getTime());

输出:-

1915-04-12 05:34:12+0553
Mon Apr 12 05:34:12 IST 1915

再次提醒,这里显示的是+0553,但标注的是IST。应该是+0530 才对。

为了进行比较,我尝试了同样的事情在javascript中:

new Date("1946-04-12 05:34:12") //prints Fri Apr 12 1946 05:34:12 GMT+0530 (IST)
new Date("1943-04-12 05:34:12") //prints Fri Apr 12 1943 05:34:12 GMT+0530 (IST)
new Date("1915-04-12 05:34:12") //prints Mon Apr 12 1915 05:34:12 GMT+0530 (IST)

这很好用。 我想知道为什么Java会受到影响,如果这是一个已知的问题,可能有什么解决方法

提前感谢。


3
在ECMAScript 2016之前,JavaScript实现预期应用当前的夏令时规则,就好像它们一直适用一样(即它们不适用由不同地理区域的各种司法管辖区定义的夏令时开始和停止日期的历史更改)(参见ES5 §15.9.1.8)。虽然现在推荐使用IANA时区数据库,但实现不是必须的(大多数可能都没有这样做)。 - RobG
4个回答

13

这很可能是Java(而不是JavaScript)的预期行为。

如上面RobG的评论所示,编程语言可能支持历史时间规则(例如夏令时和时区偏移),也可能不支持。在您的情况下,似乎您的Java运行时支持它,而您的JavaScript运行时不支持它。

印度历史时区和夏令时规则的列表可以在timeanddate.com找到。该列表确认了Java日期的时区偏移量:

  • 1941年之前:UTC+5:53:20
  • 1941年:UTC+6:30
  • 1942年:UTC+5:30
  • 1943-44年:UTC+6:30
  • 1945年之后:UTC+5:30

将您的日期与Wolfram|Alpha进行比较,进一步确认了您Java日期的UTC偏移量:1915年1943年1946年

维基百科提供了更多关于印度时间的信息:

加尔各答时间官方上一直维持为一个独立的时区,直到1948年。

加尔各答时间 可以被指定为UTC+5:54或UTC+5:53:20。后者与您的代码示例一致。

维基百科条目进一步指出,当前具有UTC+5:30偏移量的IST时区直到1955年才在印度全境生效。

正如Elliott Frisch所指出的并由上面的timeanddate.com链接确认,夏令时在二战期间生效。在你对他的回答的评论中,你说:

这是我们应该在数据库中保存和在应用程序中使用的方式吗?还是我们要使用某些变通方法?

我想这取决于情况。如果你真的需要准确地将日期区分为时间点,你需要一个与时区无关的表示法,例如UTC或Unix时间(或自Unix纪元以来的毫秒数)。如果你只使用来自相同时区的本地日期,一个简单的字符串表示(例如YYYY-MM-DD hh:mm:ss)就可以满足需求。


10

有一场战争。根据维基百科链接,印度在第二次世界大战期间(1942-1945年)实行了夏令时。


嘿,谢谢你的回答,我想知道为什么Java会受到影响。请看一下我更新的问题,编辑部分 - Mritunjay
嗨,我想知道这是否是我们应该在数据库中保存和在应用程序中使用的方式,还是我们需要使用一些变通方法。提前致谢。 - Mritunjay

7

java.time

避免使用最早版本Java中捆绑的麻烦的旧日期时间类。现在已经过时,被java.time类取代。
ZoneId z = ZoneId.of( "Asia/Kolkata" );  // "Asia/Calcutta"
LocalTime lt = LocalTime.of( 5 , 34 , 12 );

ZonedDateTime zdt1943 = ZonedDateTime.of( LocalDate.of( 1943 , Month.APRIL , 12 ) , lt , z );
ZonedDateTime zdt1945 = ZonedDateTime.of( LocalDate.of( 1945 , Month.APRIL , 12 ) , lt , z );
ZonedDateTime zdt1946 = ZonedDateTime.of( LocalDate.of( 1946 , Month.APRIL , 12 ) , lt , z );
ZonedDateTime zdt2016 = ZonedDateTime.of( LocalDate.of( 2016 , Month.APRIL , 12 ) , lt , z );

将数据转储到控制台。

System.out.println( "zdt1943: " + zdt1943 );
System.out.println( "zdt1945: " + zdt1945 );
System.out.println( "zdt1946: " + zdt1946 );
System.out.println( "zdt2016: " + zdt2016 );

请参见IdeOne.com中的实时代码

运行时,我们看到与您的问题描述相同的行为,战争期间有六个半小时的UTC偏移量,之后是五个半小时。无论使用{{link3:Asia/Kolkata}}还是Asia/Calcutta,我们得到相同的行为。Java.time类使用{{link4:tzdata}}(前身为Olson Database)。

zdt1943: 1943年4月12日05:34:12+06:30 [亚洲/加尔各答]

zdt1945: 1945年4月12日05:34:12+06:30 [亚洲/加尔各答]

zdt1946: 1946年4月12日05:34:12+05:30 [亚洲/加尔各答]

zdt2016: 2016年4月12日05:34:12+05:30 [亚洲/加尔各答]

问题是...

当我输入一个1945年之前的日期时,它会更改时区。

不,它不会改变时区。结果显示,在早期年份,“5:34”被定义为比 UTC提前六个半小时,而在后来的年份中,定义变为比UTC提前五个半小时。正如“5:34”在夏季意味着西雅图比UTC晚八个小时,而在冬季则比UTC晚七个小时,因为 夏令时(DST)的荒唐。

但我怀疑这些可能是错误的值;请继续阅读。

加尔各答时间

我们看到的行为似乎与我对Wikipedia页面 加尔各答时间 的阅读不符。该页面描述了奇怪的偏移量,超出整点或半点,如UTC+05:54, 但我们在任何代码示例中都没有看到这种情况。

因此,我怀疑tzdata不包含印度的这些历史数据。但只是一个外行的猜测;我不是历史学家。

不要使用日期时间类型来存储历史值

虽然我不知道印度这个历史时期的具体时间及其在tzdata中的处理情况,但似乎我们的任何一个日期时间库都无法处理这些历史细节。

但我们不应该指望有这样的处理!请明确,tzdata不保证完全覆盖1970年之前的时区

提到历史日期时间值时,建议您仅使用文本而不是任何日期时间数据类型。数据类型的目的是进行验证和计算,而对于历史值,您可能既没有验证也没有计算的需求。我无法想象您正在确定1943年之后发票逾期的天数。

也许您应该编辑您的问题以描述为什么要如此精确地将这些历史日期时间值存储在数据库中。如果您只是在尝试实验并注意到了这些问题,请注意,您不应该期望在遥远的过去(1970年之前)或未来(政治家有时会突然更改时区定义)进行精确的日期时间处理。

总结: 尝试精确处理历史日期时间值存在各种问题,对我来说似乎毫无意义。

有什么可能的解决方法吗?

我建议使用ISO 8601格式的“本地”日期时间值作为文本,不包含任何时区信息。


1
我建议在数据库中保留日期的纪元等效值。我认为,无论夏令时如何,一个时期的时间和日期都代表实际情况,无论是IDT还是IST。我将使用示例https://dev59.com/4mw15IYBdhLWcg3wVKF1#6687502将所有日期转换为纪元并存储到数据库中。我将反转逻辑以显示来自数据库的日期时间以及IDT / IST指示器,以避免用户混淆。

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