夏令时(CST + CDT) - 添加分钟问题

时间:2012-01-11 22:09:18

标签: java

我正在更新一个能够在将来重复约会日期的计划应用程序。

通过查找原始约会开始时间和结束时间之间的分钟数来计算约会未来约会时间。因此,对于该实例,约会是120分钟长。在下面的代码中,预订时间是可以进行预约的时间段。因此,书籍时间和其中的约会正在被复制。这是约会复制的一次迭代。

Calendar beginCalendar = Calendar.getInstance();
beginCalendar.setTime(newBookTime.getStartDate());
beginCalendar.add(Calendar.MINUTE, bookTimeDiffMinutes);

newAppointment.setStartDate(beginCalendar.getTime());
Calendar endCalendar = Calendar.getInstance();
endCalendar.setTime(newAppointment.getStartDate());
endCalendar.add(Calendar.MINUTE, appointmentDiffMinutes);
newAppointment.setStopDate(endCalendar.getTime());

问题是在CST变成CDT的夏令时......如果预约的开始时间是在CST日的晚上11点,然后在CDT日的结束时间说凌晨2点......我的约会结束一个小时或更早(取决于他们的时钟是回转还是向前)。这是因为当我为约会添加X分钟时...那天真的说少了1个小时,因为我们跳过了一个小时。

所以,当我打印我在DST日可以看到的日期,时区从CST变为CDT:

预订时间应为20:00-6:00

[STDOUT] The book time start date:Sat Mar 07 20:00:00 CST 2015
[STDOUT] The book time stop date:Sun Mar 08 07:00:00 CDT 2015

而在非夏令时更改日,我们看到:

[STDOUT] The book time start date:Sun Mar 08 20:00:00 CDT 2015
[STDOUT] The book time stop date:Mon Mar 09 06:00:00 CDT 2015

我想知道如何补偿这一点并确保我的预约和预订时间是从时区从CST变为CDT的那一天的正确长度。如果我有一种聪明的方法来检测变化,我可以增加或减少60分钟。

寻找输入。

3 个答案:

答案 0 :(得分:2)

Java内置的日期/时间功能非常可怕。切换到很多更好的库,比如JodaTime,你不必担心这样的事情。此外,JodaTime使计算时间更多更简单。 几乎使与时间相关的编程变得“有趣”。

答案 1 :(得分:2)

似乎目标是预约相同的持续时间,无论约会是在白天或标准时间内发生,还是恰好跨越时间变化。考虑到这一点,那么你的代码工作正常,输出反映了它使用更改的偏移打印的事实:在DST生效前一天晚上20:00:00 CST开始的10小时约会应该第二天在CDT 07:00:00结束,因为时区将“向前推进”一小时。

答案 2 :(得分:0)

避免遗留日期时间类

旧的旧日期时间类设计糟糕,令人困惑且麻烦。避免他们。现在取代了java.time类。

以UTC格式工作

您的核心问题是尝试将日期时间值存储在分区日期时间中。一般来说,最佳做法是将日期时间值存储在UTC而不是任何时区。

约会的长度,即尚未附加到时间轴的时间跨度,应作为Duration对象处理。然后,您可以通过调用plusminus方法进行日期时间数学运算,将其应用于各种时刻。

Duration duration = Duration.ofHours( 8 );

每个预期约会的开始时间可以作为LocalTime对象处理,该对象表示没有日期且没有时区的时间。

LocalTime startTime = LocalTime.of( 20 , 0 ); // 20:00:00 (8 PM).

您要为其创建约会的每个日期都可以表示为LocalDate,这是一个仅限日期值的类,没有时间和没有时区。

LocalDate startDate = LocalDate.of( 2015 , Month.MARCH , 7 ); // 2015-03-07

最后,要创建约会,我们需要一个时区。上面的部分,开始时间,日期和持续时间,都没有时区,所以没有任何意义。例如,新西兰奥克兰的晚上8点与加尔各答IN的晚上8点完全不同,而巴黎FR的晚上8点则不同。

你显然打算将这些约会用于北美中部的时区。永远不要使用时区的3-4个字母缩写,因为这些不是真正的时区,不是标准化的,甚至不是唯一的(!)。以continent/region的格式使用proper time zone names。在您的情况下可能America/Chicago

ZoneId z = ZoneId.of( "America/Chicago" );

这些是你应该存储的核心数据。请注意,我们需要存储结束时间。

  • LocalDate - 约会开始时
  • LocalTime - 约会开始时
  • ZoneID - 日期和时间有意义的时区
  • Duration - 约会应该多长时间

时区规则经常变化,经常相当。政治家们喜欢重新定义夏令时(DST),或者重新定义他们的时区,并且经常在没有预先警告的情况下这样做。因此,确定未来的约会是棘手的事情。最好延迟确定约会,直到你真的需要。例如,请等到生成要呈现给用户的日历。

当您准备好通过某个区域的挂钟时间镜头确定约会的开始和停止时间时,将上面的这些部分组合起来生成ZonedDateTime对象。

ZonedDateTime zdtStart = ZonedDateTime.of( startDate , startTime , z );  // 8 PM
ZonedDateTime zdtStop = zdtStart.plus( duration );  // 4 AM, or 3 AM, or 5 AM depending on DST.

解决方案的关键在于结束时间自然消失了。 ZonedDateTime类处理的问题是确定时间应该是大多数日子的凌晨4点,或夏令时(DST)参与时的凌晨5点,或者夏令时脱离时的凌晨3点。确定一天中的特定时间,凌晨4点,凌晨3点或上午5点是结果,即输出。约会都是8个小时,所有都有相同的持续时间,无论在每一端看到的挂钟时间。换句话说,不要专注于结束时间。

电源提示:不要在任何特定时区思考。将UTC视为“同一时间”。我们仅应用时区来向用户报告特定区域的wall-clock time。应用时区有点像本地化 - 我们需要它来呈现给用户,而不是跟踪核心数据。

如果要将此对跟踪为时间间隔,请将ThreeTen-Extra库添加到项目中以使用其Interval类。间隔处理一对Instant个对象,而不是上面看到的ZonedDateTime个对象。

Instant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds。这意味着最多九(9)位小数。可以认为InstantZonedDateTime剥离了指定的时区。换句话说,从概念上讲,您可以这样考虑:ZonedDateTime =(Instant + ZoneId)。我们可以从每对Instant对象中提取ZonedDateTime

Interval interval = Interval.of( zdtStart.toInstant() , zdtStop.toInstant() );

请注意,日期时间工作中的常见做法是使用半开放方法定义一段时间,例如此Interval,其中开头是包含,而结尾是独家。因此,在您的示例中,约会从晚上8点开始运行,但包括,第二天凌晨4点。这避免了以小数秒确定最后包含时刻的问题。

您可能会发现Interval类方法非常方便,例如比较overlapsabutsenclosescontains

关于java.time

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

现在位于Joda-Timemaintenance mode项目建议迁移到java.time。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。

大部分java.time功能都被反向移植到Java 6& ThreeTen-Backport中的7,并进一步适应Android中的ThreeTenABP(见How to use…)。

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore