Java Joda时间当地时间24: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,

我们正在创建一个日程安排应用程序,我们需要表示某个人白天的可用日程安排,而不管他们在哪个时区。以Joda Time的Interval为线索,它表示两个实例之间的绝对时间间隔(包括开始和结束),我们创建了一个LocalInterval。LocalInterval由两个LocalTimes(包括开始和结束)组成,我们甚至制作了一个方便的类来在Hibernate中持久化它

例如,如果某人在下午1:00到5:00有空,我们将创建:

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));
}