【问题标题】:Daylight Savings Time (CST + CDT) - adding minutes issue夏令时 (CST + CDT) - 添加分钟问题
【发布时间】:2012-02-08 06:57:30
【问题描述】:

我正在更新一个能够在未来重复约会日期的日程安排应用程序。

通过查找原始约会开始时间和结束时间之间的分钟数来计算约会未来约会时间。因此,例如,约会时长为 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 分钟。

寻找输入。

【问题讨论】:

  • 我很困惑,你的意思是约会应该是相同的持续时间,还是应该同时结束......如果目标是有一个 10 小时的约会,你有在您的示例中的这两种情况下......约会总是在同一时间结束和开始的目标,无论开始或结束在哪个时区?
  • 让我们看看上面的预订时间......你可以看到他们在 3 月 8 日/9 日预订时间是 20:00-6:00,而在 DST 切换日预订时间是 20:00 -7:00 多一个小时。它在正确的时间开始,但在更晚的时间结束。这是因为这一天实际上比所有日子都短一个小时。但是,我希望预订时间在 6:00 结束。所以当我在这一天时,我应该减少几分钟来实现同样的事情。嗯嗯,其实现在想起来,也许应该晚点结束,因为时间应该是一样的。也许这正在按我需要的方式工作。我稍后会更新。
  • 你是对的。回答问题,我会接受。谢谢!我把头埋在代码中,以至于我实际上并没有想到现实世界。而且,是的,您当然希望相同的约会持续时间而不是相同的结束时间!

标签: java


【解决方案1】:

似乎目标是进行相同持续时间的约会,无论约会是在白天还是标准时间进行,或者恰好跨越时间变化。鉴于此,那么您的代码工作得很好,输出反映了它使用更改的偏移量进行打印的事实:在 DST 生效前一天晚上 20:00:00 CST 开始的 10 小时约会应该结束于第二天 07:00:00 CDT,因为时区将“向前”一个小时。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我正在更新一个企业应用程序,不幸的是没有切换的选项,我一定会考虑它以备将来使用。感谢您的意见!
    【解决方案3】:

    避免使用旧的日期时间类

    旧的旧日期时间类设计不良、混乱且麻烦。避开他们。现在被 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 点与印度加尔各答的晚上 8 点完全不同,并且又与法国巴黎的晚上 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 点,或者 DST 取消时的凌晨 3 点。确定一天中的特定时间(凌晨 4 点、凌晨 3 点或凌晨 5 点)是一个结果,即一个输出。约会都是 8 小时长,都具有相同的持续时间,无论在每一端看到的挂钟时间如何。换句话说,不要专注于一天中的结束时间。

    强力提示:停止在任何特定时区思考。将 UTC 视为“一个真实的时间”。我们仅应用时区来向用户报告特定区域的wall-clock time。应用时区有点像本地化——我们需要它来向用户展示,而不是跟踪核心数据。

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

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

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

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

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

    关于java.time

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2017-09-14
      • 2017-03-14
      • 1970-01-01
      • 2013-11-15
      • 1970-01-01
      • 2015-06-07
      • 2011-11-20
      • 2011-11-20
      • 2013-02-19
      相关资源
      最近更新 更多