java Joda 时间 LocalTime of 24:00 end-of-day
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5384921/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Joda Time LocalTime of 24:00 end-of-day
提问by Garret Wilson
We're creating a scheduling application and we need to represent someone's available schedule during the day, regardless of what time zone they are in. Taking a cue from Joda Time's Interval, which represents an interval in absolute time between two instances (start inclusive, end exclusive), we created a LocalInterval. The LocalInterval is made up of two LocalTimes (start inclusive, end exclusive), and we even made a handy class for persisting this in Hibernate.
我们正在创建一个调度应用程序,我们需要表示某个人在白天的可用时间表,而不管他们在哪个时区。从 Joda Time 的 Interval 中获得提示,它表示两个实例之间的绝对时间间隔(包括开始,结束独占),我们创建了一个LocalInterval。LocalInterval 由两个 LocalTimes 组成(开始包含,结束不包含),我们甚至制作了一个方便的类来在 Hibernate 中持久化它。
For example, if someone is available from 1:00pm to 5:00pm, we would create:
例如,如果有人在下午 1:00 到下午 5:00 有空,我们将创建:
new LocalInterval(new LocalTime(13, 0), new LocalTime(17, 0));
So far so good---until someone wants to be available from 11:00pm until midnight on some day. Since the end of an interval is exclusive, this shouldbe easily represented as such:
到目前为止一切顺利——直到有人想在某一天晚上 11:00 到午夜有空。由于间隔的结尾是排他的,因此应该很容易表示为:
new LocalInterval(new LocalTime(23, 0), new LocalTime(24, 0));
Ack! No go. This throws an exception, because LocalTime cannot hold any hour greater than 23.
确认!不行。这会引发异常,因为 LocalTime 不能容纳任何大于 23 的小时。
This seems like a design flaw to me---Joda didn't consider that someone may want a LocalTime that represents a non-inclusive endpoint.
这对我来说似乎是一个设计缺陷——Joda 没有考虑到有人可能想要一个代表非包含端点的 LocalTime。
This is really frustrating, as it blows a hole in what was otherwise a very elegant model that we created.
这真的令人沮丧,因为它在我们创建的一个非常优雅的模型中炸了一个洞。
What are my options---other than forking Joda and taking out the check for hour 24? (No, I don't like the option of using a dummy value---say 23:59:59---to represent 24:00.)
除了分叉 Joda 并在 24 小时内取出支票之外,我还有哪些选择?(不,我不喜欢使用虚拟值的选项---比如 23:59:59---代表 24:00。)
Update: To those who keep saying that there is no such thing as 24:00, here's a quote from ISO 8601-2004 4.2.3 Notes 2,3: "The end of one calendar day [24:00] coincides with [00:00] at the start of the next calendar day ..." and "Representations where [hh] has the value [24] are only preferred to represent the end of a time interval ...."
更新:对于那些一直说没有 24:00 这样的东西的人,这里引用了 ISO 8601-2004 4.2.3 注释 2,3:“一个日历日的结束 [24:00] 与 [00 :00] 在下一个日历日的开始......”和“[hh] 具有值 [24] 的表示仅首选表示时间间隔的结束......”
采纳答案by Garret Wilson
The solution we finally went with was to use 00:00 as a stand-in for 24:00, with logic throughout the class and the rest of the application to interpret this local value. This is a true kludge, but it's the least intrusive and most elegant thing I could come up with.
我们最终采用的解决方案是使用 00:00 作为 24:00 的替代,在整个类和应用程序的其余部分使用逻辑来解释这个本地值。这是一个真正的混杂,但它是我能想到的最少侵入性和最优雅的事情。
First, the LocalTimeInterval class keeps an internal flag of whether the interval endpoint is end-of-day midnight (24:00). This flag will only be true if the end time is 00:00 (equal to LocalTime.MIDNIGHT).
首先,LocalTimeInterval 类保留一个内部标志,指示间隔端点是否为一天结束的午夜 (24:00)。仅当结束时间为 00:00(等于 LocalTime.MIDNIGHT)时,此标志才会为真。
/**
* @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;
}
By default the constructor considers 00:00 to be beginning-of-day, but there is an alternate constructor for manually creating an interval that goes all day:
默认情况下,构造函数将 00:00 视为一天的开始,但还有一个替代构造函数用于手动创建一个全天的间隔:
public LocalTimeInterval(final LocalTime start, final LocalTime end, final boolean considerMidnightEndOfDay)
{
...
this.isEndOfDay = considerMidnightEndOfDay && LocalTime.MIDNIGHT.equals(end);
}
There is a reason why this constructor doesn't just have a start time and an "is end-of-day" flag: when used with a UI with a drop-down list of times, we don't know if the user will choose 00:00 (which is rendered as 24:00), but we know that as the drop-down list is for the end of the range, in our use case it means 24:00. (Although LocalTimeInterval allows empty intervals, we don't allow them in our application.)
这个构造函数不只是有一个开始时间和一个“is end-of-day”标志是有原因的:当与带有下拉时间列表的 UI 一起使用时,我们不知道用户是否会选择 00:00(呈现为 24:00),但我们知道下拉列表是针对范围的末尾,在我们的用例中它意味着 24:00。(虽然 LocalTimeInterval 允许空间隔,但我们不允许在我们的应用程序中使用它们。)
Overlap checking requires special logic to take care of 24:00:
重叠检查需要特殊的逻辑来处理 24:00:
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());
}
Similarly, converting to an absolute Interval requires adding another day to the result if isEndOfDay() returns true. It is important that application code never constructs an Interval manually from a LocalTimeInterval's start and end values, as the end time may indicate end-of-day:
类似地,如果 isEndOfDay() 返回 true,则转换为绝对间隔需要将另一天添加到结果中。应用程序代码永远不要从 LocalTimeInterval 的开始和结束值手动构造 Interval 很重要,因为结束时间可能表示一天的结束:
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);
}
When persisting LocalTimeInterval in the database, we were able to make the kludge totally transparent, as Hibernate and SQL have no 24:00 restriction (and indeed have no concept of LocalTime anyway). If isEndOfDay() returns true, our PersistentLocalTimeIntervalAsTime implementation stores and retrieves a true time value of 24:00:
当在数据库中持久化 LocalTimeInterval 时,我们能够使 kludge 完全透明,因为 Hibernate 和 SQL 没有 24:00 限制(并且确实没有 LocalTime 的概念)。如果 isEndOfDay() 返回 true,我们的 PersistentLocalTimeIntervalAsTime 实现会存储和检索 24:00 的真实时间值:
...
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));
and
和
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);
It's sad that we had to write a workaround in the first place; this is the best I could do.
很遗憾,我们不得不首先编写一个解决方法;这是我能做的最好的事情。
回答by aroth
Well after 23:59:59 comes 00:00:00 on the next day. So maybe use a LocalTime
of 0, 0
on the next calendar day?
23:59:59 之后是第二天的 00:00:00。那么也许在下一个日历日使用LocalTime
of 0, 0
?
Although since your start and end times are inclusive, 23:59:59 is really what you want anyways. That includes the 59th second of the 59th minute of the 23rd hour, and ends the range exactly on 00:00:00.
尽管由于您的开始时间和结束时间都包含在内,但无论如何 23:59:59 确实是您想要的。这包括第 23 小时的第 59 分钟的第 59 秒,并恰好在 00:00:00 结束该范围。
There is no such thing as 24:00 (when using LocalTime
).
没有 24:00 之类的东西(使用 时LocalTime
)。
回答by skaffman
It's not a design flaw. LocalDate
doesn't handle (24,0)
because there's no such thing as 24:00.
这不是设计缺陷。 LocalDate
不处理,(24,0)
因为没有 24:00 这样的事情。
Also, what happens when you want to represent an interval between, say 9pm and 3am?
另外,当您想表示晚上 9 点和凌晨 3 点之间的时间间隔时会发生什么?
What's wrong with this:
这有什么问题:
new LocalInterval(new LocalTime(23, 0), new LocalTime(0, 0));
You just have to handle the possibility that the end time might be "before" the start time, and add a day when necessary, and just hope that noone wants to represent an interval longer than 24 hours.
您只需要处理结束时间可能在开始时间“之前”的可能性,并在必要时添加一天,并希望没有人想要表示超过 24 小时的间隔。
Alternatively, represent the interval as a combination of a LocalDate
and a Duration
or Period
. That removes the "longer than 24 hours" problem.
或者,将区间表示为 aLocalDate
和 aDuration
或的组合Period
。这消除了“超过 24 小时”的问题。
回答by Dingfeng Quek
Your problem can be framed as defining an interval on a domain that wraps around. Your min is 00:00, and your max is 24:00 (not inclusive).
您的问题可以定义为在环绕的域上定义一个区间。您的最小值为 00:00,最大值为 24:00(不包括在内)。
Suppose your interval is defined as (lower, upper). If you require that lower < upper, you can represent (21:00, 24:00), but you are still unable to represent (21:00, 02:00), an interval that wraps across the min/max boundary.
假设您的间隔定义为(下,上)。如果您要求lower < upper,则可以表示(21:00, 24:00),但仍然无法表示(21:00, 02:00),这是一个跨越最小/最大边界的区间。
I don't know whether your scheduling application would involve wrap-around intervals, but if you are going to go to (21:00, 24:00) without involving days, I don't see what will stop you from requiring (21:00, 02:00) without involving days (thus leading to a wrap-around dimension).
我不知道您的日程安排应用程序是否会涉及环绕间隔,但是如果您打算去 (21:00, 24:00) 而不涉及几天,我看不出有什么会阻止您要求 (21 :00, 02:00) 不涉及天(从而导致环绕维度)。
If your design is amenable to a wrap-around implementation, the interval operators are quite trivial.
如果您的设计适用于环绕式实现,则区间运算符非常简单。
For example (in pseudo-code):
例如(在伪代码中):
is x in (lower, upper)? :=
if (lower <= upper) return (lower <= x && x <= upper)
else return (lower <= x || x <= upper)
In this case, I have found that writing a wrapper around Joda-Time implementing the operators is simple enough, and reduces impedance between thought/math and API. Even if it is just for the inclusion of 24:00 as 00:00.
在这种情况下,我发现围绕实现运算符的 Joda-Time 编写包装器非常简单,并且减少了思想/数学和 API 之间的阻抗。即使只是为了将 24:00 包含为 00:00。
I do agree that the exclusion of 24:00 annoyed me at the start, and it'll be nice if someone offered a solution. Luckily for me, given that my use of time intervals is dominated by wrap-around semantics, I always end up with a wrapper, which incidentally solves the 24:00 exclusion.
我同意排除 24:00 一开始让我很恼火,如果有人提供解决方案会很好。对我来说幸运的是,考虑到我对时间间隔的使用由环绕语义主导,我总是以包装器结束,这顺便解决了 24:00 排除。
回答by JodaStephen
The time 24:00 is a difficult one. While we humans can understand what is meant, coding up an API to represent that without negatively impacting everything else appears to me to be nigh on impossible.
24:00 是一个艰难的时刻。虽然我们人类可以理解是什么意思,但编写一个 API 来表示它而不会对其他一切产生负面影响在我看来几乎是不可能的。
The value 24 being invalid is deeply encoded in Joda-Time - trying to remove it would have negative implications in a lot of places. I wouldn't recommend trying to do that.
无效的值 24 在 Joda-Time 中被深度编码 - 试图删除它会在很多地方产生负面影响。我不建议尝试这样做。
For your problem, the local interval should consist of either (LocalTime, LocalTime, Days)
or (LocalTime, Period)
. The latter is slightly more flexible. This is needed to correctly support an interval from 23:00 to 03:00.
对于您的问题,本地间隔应由(LocalTime, LocalTime, Days)
或组成(LocalTime, Period)
。后者稍微灵活一些。这是正确支持从 23:00 到 03:00 的间隔所必需的。
回答by binuWADa
I find JodaStephen's proposal of (LocalTime, LocalTime, Days)
acceptable.
我觉得JodaStephen的提议(LocalTime, LocalTime, Days)
可以接受。
Considering on 13 March 2011 and your availability on Sunday from 00:00-12:00 you would have (00:00, 12:00, 0)
which were in fact 11 hours long because of DST.
考虑到 2011 年 3 月 13 日以及您在周日 00:00-12:00 的空闲(00:00, 12:00, 0)
时间,由于夏令时,您实际上会有11 个小时的空闲时间。
An availability from say 15:00-24:00 you could then code as (15:00, 00:00, 1)
which would expanded to 2011-03-13T15:00 - 2011-03-14T00:00 whereat the end would be desired 2011-03-13T24:00. That means you would use a LocalTime of 00:00 on the next calendar day like already arothproposed.
比如说 15:00-24:00 的可用性,然后您可以编码为(15:00, 00:00, 1)
将扩展到 2011-03-13T15:00 - 2011-03-14T00:00,最终需要 2011-03-13T24:00。这意味着您将在下一个日历日使用 00:00 的LocalTime,就像aroth提议的那样。
Of course it would be nice to use a 24:00 LocalTime directly and ISO 8601 conform but this seems not possible without changing a lot inside JodaTime so this approach seems the lesser evil.
当然,直接使用 24:00 LocalTime 并且符合 ISO 8601 会很好,但是如果不在 JodaTime 内部进行大量更改,这似乎是不可能的,因此这种方法似乎不那么邪恶。
And last but not least you could even extend the barrier of a single day with something like (16:00, 05:00, 1)
...
最后但并非最不重要的一点是,您甚至可以使用诸如(16:00, 05:00, 1)
......
回答by Roman
this is our implementation of TimeInterval, using null as end Date for end-of-day. It supports the overlaps() and contains() methods and is also based on joda-time. It supports intervals spanning multiple days.
这是我们对 TimeInterval 的实现,使用 null 作为一天结束的结束日期。它支持overlaps() 和contains() 方法并且也基于joda-time。它支持跨越多天的间隔。
/**
* 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();
}
}
回答by Jaime Casero
For the sake of completeness this test fails:
为了完整起见,此测试失败:
@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));
}
This means the MIDNIGHT constant is not very usefull for the problem, as someone suggested.
正如有人建议的那样,这意味着 MIDNIGHT 常量对这个问题不是很有用。
回答by Gus
This question is old, but many of these answers focus on Joda Time, and only partly address the true underlying problem:
这个问题很老了,但其中许多答案都集中在 Joda Time 上,并且仅部分解决了真正的潜在问题:
The model in the OP's code doesn't match the reality it's modeling.
OP 代码中的模型与其建模的现实不匹配。
Unfortunately, since you do appear to care about the boundary condition between days, your "otherwise elegant model" isn't a good match for the problem you are modeling. You've used a pair of time values to represent intervals. Attempting to simplify the model down to a pair of times is simplifying below the complexity of the real world problem. Day boundaries actually do exist in reality and a pair of times looses that type of information. As always, over simplification results in subsequent complexity to restore or compensate for the missing information. Real complexity can only be pushed around from one part of the code to another.
不幸的是,由于您似乎确实关心天之间的边界条件,因此您的“其他优雅模型”与您正在建模的问题并不匹配。您使用了一对时间值来表示间隔。试图将模型简化为两倍是在现实世界问题的复杂性之下进行简化。日界限实际上确实存在于现实中,并且有时会丢失这种类型的信息。与往常一样,过度简化会导致后续恢复或补偿缺失信息的复杂性。真正的复杂性只能从代码的一部分转移到另一部分。
The complexity of reality can only be eliminated with the magic of "unsupported use cases".
现实的复杂性只能通过“不受支持的用例”的魔力来消除。
Your model would only make sense in a problem space where one didn't care how many days might exist between the start and end times. That problem space doesn't match most real world problems. Therefore, it's not surprising that Joda Time doesn't support it well. The use of 25 values for the hours place (0-24) is a code smelland usually points to a weakness in the design. There are only 24 hours in the day so 25 values should not be needed!
您的模型仅在人们不关心开始时间和结束时间之间可能存在多少天的问题空间中才有意义。该问题空间与大多数现实世界的问题不匹配。因此,Joda Time 不能很好地支持它也就不足为奇了。小时位置 (0-24) 使用 25 个值是一种代码味道,通常表示设计中的弱点。一天只有 24 小时,所以不需要 25 个值!
Note that since you aren't capturing the date on either end of LocalInterval
, your class also does not capture sufficient information to account for daylight savings time. [00:30:00 TO 04:00:00)
is usually 3.5 hours long but could also be 2.5, or 4.5 hours long.
请注意,由于您没有在 的任一端捕获日期LocalInterval
,因此您的类也没有捕获足够的信息来说明夏令时。[00:30:00 TO 04:00:00)
通常为 3.5 小时,但也可能为 2.5 或 4.5 小时。
You should either use a start date/time and duration, or a start date/time and an end date/time (inclusive start, exclusive end is a good default choice). Using a duration becomes tricky if you intend to display the end time because of things like daylight savings time, leap years and leap seconds. On the other hand using an end date becomes just as tricky if you expect to display the duration. Storing both of course is dangerous because it violates the DRY principle. If I were writing such a class I would store an end date/time and encapsulate the logic for obtaining the duration via a method on the object. That way clients of the class class do not all come up with their own code to calculate the duration.
您应该使用开始日期/时间和持续时间,或者使用开始日期/时间和结束日期/时间(包括开始,不包括结束是一个很好的默认选择)。如果由于夏令时、闰年和闰秒等原因而打算显示结束时间,则使用持续时间会变得棘手。另一方面,如果您希望显示持续时间,那么使用结束日期也会变得同样棘手。存储两者当然是危险的,因为它违反了DRY 原则。如果我正在编写这样一个类,我将存储一个结束日期/时间并封装通过对象上的方法获取持续时间的逻辑。这样,类的客户就不会都想出自己的代码来计算持续时间。
I'd code up a example, but there's an even better option. Use the standard IntervalClass from Joda time, which already accepts a start instant and either duration or end instant. It will also and happily calculate the duration or the end time for you. Sadly JSR-310 doesn't have an interval or similar class. (though one can use ThreeTenExtrato make up for that)
我会编写一个示例,但还有一个更好的选择。使用Joda 时间的标准IntervalClass,它已经接受开始时刻和持续时间或结束时刻。它还会很高兴地为您计算持续时间或结束时间。遗憾的是,JSR-310 没有间隔或类似的类。(虽然可以使用ThreeTenExtra来弥补这一点)
The relatively bright folks at Joda Time and Sun/Oracle (JSR-310) both thought very carefully about these problems. You might be smarter than them. It's possible. However, even if you are a brighter bulb, your 1 hour is probably not going to accomplish what they spent years on. Unless you are somewhere out in an esoteric edge case, it's usually waste of time and money to spend effort second guessing them. (of course at the time of the OP JSR-310 wasn't complete...)
Joda Time 和 Sun/Oracle ( JSR-310)相对聪明的人都非常仔细地考虑了这些问题。你可能比他们聪明。这是可能的。然而,即使你是一个更亮的灯泡,你的 1 小时也可能无法完成他们花费多年的时间。除非你在某个深奥的边缘情况下,否则花费精力再猜测它们通常是浪费时间和金钱。(当然,当时 OP JSR-310 还没有完成……)
Hopefully the above will help folks who find this question while designing or fixing similar issues.
希望以上内容能帮助那些在设计或解决类似问题时发现这个问题的人。