时区更改时如何将时间添加到 DateTime?

How to add time to DateTime when timezone changes?

我有以下代码将时间添加到 DateTime 实例:

        DateTime d1 = new DateTime();
        d1 = d1.withZone(DateTimeZone.forID("Europe/London"));
        ArrayList<String> timeList = new ArrayList<String>();

        for(int x = 1; x <= 10; x++) {
        //Adds six hours to the DateTime instance.
                d1 = d1.plusHours(6);
                d1 = d1.plusMinutes(0);
                d1 = d1.plusSeconds(0);
                timeList.add(d1.toString());
        }

这将创建一组 10 次以添加到数组列表中。但是,假设在添加 6 小时后夏令时发生了变化。由于时区更改,额外的小时数 added/removed 如何生成正确的时间?目前它不会 remove/add 使用此方法的额外时间。

例如,如果我要在 2015 年 10 月 24 日 10:00am 开始 运行 代码,我希望生成以下时间。请注意,时区在 2015 年 10 月 25 日 02:00am 更改。

24/10/2015 10:00:00 BST
24/10/2015 16:00:00 BST
24/10/2015 22:00:00 BST
25/10/2015 05:00:00 GMT
25/10/2015 11:00:00 GMT
25/10/2015 17:00:00 GMT
25/10/2015 23:00:00 GMT
26/10/2015 05:00:00 GMT
26/10/2015 11:00:00 GMT
26/10/2015 17:00:00 GMT

通常我会将正确的答案留给 Jon Skeet(他的最后一条评论或多或少是我认为的答案),但现在有另外两个不可接受的答案错过了关键点。

你的“问题”可以缩小到这些行和(错误的)期望:

24/10/2015 22:00:00 BST

25/10/2015 05:00:00 GMT

您正确地写道,英国夏令时切换回冬令时发生在 2015 年 10 月 25 日 2:00 上午。这意味着标记为“01”的小时发生了两次(重叠情况),因为时钟拨回并在该小时重复。因此,作为时钟位置的名义小时数必须 增加一个小时 才能获得以小时为单位的实际物理持续时间。数学上:

nominal duration + one hour = real duration (= 6 real hours)
=> nominal duration = (6 - 1) hours = 5 virtual hours

请记住,像“24/10/2015 22:00:00 BST”这样的时间戳(在 ISO 偏移量表示法中:“2015-10-24T22:00:00+01” ) 代表全局物理瞬间,因此这些瞬间之间的时间增量表示物理持续时间。在原来的瞬间 增加了六个小时的持续时间 包含一个额外的小时 但是你必须 从实际时间 中删除一个小时才能得到标称持续时间(以时钟位置测量 - 参见上面给定等式的第二部分)。因此在即时符号中:

[2015-10-24T22:00+01] + 6 physical hours = 
  [2015-10-25T04:00+01] = [2015-10-25T03:00+00] = [2015-10-25T03:00Z]

并且在名义上的本地时间戳符号中(只看时钟位置):

[2015-10-24T22:00] + 5 virtual hours (clock positions) = [2015-10-25T03:00]

所以重复一个时钟位置会减少标称持续时间而不是增加它。

这就是 Joda-Time 正确的做法:

DateTime d1 = new DateTime(2015, 10, 24, 10, 0, 0, 0, DateTimeZone.forID("Europe/London"));

for (int x = 1; x <= 10; x++) {
    d1 = d1.plusHours(6);
    System.out.println("> " + d1.toString());
}

> 2015-10-24T16:00:00.000+01:00
> 2015-10-24T22:00:00.000+01:00
> 2015-10-25T03:00:00.000Z
> 2015-10-25T09:00:00.000Z
> 2015-10-25T15:00:00.000Z
> 2015-10-25T21:00:00.000Z
> 2015-10-26T03:00:00.000Z
> 2015-10-26T09:00:00.000Z
> 2015-10-26T15:00:00.000Z
> 2015-10-26T21:00:00.000Z

不用担心,用java.time

  • 显然您正在使用 Joda-Time 库。请改用 java.time。
  • java.time classes 自动处理夏令时 (DST) 切换。
  • 除了使用 tzdata time zone database changes that may affect your time zones of interest. See Oracle’s provided tool for tzdata 更新使 JVM 保持最新外,您无需执行任何操作。

java.time

让我们看看使用 java.time classes 添加六个小时的结果。

定义日期和时间部分。

LocalDate ld = LocalDate.of ( 2015, Month.OCTOBER, 24 );  // 24th Oct 2015 at 10:00am per the Question.
LocalTime lt = LocalTime.of ( 10, 0 );

定义时区,ZoneId对象,Europe/London

ZoneId z = ZoneId.of ( "Europe/London" );

合并创建一个 ZonedDateTime 对象。

ZonedDateTime zdtStart = ZonedDateTime.of ( ld, lt, z );

ZonedDateTime 中提取一个 InstantInstant class represents a moment on the timeline in UTC with a resolution of nanoseconds(最多九 (9) 位小数)。

Instant instantStart = zdtStart.toInstant ( );

定义我们的时间跨度,六个小时,作为Duration。 java.time classes 可以通过添加 Duration 对象来执行日期时间数学运算。

一个Duration是独立于时间轴的,实际存​​储了秒数和纳秒数。所以在这个 class 中没有关于“六小时”和时钟和 DST 等的智能。当我们要求 Duration 六个小时时,class 立即计算出(6 小时 * 每小时 60 分钟 * 每分钟 60 秒)= 总共 21,600 秒。

Duration sixHours = Duration.ofHours ( 6 );  // 21,600 seconds = ( 6 hours * 60 minutes per hour * 60 seconds per minute ).

循环十次。首先循环添加 DurationZonedDateTime, and convert the result to an Instant.

// Increment the `ZonedDateTime`.
ZonedDateTime zdt = zdtStart;
for ( int i = 1 ; i <= 10 ; i++ ) {
    System.out.println ( ">zdt.toString() " + zdt + " | zdt.toInstant().toString(): " + zdt.toInstant ( ) + "\n");
    // Set up next loop.
    zdt = zdt.plus ( sixHours );
}

当运行。请注意伦敦时间的时间跳跃。这是 Daylight Saving Time (DST) 切换,即秋季的“回退”时间,当时英格兰从 +01:00 的 UTC 偏移量切换回标准时间到 +00:00 的祖鲁偏移量,在凌晨 2 点,时钟跳回以重复凌晨 1 点。因此,我们原本期望 22:00 加上六个小时会导致凌晨 4 点,但我们看到的却是凌晨 3 点。您可以在 Instant 值中看到确实过去了六个小时。诀窍是伦敦人在那个时候将他们的时钟拨回一个小时。

查看 DST 转换历史 Europe/London

zdt.toString() 2015-10-24T10:00+01:00[Europe/London] | zdt.toInstant().toString(): 2015-10-24T09:00:00Z

zdt.toString() 2015-10-24T16:00+01:00[Europe/London] | zdt.toInstant().toString(): 2015-10-24T15:00:00Z

zdt.toString() 2015-10-24T22:00+01:00[Europe/London] | zdt.toInstant().toString(): 2015-10-24T21:00:00Z

zdt.toString() 2015-10-25T03:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T03:00:00Z

zdt.toString() 2015-10-25T09:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T09:00:00Z

zdt.toString() 2015-10-25T15:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T15:00:00Z

zdt.toString() 2015-10-25T21:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-25T21:00:00Z

zdt.toString() 2015-10-26T03:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-26T03:00:00Z

zdt.toString() 2015-10-26T09:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-26T09:00:00Z

zdt.toString() 2015-10-26T15:00Z[Europe/London] | zdt.toInstant().toString(): 2015-10-26T15:00:00Z

为了好玩,我们交换,将六个小时连续添加到 Instant,并将结果转换为伦敦时间。

// Increment the `Instant`.
Instant instant = instantStart;
for ( int i = 1 ; i <= 10 ; i++ ) {
    System.out.println ( ">instant.toString() " + instant + " | instant.atZone(z).toString(): " + instant.atZone ( z ) + "\n");
    // Set up next loop.
    instant = instant.plus ( sixHours );
}

当 运行 时,我们看到相同的值输出。

instant.toString() 2015-10-24T09:00:00Z | instant.atZone(z).toString(): 2015-10-24T10:00+01:00[Europe/London]

instant.toString() 2015-10-24T15:00:00Z | instant.atZone(z).toString(): 2015-10-24T16:00+01:00[Europe/London]

instant.toString() 2015-10-24T21:00:00Z | instant.atZone(z).toString(): 2015-10-24T22:00+01:00[Europe/London]

instant.toString() 2015-10-25T03:00:00Z | instant.atZone(z).toString(): 2015-10-25T03:00Z[Europe/London]

instant.toString() 2015-10-25T09:00:00Z | instant.atZone(z).toString(): 2015-10-25T09:00Z[Europe/London]

instant.toString() 2015-10-25T15:00:00Z | instant.atZone(z).toString(): 2015-10-25T15:00Z[Europe/London]

instant.toString() 2015-10-25T21:00:00Z | instant.atZone(z).toString(): 2015-10-25T21:00Z[Europe/London]

instant.toString() 2015-10-26T03:00:00Z | instant.atZone(z).toString(): 2015-10-26T03:00Z[Europe/London]

instant.toString() 2015-10-26T09:00:00Z | instant.atZone(z).toString(): 2015-10-26T09:00Z[Europe/London]

instant.toString() 2015-10-26T15:00:00Z | instant.atZone(z).toString(): 2015-10-26T15:00Z[Europe/London]

看到这个 code run live at IdeOne.com


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

从哪里获得java.time classes?

  • Java SE 8 and SE 9 及更高版本
    • 内置。
    • 标准 Java API 的一部分,带有捆绑实施。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6 and SE 7
  • Android
    • ThreeTenABP 项目专门为 Android 改编 ThreeTen-Backport(如上所述)。
    • 参见

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.