避免使用旧的日期时间类
您正在使用旧的日期时间类,这些类已被证明设计不佳、令人困惑且麻烦。避开他们。
java.time
使用 Java 8 及更高版本中内置的 java.time 框架。见Tutorial。对于 Java 6 和 7,请使用 ThreeTen-Backport 项目。对于 Android,该反向端口的适配,ThreeTenABP。
这里有一些示例代码可以帮助您进行操作。我只是即兴创作的,所以它可能不够健壮。此代码似乎确实适用于您的一个示例用法:
(星期五 18:00)+ 48 =(星期二 18:00)(星期六和星期日被忽略)
我稍微概括了这段代码。而不是几个小时,它需要Duration 来表示任何时间跨度(内部表示为总秒数加上以纳秒为单位的秒的分数)。您会注意到 toString 方法的输出使用标准 ISO 8601 表示法,例如 PT48H 用于您的示例 48 小时。
假设您想要时间线上的真实时刻,我们需要使用时区来解释夏令时 (DST) 等异常情况。为此,我们需要从 ZonedDateTime 开始,它将 UTC 时间线上的时刻 (Instant) 与时区 (ZoneId) 结合起来。
我们还传递了一个持续时间(例如 48 小时)和一组星期几值(例如周六和周日)。跳过此方法以在下面讨论这些类型。
这种方法的策略是利用我们的持续时间(例如 48 小时),一次一天地减少它,直到第二天开始为止。如果第二天恰好是被禁止的星期几,我们滑到之后的第二天,并继续滑动直到我们到达允许的(非禁止的)星期几。我们继续蚕食剩余的时间,直到它达到零。阅读代码中的 cmets 进行更多讨论。
在计算第二天的开始时间时,不要假设一天中的时间是00:00:00.0。由于夏令时 (DST) 以及某些时区的其他异常情况,这一天可能会在另一个时间开始。我们调用atStartOfDay 让 java.time 确定一天中的第一个时刻。
public ZonedDateTime addDurationSkippingDaysOfWeek ( ZonedDateTime zdt , Duration duration , EnumSet<DayOfWeek> daysOfWeek ) {
// Purpose: Start at zdt, add duration but skip over entire dates where day-of-week is contained in EnumSet of prohibited days-of-week. For example, skip over United States weekend of Saturday-Sunday.
// Verify inputs.
if ( ( null == zdt ) || ( null == zdt ) || ( null == zdt ) ) {
throw new IllegalArgumentException ( "Passed null argument. Message # bf186439-c4b2-423a-b5c9-76edebd87cf0." );
}
if ( daysOfWeek.size () == DayOfWeek.values ().length ) { // We must receive 6 or less days. If passed all 7 days of the week, no days left to use for calculation.
throw new IllegalArgumentException ( "The EnumSet argument specified all days of the week. Count: " + daysOfWeek.size () + ". So, impossible to calculate if we skip over all days. Message # 103a3088-5600-4d4e-a1e0-54410afa14f8." );
}
// Move through time, day-to-day, allocating remaining duration.
ZoneId zoneId = zdt.getZone (); // Passed as argument in code below.
ZonedDateTime moment = zdt; // This var is reassigned in loop below to fresh value, later and later, as we allocate the Duration to determine our target date-time.
Duration toAllocate = duration; // Loop below chips away at this Duration until none left.
while ( ! toAllocate.isZero () ) { // Loop while some duration remains to be allocated.
if ( toAllocate.isNegative () ) { // Bad - Going negative should be impossible. Means our logic is flawed.
throw new RuntimeException ( "The duration to allocate ran into a negative amount. Should not be possible. Message # 15a4267d-c16a-417e-a815-3c8f87af0232." );
}
ZonedDateTime nextDayStart = moment.toLocalDate ().plusDays ( 1 ).atStartOfDay ( zoneId );
Duration untilTomorrow = Duration.between ( moment , nextDayStart );
// ZonedDateTime oldMoment = moment; // Debugging.
Duration allocation = null;
if ( untilTomorrow.compareTo ( toAllocate ) >= 0 ) { // If getting to tomorrow exceeds our remaining duration to allocate, we are done.
// Done -- we can allocate the last of the duration. Remaining to allocate is logically zero after this step.
moment = moment.plus ( toAllocate );
allocation = toAllocate; // Allocating all of our remaining duration.
// Do not exit here; do not call "return". Code below checks to see if the day-of-week of this date is prohibited.
} else { // Else more duration to allocate, so increment to next day.
moment = nextDayStart;
allocation = untilTomorrow; // Allocating the amount of time to take us to the start of tomorrow.
}
toAllocate = toAllocate.minus ( allocation ); // Subtract the amount of time allocated to get a fresh amount remaining to be
// Check to see if the moment has a date which happens to be a prohibited day-of-week.
while ( daysOfWeek.contains ( moment.getDayOfWeek () ) ) { // If 'moment' is a date which is a day-of-week on oun prohibited list, move on to the next date.
moment = moment.toLocalDate ().plusDays ( 1 ).atStartOfDay ( zoneId ); // Move to start of the next day after. Continue loop to check this next day.
}
}
return moment;
}
要调用该代码,我们将使用您的示例数据值。作为获取原始数据输入的一种方式,我们将字符串解析为LocalDateTime。
String input = "2016-01-01T18:00:00";
LocalDateTime ldt = LocalDateTime.parse ( input );
LocalDateTime 对象确实不代表时间轴上的实际时刻。因此,我们应用时区来获取实际时刻,ZonedDateTime。
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime zdt = ldt.atZone ( zoneId );
Duration duration = Duration.ofHours ( 48 );
我还从“the weekend”推广到任何一组星期几值,而不是硬编码周六和周日。 java.time 类包括一个方便的enum、DayOfWeek——比在我们的代码中使用字符串或数字要好得多。
Java 中的枚举功能比大多数语言中的简单数字掩码为常量更加灵活和有用。它的一个特点是一个特殊的Set 实现EnumSet,当你想要收集由你的枚举定义的可能项目的子集时。对于我们这里,我们想收集一对物品,周六物品和周日物品。
EnumSet<DayOfWeek> daysOfWeek = EnumSet.of ( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
也许工作日是一周四天,例如周一至周二加上周四至周五,然后使用EnumSet.of ( DayOfWeek.WEDNESDAY , DayOfWeek.SATURDAY , DayOfWeek.SUNDAY )。
现在我们准备调用我们的计算方法。传递三个参数:
- 首发
ZonedDateTime
-
Duration 添加到开始日期时间
-
EnumSet 的 DayOfWeek 项。
我们取回我们计算出来的未来日期时间。
ZonedDateTime zdtLater = this.addDurationSkippingDaysOfWeek ( zdt , duration , daysOfWeek );
转储到控制台。
System.out.println ( "zdt: " + zdt + " plus duration: " + duration + " while skipping daysOfWeek: " + daysOfWeek + " is zdtLater: " + zdtLater );
zdt: 2016-01-01T18:00-05:00[America/Montreal] 加上持续时间: PT48H 同时跳过 daysOfWeek: [SATURDAY, SUNDAY] is zdtLater: 2016-01-05T18:00-05:00[America /蒙特利尔]