Java Joda时间当地时间24:00一天结束
我们正在创建一个日程安排应用程序,我们需要表示某个人白天的可用日程安排,而不管他们在哪个时区。以Joda Time的Interval为线索,它表示两个实例之间的绝对时间间隔(包括开始和结束),我们创建了一个LocalInterval。LocalInterval由两个LocalTimes(包括开始和结束)组成,我们甚至制作了一个方便的类来在Hibernate中持久化它 例如,如果某人在下午1:00到5:00有空,我们将创建:Java Joda时间当地时间24:00一天结束,java,jodatime,intervals,localtime,Java,Jodatime,Intervals,Localtime,我们正在创建一个日程安排应用程序,我们需要表示某个人白天的可用日程安排,而不管他们在哪个时区。以Joda Time的Interval为线索,它表示两个实例之间的绝对时间间隔(包括开始和结束),我们创建了一个LocalInterval。LocalInterval由两个LocalTimes(包括开始和结束)组成,我们甚至制作了一个方便的类来在Hibernate中持久化它 例如,如果某人在下午1:00到5:00有空,我们将创建: new LocalInterval(new LocalTime(13,
new LocalInterval(new LocalTime(13, 0), new LocalTime(17, 0));
到目前为止还不错,直到有人想在某一天从晚上11点到午夜有空。由于间隔的末尾是独占的,因此应很容易表示为:
new LocalInterval(new LocalTime(23, 0), new LocalTime(24, 0));
啊!不行。这会引发异常,因为LocalTime不能容纳任何大于23小时的时间
这对我来说似乎是一个设计缺陷--- Joda没有考虑到有人想要一个代表非包容性端点的本地时间。 这确实令人沮丧,因为它在我们创建的原本非常优雅的模型上打了一个洞
除了用叉子叉乔达和拿24小时的支票,我还有什么选择?(不,我不喜欢使用虚拟值(比如23:59:59)来表示24:00。)更新:对于那些一直说不存在24:00的人,这里引用ISO 8601-2004 4.2.3注释2,3:“一个日历日[24:00]的结束与下一个日历日开始时的[00:00]重合……”和“如果[hh]的值为[24],则表示仅首选表示时间间隔的结束……”这不是设计缺陷
LocalDate
不处理(24,0)
,因为没有24:00这样的事情
另外,如果要表示一个介于晚上9点到凌晨3点之间的时间间隔,会发生什么情况
这有什么问题吗
new LocalInterval(new LocalTime(23, 0), new LocalTime(0, 0));
您只需处理结束时间可能“早于”开始时间的可能性,并在必要时添加一天,只希望没有人希望表示超过24小时的间隔
或者,将间隔表示为
LocalDate
和Duration
或Period
的组合。这就消除了“超过24小时”的问题。23:59:59之后的第二天是00:00:00。那么在下一个日历日使用0,0
的LocalTime
虽然你的开始时间和结束时间都包含在内,但23:59:59确实是你想要的。这包括第23小时第59分钟的第59秒,精确到00:00:00结束
没有24:00(当使用
LocalTime
时) 24:00是一个艰难的时刻。虽然我们人类可以理解这意味着什么,但在我看来,编写一个API来表示这一点而不对其他一切产生负面影响几乎是不可能的
无效的值24在Joda Time中被深度编码-试图删除它会在很多地方产生负面影响。我不建议你这么做
对于您的问题,本地间隔应包括(LocalTime,LocalTime,Days)
或(LocalTime,Period)
。后者稍微灵活一些。这是正确支持从23:00到03:00的间隔所必需的。我发现的(LocalTime,LocalTime,Days)
的建议是可以接受的
考虑到2011年3月13日以及您周日00:00-12:00的可用性,由于DST,您将拥有(00:00,12:00,0)
,而事实上,由于DST的原因,您的可用时间为11小时
从15:00到24:00的可用性,然后您可以将代码编码为(15:00,00:00,1)
,该代码将扩展到2011-03-13T15:00-2011-03-14T00:00,在2011-03-13T24:00结束。这意味着您将在下一个日历日使用00:00的本地时间,就像已经提议的那样
当然,直接使用24:00Localtime和ISO8601标准是很好的,但是如果不在JodaTime内部进行大量更改,这似乎是不可能的,因此这种方法似乎不是那么糟糕
最后但并非最不重要的一点是,你甚至可以用类似于
(16:00,05:00,1)
…的东西来延长一天的时间间隔。你的问题可以被定义为在一个领域中定义一个时间间隔。最小值为00:00,最大值为24:00(不包括在内)
假设您的间隔定义为(下、上)。如果需要下<上,则可以表示(21:00,24:00),但仍然无法表示(21:00,02:00),即跨越最小/最大边界的间隔
我不知道您的日程安排应用程序是否会涉及环绕时间间隔,但如果您要转到(21:00,24:00)而不涉及天数,我看不出有什么会阻止您要求(21:00,02:00)而不涉及天数(从而导致环绕维度)
如果您的设计适合于环绕式实现,那么间隔运算符就非常简单
例如(在伪代码中):
x英寸(下,上)吗?:=
如果(lower我们最终采用的解决方案是使用00:00作为24:00的替代,整个类和应用程序的其余部分都使用逻辑来解释这个局部值。这是一个真正的难题,但这是我能想到的最不具侵扰性和最优雅的方法
首先,LocalTimeInterval类保留一个内部标志,指示间隔端点是否为一天的午夜结束(24:00)。只有当结束时间为00:00(等于LocalTime.midnight)时,该标志才为真
默认情况下,构造函数将00:00视为一天的开始,但有一个替代构造函数用于手动创建全天的间隔:
public LocalTimeInterval(final LocalTime start, final LocalTime end, final boolean considerMidnightEndOfDay)
{
...
this.isEndOfDay = considerMidnightEndOfDay && LocalTime.MIDNIGHT.equals(end);
}
这个构造函数不只是有一个开始时间和一个“is end of day”标志是有原因的:当与一个带有时间下拉列表的UI一起使用时,我们不知道用户是否会选择
/**
* @return Whether the end of the day is {@link LocalTime#MIDNIGHT} and this should be considered midnight of the
* following day.
*/
public boolean isEndOfDay()
{
return isEndOfDay;
}
public LocalTimeInterval(final LocalTime start, final LocalTime end, final boolean considerMidnightEndOfDay)
{
...
this.isEndOfDay = considerMidnightEndOfDay && LocalTime.MIDNIGHT.equals(end);
}
public boolean overlaps(final LocalTimeInterval localInterval)
{
if (localInterval.isEndOfDay())
{
if (isEndOfDay())
{
return true;
}
return getEnd().isAfter(localInterval.getStart());
}
if (isEndOfDay())
{
return localInterval.getEnd().isAfter(getStart());
}
return localInterval.getEnd().isAfter(getStart()) && localInterval.getStart().isBefore(getEnd());
}
public Interval toInterval(final ReadableInstant baseInstant)
{
final DateTime start = getStart().toDateTime(baseInstant);
DateTime end = getEnd().toDateTime(baseInstant);
if (isEndOfDay())
{
end = end.plusDays(1);
}
return new Interval(start, end);
}
...
final Time startTime = (Time) Hibernate.TIME.nullSafeGet(resultSet, names[0]);
final Time endTime = (Time) Hibernate.TIME.nullSafeGet(resultSet, names[1]);
...
final LocalTime start = new LocalTime(startTime, DateTimeZone.UTC);
if (endTime.equals(TIME_2400))
{
return new LocalTimeInterval(start, LocalTime.MIDNIGHT, true);
}
return new LocalTimeInterval(start, new LocalTime(endTime, DateTimeZone.UTC));
final Time startTime = asTime(localTimeInterval.getStart());
final Time endTime = localTimeInterval.isEndOfDay() ? TIME_2400 : asTime(localTimeInterval.getEnd());
Hibernate.TIME.nullSafeSet(statement, startTime, index);
Hibernate.TIME.nullSafeSet(statement, endTime, index + 1);
/**
* Description: Immutable time interval<br>
* The start instant is inclusive but the end instant is exclusive.
* The end is always greater than or equal to the start.
* The interval is also restricted to just one chronology and time zone.
* Start can be null (infinite).
* End can be null and will stay null to let the interval last until end-of-day.
* It supports intervals spanning multiple days.
*/
public class TimeInterval {
public static final ReadableInstant INSTANT = null; // null means today
// public static final ReadableInstant INSTANT = new Instant(0); // this means 1st jan 1970
private final DateTime start;
private final DateTime end;
public TimeInterval() {
this((LocalTime) null, null);
}
/**
* @param from - null or a time (null = left unbounded == LocalTime.MIDNIGHT)
* @param to - null or a time (null = right unbounded)
* @throws IllegalArgumentException if invalid (to is before from)
*/
public TimeInterval(LocalTime from, LocalTime to) throws IllegalArgumentException {
this(from == null ? null : from.toDateTime(INSTANT),
to == null ? null : to.toDateTime(INSTANT));
}
/**
* create interval spanning multiple days possibly.
*
* @param start - start distinct time
* @param end - end distinct time
* @throws IllegalArgumentException - if start > end. start must be <= end
*/
public TimeInterval(DateTime start, DateTime end) throws IllegalArgumentException {
this.start = start;
this.end = end;
if (start != null && end != null && start.isAfter(end))
throw new IllegalArgumentException("start must be less or equal to end");
}
public DateTime getStart() {
return start;
}
public DateTime getEnd() {
return end;
}
public boolean isEndUndefined() {
return end == null;
}
public boolean isStartUndefined() {
return start == null;
}
public boolean isUndefined() {
return isEndUndefined() && isStartUndefined();
}
public boolean overlaps(TimeInterval other) {
return (start == null || (other.end == null || start.isBefore(other.end))) &&
(end == null || (other.start == null || other.start.isBefore(end)));
}
public boolean contains(TimeInterval other) {
return ((start != null && other.start != null && !start.isAfter(other.start)) || (start == null)) &&
((end != null && other.end != null && !other.end.isAfter(end)) || (end == null));
}
public boolean contains(LocalTime other) {
return contains(other == null ? null : other.toDateTime(INSTANT));
}
public boolean containsEnd(DateTime other) {
if (other == null) {
return end == null;
} else {
return (start == null || !other.isBefore(start)) &&
(end == null || !other.isAfter(end));
}
}
public boolean contains(DateTime other) {
if (other == null) {
return start == null;
} else {
return (start == null || !other.isBefore(start)) &&
(end == null || other.isBefore(end));
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("TimeInterval");
sb.append("{start=").append(start);
sb.append(", end=").append(end);
sb.append('}');
return sb.toString();
}
}
@Test()
public void testJoda() throws DGConstraintViolatedException {
DateTimeFormatter simpleTimeFormatter = DateTimeFormat.forPattern("HHmm");
LocalTime t1 = LocalTime.parse("0000", simpleTimeFormatter);
LocalTime t2 = LocalTime.MIDNIGHT;
Assert.assertTrue(t1.isBefore(t2));
}