【问题标题】:How to add time to DateTime when timezone changes?时区更改时如何将时间添加到 DateTime?
【发布时间】:2015-08-04 11:21:53
【问题描述】:

我有以下代码为 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 次以添加到 arraylist。但是,当添加 6 小时时,说夏令时发生了变化。由于时区更改而添加/删除额外小时时,如何生成正确的时间?目前它不会使用此方法删除/添加额外的小时。

例如,如果我要在 2015 年 10 月 24 日上午 10:00 开始运行代码,我预计会生成以下时间。请注意,时区会在 2015 年 10 月 25 日凌晨 02:00 发生变化。

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

【问题讨论】:

  • “包容”是什么意思?如果您能展示一个简短但完整的程序来演示问题,这将非常有帮助 - 包括实际输出和预期输出。
  • 在帖子中添加了更多信息。
  • 你还没有添加一个简短但完整的例子,这是我真正希望看到的......
  • 我认为这是 Joda Time,顺便说一句?你实际上并没有说任何地方......
  • 您为什么希望它从 22:00:00 到 05:00:00?我希望它会以 other 的方式运行,到 03:00:00,这是我实际观察到的。从英国夏令时 22:00:00 到格林威治标准时间 05:00:00 的经过时间为 8 小时。

标签: java datetime time timezone dst


【解决方案1】:

通常情况下,我会将正确答案留给 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

【讨论】:

  • 哦,天哪facepalm 我只是认为 OP 是对的,没有看日期。
  • 更正... 关于声明:“标记为“02”的小时发生了两次”,我相信实际上是01 小时重复。据我所知,在美国、加拿大和欧盟,在凌晨 1 点 59 分或凌晨 2 点钟,时钟被调回 01:00 以重复凌晨 1 点。
  • @BasilBourque 已更正错字。我被误导了,因为在中欧,时钟会在当地时间一小时后的凌晨 3 点跳回(在德国:时钟 2 正在重复)。
【解决方案2】:

别着急,用java.time

java.time

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

定义日期和时间部分。

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

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

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

组合创建一个ZonedDateTime 对象。

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

ZonedDateTime 中提取InstantInstant 类代表UTC 时间线上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。

Instant instantStart = zdtStart.toInstant ( );

将我们的时间跨度(六个小时)定义为Duration。 java.time 类可以通过添加Duration 对象来执行日期时间数学。

Duration 未附加到时间线,实际上存储了秒数和纳秒数。因此,这门课中没有关于“六小时”以及时钟和夏令时等方面的聪明才智。当我们要求 Duration 为 6 小时时,该课程立即计算出 (6 小时 * 60 分钟/小时 * 60 秒/分钟) = 总共 21,600 秒。

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

循环十次。第一个循环,将Duration 添加到ZonedDateTime,并将结果转换为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 加上 6 小时会导致凌晨 4 点,我们会看到凌晨 3 点。您可以在 Instant 值中看到六个小时确实过去了。诀窍是伦敦人大约在那时将时钟拨回一个小时。

请参阅history of DST cutovers 以获取Europe/London

zdt.toString() 2015-10-24T10:00+01:00[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-24T09:00:00Z

zdt.toString() 2015-10-24T16:00+01:00[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-24T15:00:00Z

zdt.toString() 2015-10-24T22:00+01:00[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-24T21:00:00Z

zdt.toString() 2015-10-25T03:00Z[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-25T03:00:00Z

zdt.toString() 2015-10-25T09:00Z[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-25T09:00:00Z

zdt.toString() 2015-10-25T15:00Z[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-25T15:00:00Z

zdt.toString() 2015-10-25T21:00Z[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-25T21:00:00Z

zdt.toString() 2015-10-26T03:00Z[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-26T03:00:00Z

zdt.toString() 2015-10-26T09:00Z[欧洲/伦敦] | zdt.toInstant().toString(): 2015-10-26T09:00:00Z

zdt.toString() 2015-10-26T15:00Z[欧洲/伦敦] | 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[欧洲/伦敦]

instant.toString() 2015-10-24T15:00:00Z | instant.atZone(z).toString(): 2015-10-24T16:00+01:00[欧洲/伦敦]

instant.toString() 2015-10-24T21:00:00Z | instant.atZone(z).toString(): 2015-10-24T22:00+01:00[欧洲/伦敦]

instant.toString() 2015-10-25T03:00:00Z | instant.atZone(z).toString(): 2015-10-25T03:00Z[欧洲/伦敦]

instant.toString() 2015-10-25T09:00:00Z | instant.atZone(z).toString(): 2015-10-25T09:00Z[欧洲/伦敦]

instant.toString() 2015-10-25T15:00:00Z | instant.atZone(z).toString(): 2015-10-25T15:00Z[欧洲/伦敦]

instant.toString() 2015-10-25T21:00:00Z | instant.atZone(z).toString(): 2015-10-25T21:00Z[欧洲/伦敦]

instant.toString() 2015-10-26T03:00:00Z | instant.atZone(z).toString(): 2015-10-26T03:00Z[欧洲/伦敦]

instant.toString() 2015-10-26T09:00:00Z | instant.atZone(z).toString(): 2015-10-26T09:00Z[欧洲/伦敦]

instant.toString() 2015-10-26T15:00:00Z | instant.atZone(z).toString(): 2015-10-26T15:00Z[欧洲/伦敦]

看到这个code run live at IdeOne.com


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-06
    • 1970-01-01
    • 2010-12-23
    相关资源
    最近更新 更多