找出一系列日期是否涵盖一个时间间隔
Find out if a series of dates covering an interval
我有两个对象日历
Calendar startCalendar = new GregorianCalendar(2013,0,31);
Calendar endCalendar = new GregorianCalendar();
我想知道上面列出的两个日期之间的间隔是否被n个其他对象对日历覆盖而没有间隔之间的空洞
示例 1:
Calendar startCalendar1(2013,0,31);
Calendar endCalendar1(2014,0,31);
Calendar startCalendar2(2013,5,31);
Calendar endCalendar2();
很好
示例 2:
Calendar startCalendar1(2013,0,31);
Calendar endCalendar1(2014,0,31);
Calendar startCalendar2(2014,2,31);
Calendar endCalendar2();
不好
我用Java6
谢谢
1 粗鲁但简单的方法
使用集合<长>
Set<Long> all_times_in_milli=new HashSet<Long>();
// Put every interval
// interval 1
for (long time_in_millis=startCalendar1.getTimeInMillis();
time_in_millis<= endCalendar1.getTimeInMillis();
time_in_millis+=86400000)
all_times_in_milli.add(time_in_millis);
// interval 2
for (long time_in_millis=startCalendar2.getTimeInMillis();
time_in_millis<= endCalendar2.getTimeInMillis();
time_in_millis+=86400000)
all_times_in_milli.add(time_in_millis);
// ETC
// AND TEST !
boolean failed=false;
for (long time_in_millis=startCalendar.getTimeInMillis();
time_in_millis<= endCalendar.getTimeInMillis();
time_in_millis+=86400000)
{
if (all_times_in_milli.contains(time_in_millis))
{
failed=true; break;
}
}
if (failed) System.out.println("Your are done !");
2 更聪明的方法
因为每个区间都是[long - long]区间
- assemble 你的间隔得到连续间隔(重叠间隔集)=> 然后你得到 B1-E1、B2-E2、B3-E3 不同的间隔
- 检查你的第一个区间是否在其中:B1 <= start <= end <=E1,
或 B2 <= 开始 <= 结束 <=E2, ...
只有当你有很多数据时才有趣
您使用的旧日期时间 classes 已被 Java 8 及更高版本中的 java.time 框架取代。那些旧的 classes 已被证明是笨重、混乱和有缺陷的。
java.time
新java.time classes are inspired by the highly successful Joda‑Time library, intended as its successor, similar in concept but re-architected. Defined by JSR 310. Extended by the ThreeTen‑Extra project. See the Tutorial.
新的 classes 包括 LocalDate
用于没有时间的仅日期值。为了您的目的,请使用它而不是 Calendar
。
请注意,月份数字通常从一开始,这与 Calendar
不同。
LocalDate start = LocalDate.of( 2013 , 1 , 31 );
请注意,为了确定日期,时区至关重要。世界各地的日期并不同时相同。例如,巴黎的新一天比蒙特利尔早。
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
LocalDate today = LocalDate.now ( zoneId );
从那里您可以调用 isAfter
、isBefore
或 isEqual
的任意组合来执行您的逻辑。您的问题并不清楚该逻辑,因此我无法解决。
扩展 java.time 的 ThreeTen-Extra 项目包括 Interval
class that would help you. Unfortunately that class works only with Instant
objects (date-time in UTC), not LocalDate
. Specifically the methods for comparing intervals would help, abuts
, encloses
, and overlaps
。
您将为 LocalDate 对象创建自己的 IntervalLD
class。通常我不建议滚动你自己的日期时间处理 classes 因为日期时间工作非常棘手。但在这种情况下 LocalDate
逻辑 可能 简单。这是我的快速草稿 完全未经测试的 示例,可帮助您入门。
package com.example.javatimestuffmaven;
import java.time.LocalDate;
/**
* Similar to the 'Interval'class in the ThreeTen-Extra project, but for LocalDate objects.
*
* @author Basil Bourque
*/
public class IntervalLD {
private LocalDate start, end;
// Constructor
public IntervalLD ( LocalDate startArg , LocalDate endArg ) {
this.start = startArg;
this.end = endArg;
}
public Boolean isBefore ( IntervalLD interval ) {
// True if this one's end is before that one's start.
boolean before = this.getEnd ().isBefore ( interval.getStart () );
return before;
}
public Boolean isAfter ( IntervalLD interval ) {
// True if this one's start is after that one's end.
boolean after = this.getStart ().isAfter ( interval.getStart () );
return after;
}
public Boolean abuts ( IntervalLD interval ) {
// True if the intervals are next to each other on the time line but do not share a date. (exclusive of each other, not half-open)
// True if either one's end is a day ahead of the other's start or vice versa, either's start is day after the other's end.
if ( this.isBefore ( interval ) ) {
if ( this.getEnd ().plusDays ( 1 ).equals ( interval.getStart () ) ) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
} else if ( this.isAfter ( interval ) ) {
if ( this.getStart ().minusDays ( 1 ).equals ( interval.getEnd () ) ) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
} else if ( this.isEqual ( interval ) ) {
return Boolean.FALSE;
}
// Impossible. Should never reach this point.
// TODO: Handle this error condition.
return Boolean.FALSE;
}
public Boolean encloses ( IntervalLD interval ) {
//This checks if the specified interval is fully enclosed by this interval.
// The result is true if the start of the specified interval is contained in this interval, and
// the end is contained or equal to the end of this interval.
boolean thatOneStartsOnOrAfterThisOne = ! interval.getStart ().isBefore ( this.getStart () );
boolean thatOneEndsOnOrAfterThisOne = ! interval.getEnd ().isAfter ( this.getEnd () );
boolean doesEnclose = ( thatOneStartsOnOrAfterThisOne && thatOneEndsOnOrAfterThisOne );
return doesEnclose;
}
public Boolean overlaps ( IntervalLD interval ) {
// True if the two intervals share some part of the timeline.
// True if this interval does NOT start after that one ends OR this interval does NOT end before that one starts.
boolean startsTooLate = this.getStart ().isAfter ( interval.getEnd () );
boolean endsTooEarly = this.getEnd ().isAfter ( interval.getEnd () );
boolean doesOverlap = ( ! startsTooLate && ! endsTooEarly );
return ( doesOverlap );
}
public Boolean isEqual ( IntervalLD interval ) {
boolean sameStart = this.getStart ().isEqual ( interval.getStart () );
boolean sameEnd = this.getEnd ().isEqual ( interval.getEnd () );
return ( sameStart && sameEnd );
}
@Override
public String toString () {
String output = this.getStart () + "/" + this.getEnd ();
return output;
}
// Getters. Read-only (immutable) so no Setters.
/**
* @return the start
*/
public LocalDate getStart () {
return this.start;
}
/**
* @return the end
*/
public LocalDate getEnd () {
return this.end;
}
}
第一种方法:仅使用Java 6
当我看到像 2015-01-31
这样的日期示例时,我强烈怀疑您说的是 截止日期间隔 否则选择月末可能会出现有点奇怪。这是一种广泛传播且合理的方法。不幸的是,选择像 java.util.Calendar
这样的数据类型来表示一个瞬间(也是一个日期-时区-组合)与闭区间不一致。这种类似即时的类型在半开间隔下效果更好。结果是:
如果你决定只使用 Java-6-types 那么你可以尝试将所有 Calendar
-objects 转换为 Long-values 代表自 Unix 纪元以来经过的毫秒数,正如@guillaume所建议的girod-vitouchkina(以我的赞成票为例,如何在没有任何外部库的情况下执行此操作)。 但是你必须提前为每个Calendar
对象(如果代表结束边界)添加额外的一天,以达到关闭日期间隔的效果。
当然,您仍然需要自己做一些自己开发的区间算术,如该答案中粗略显示的那样。如果你仔细研究其他提案和你自己的需求,你会发现最终的解决方案甚至需要的不仅仅是一个新的区间 class 或者区间的基本比较。您还需要一个更高的抽象层,即在几个间隔之间定义操作。自己做这一切可能会引起一些头痛。另一方面:如果您具有良好的编程技能,那么实施基于 Long 的区间算法可能会节省一些性能开销,这是典型的额外区间库。
第二种方法:使用专用区间库
我只知道四个承诺处理间隔的库。无法使用@Basil Bourque 提到的Threeten-Extra,因为它需要Java-8。它的间隔 class 的缺点是只能处理瞬间,不能处理日历日期。也几乎不支持处理间隔集合。 Joda-Time 也是如此(它至少在 Java-6 上工作并且还提供专用的日历日期类型,即 LocalDate
但没有日期间隔)。
一个有趣的选择是使用 Guava 及其 class RangeSet,特别是如果您决定继续使用 Calendar
-objects 和 Longs .这个 class 对处理间隔之间的操作有一些支持 - 对我来说比使用 Joda-Time 的简单间隔 class 更具吸引力。
最后,您还可以选择使用我的库 Time4J,其中包含 range-package。我现在将展示一个完整的解决方案来解决您的问题:
// our test interval
PlainDate start = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate end = SystemClock.inLocalView().today();
DateInterval test = DateInterval.between(start, end);
IntervalCollection<PlainDate> icTest = IntervalCollection.onDateAxis().plus(test);
// two intervals for your GOOD case
PlainDate s1 = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate e1 = PlainDate.of(2014, Month.JANUARY, 31);
DateInterval i1 = DateInterval.between(s1, e1);
PlainDate s2 = PlainDate.of(2013, Month.MAY, 31);
PlainDate e2 = end; // today
DateInterval i2 = DateInterval.between(s2, e2);
IntervalCollection<PlainDate> goodCase =
IntervalCollection.onDateAxis().plus(i1).plus(i2);
boolean covered = icTest.minus(goodCase).isEmpty();
System.out.println("Good case: " + covered); // true
// two intervals for your BAD case
PlainDate s3 = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate e3 = PlainDate.of(2014, Month.JANUARY, 31);
DateInterval i3 = DateInterval.between(s3, e3);
PlainDate s4 = PlainDate.of(2014, Month.MARCH, 31);
PlainDate e4 = end; // today
DateInterval i4 = DateInterval.between(s4, e4);
IntervalCollection<PlainDate> badCase =
IntervalCollection.onDateAxis().plus(i3).plus(i4);
covered = icTest.minus(badCase).isEmpty();
System.out.println("Bad case: " + covered); // false
代码的最大部分只是区间构造。真正的区间运算本身是由这个小得惊人的代码片段完成的:
boolean covered =
IntervalCollection.onDateAxis().plus(test).minus(
IntervalCollection.onDateAxis().plus(i1).plus(i2)
).isEmpty();
说明:如果test减去i1和i2的余数为空,则测试区间被区间i1和i2覆盖
顺便提一下:Time4J中的日期区间默认是闭区间。但是,如果您确实需要,可以将这些间隔更改为半开间隔(只需在给定的日期间隔内调用 withOpenEnd()
)。
如果您打算稍后迁移到 Java-8,您只需将 Time4J 版本更新到版本行 4.x(版本 v3.x 适用于 Java-6) 并非常容易地转换为 Java-8 类型,如 java.time.LocalDate
(例如:PlainDate.from(localDate)
或 LocalDate ld = plainDate.toTemporalAccessor()
),因此您可以继续使用 Time4J 以获得额外功能即使在未来也不在标准 Java 范围内。
我有两个对象日历
Calendar startCalendar = new GregorianCalendar(2013,0,31);
Calendar endCalendar = new GregorianCalendar();
我想知道上面列出的两个日期之间的间隔是否被n个其他对象对日历覆盖而没有间隔之间的空洞
示例 1:
Calendar startCalendar1(2013,0,31);
Calendar endCalendar1(2014,0,31);
Calendar startCalendar2(2013,5,31);
Calendar endCalendar2();
很好
示例 2:
Calendar startCalendar1(2013,0,31);
Calendar endCalendar1(2014,0,31);
Calendar startCalendar2(2014,2,31);
Calendar endCalendar2();
不好
我用Java6 谢谢
1 粗鲁但简单的方法
使用集合<长>
Set<Long> all_times_in_milli=new HashSet<Long>();
// Put every interval
// interval 1
for (long time_in_millis=startCalendar1.getTimeInMillis();
time_in_millis<= endCalendar1.getTimeInMillis();
time_in_millis+=86400000)
all_times_in_milli.add(time_in_millis);
// interval 2
for (long time_in_millis=startCalendar2.getTimeInMillis();
time_in_millis<= endCalendar2.getTimeInMillis();
time_in_millis+=86400000)
all_times_in_milli.add(time_in_millis);
// ETC
// AND TEST !
boolean failed=false;
for (long time_in_millis=startCalendar.getTimeInMillis();
time_in_millis<= endCalendar.getTimeInMillis();
time_in_millis+=86400000)
{
if (all_times_in_milli.contains(time_in_millis))
{
failed=true; break;
}
}
if (failed) System.out.println("Your are done !");
2 更聪明的方法 因为每个区间都是[long - long]区间
- assemble 你的间隔得到连续间隔(重叠间隔集)=> 然后你得到 B1-E1、B2-E2、B3-E3 不同的间隔
- 检查你的第一个区间是否在其中:B1 <= start <= end <=E1, 或 B2 <= 开始 <= 结束 <=E2, ...
只有当你有很多数据时才有趣
您使用的旧日期时间 classes 已被 Java 8 及更高版本中的 java.time 框架取代。那些旧的 classes 已被证明是笨重、混乱和有缺陷的。
java.time
新java.time classes are inspired by the highly successful Joda‑Time library, intended as its successor, similar in concept but re-architected. Defined by JSR 310. Extended by the ThreeTen‑Extra project. See the Tutorial.
新的 classes 包括 LocalDate
用于没有时间的仅日期值。为了您的目的,请使用它而不是 Calendar
。
请注意,月份数字通常从一开始,这与 Calendar
不同。
LocalDate start = LocalDate.of( 2013 , 1 , 31 );
请注意,为了确定日期,时区至关重要。世界各地的日期并不同时相同。例如,巴黎的新一天比蒙特利尔早。
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
LocalDate today = LocalDate.now ( zoneId );
从那里您可以调用 isAfter
、isBefore
或 isEqual
的任意组合来执行您的逻辑。您的问题并不清楚该逻辑,因此我无法解决。
扩展 java.time 的 ThreeTen-Extra 项目包括 Interval
class that would help you. Unfortunately that class works only with Instant
objects (date-time in UTC), not LocalDate
. Specifically the methods for comparing intervals would help, abuts
, encloses
, and overlaps
。
您将为 LocalDate 对象创建自己的 IntervalLD
class。通常我不建议滚动你自己的日期时间处理 classes 因为日期时间工作非常棘手。但在这种情况下 LocalDate
逻辑 可能 简单。这是我的快速草稿 完全未经测试的 示例,可帮助您入门。
package com.example.javatimestuffmaven;
import java.time.LocalDate;
/**
* Similar to the 'Interval'class in the ThreeTen-Extra project, but for LocalDate objects.
*
* @author Basil Bourque
*/
public class IntervalLD {
private LocalDate start, end;
// Constructor
public IntervalLD ( LocalDate startArg , LocalDate endArg ) {
this.start = startArg;
this.end = endArg;
}
public Boolean isBefore ( IntervalLD interval ) {
// True if this one's end is before that one's start.
boolean before = this.getEnd ().isBefore ( interval.getStart () );
return before;
}
public Boolean isAfter ( IntervalLD interval ) {
// True if this one's start is after that one's end.
boolean after = this.getStart ().isAfter ( interval.getStart () );
return after;
}
public Boolean abuts ( IntervalLD interval ) {
// True if the intervals are next to each other on the time line but do not share a date. (exclusive of each other, not half-open)
// True if either one's end is a day ahead of the other's start or vice versa, either's start is day after the other's end.
if ( this.isBefore ( interval ) ) {
if ( this.getEnd ().plusDays ( 1 ).equals ( interval.getStart () ) ) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
} else if ( this.isAfter ( interval ) ) {
if ( this.getStart ().minusDays ( 1 ).equals ( interval.getEnd () ) ) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
} else if ( this.isEqual ( interval ) ) {
return Boolean.FALSE;
}
// Impossible. Should never reach this point.
// TODO: Handle this error condition.
return Boolean.FALSE;
}
public Boolean encloses ( IntervalLD interval ) {
//This checks if the specified interval is fully enclosed by this interval.
// The result is true if the start of the specified interval is contained in this interval, and
// the end is contained or equal to the end of this interval.
boolean thatOneStartsOnOrAfterThisOne = ! interval.getStart ().isBefore ( this.getStart () );
boolean thatOneEndsOnOrAfterThisOne = ! interval.getEnd ().isAfter ( this.getEnd () );
boolean doesEnclose = ( thatOneStartsOnOrAfterThisOne && thatOneEndsOnOrAfterThisOne );
return doesEnclose;
}
public Boolean overlaps ( IntervalLD interval ) {
// True if the two intervals share some part of the timeline.
// True if this interval does NOT start after that one ends OR this interval does NOT end before that one starts.
boolean startsTooLate = this.getStart ().isAfter ( interval.getEnd () );
boolean endsTooEarly = this.getEnd ().isAfter ( interval.getEnd () );
boolean doesOverlap = ( ! startsTooLate && ! endsTooEarly );
return ( doesOverlap );
}
public Boolean isEqual ( IntervalLD interval ) {
boolean sameStart = this.getStart ().isEqual ( interval.getStart () );
boolean sameEnd = this.getEnd ().isEqual ( interval.getEnd () );
return ( sameStart && sameEnd );
}
@Override
public String toString () {
String output = this.getStart () + "/" + this.getEnd ();
return output;
}
// Getters. Read-only (immutable) so no Setters.
/**
* @return the start
*/
public LocalDate getStart () {
return this.start;
}
/**
* @return the end
*/
public LocalDate getEnd () {
return this.end;
}
}
第一种方法:仅使用Java 6
当我看到像 2015-01-31
这样的日期示例时,我强烈怀疑您说的是 截止日期间隔 否则选择月末可能会出现有点奇怪。这是一种广泛传播且合理的方法。不幸的是,选择像 java.util.Calendar
这样的数据类型来表示一个瞬间(也是一个日期-时区-组合)与闭区间不一致。这种类似即时的类型在半开间隔下效果更好。结果是:
如果你决定只使用 Java-6-types 那么你可以尝试将所有 Calendar
-objects 转换为 Long-values 代表自 Unix 纪元以来经过的毫秒数,正如@guillaume所建议的girod-vitouchkina(以我的赞成票为例,如何在没有任何外部库的情况下执行此操作)。 但是你必须提前为每个Calendar
对象(如果代表结束边界)添加额外的一天,以达到关闭日期间隔的效果。
当然,您仍然需要自己做一些自己开发的区间算术,如该答案中粗略显示的那样。如果你仔细研究其他提案和你自己的需求,你会发现最终的解决方案甚至需要的不仅仅是一个新的区间 class 或者区间的基本比较。您还需要一个更高的抽象层,即在几个间隔之间定义操作。自己做这一切可能会引起一些头痛。另一方面:如果您具有良好的编程技能,那么实施基于 Long 的区间算法可能会节省一些性能开销,这是典型的额外区间库。
第二种方法:使用专用区间库
我只知道四个承诺处理间隔的库。无法使用@Basil Bourque 提到的Threeten-Extra,因为它需要Java-8。它的间隔 class 的缺点是只能处理瞬间,不能处理日历日期。也几乎不支持处理间隔集合。 Joda-Time 也是如此(它至少在 Java-6 上工作并且还提供专用的日历日期类型,即 LocalDate
但没有日期间隔)。
一个有趣的选择是使用 Guava 及其 class RangeSet,特别是如果您决定继续使用 Calendar
-objects 和 Longs .这个 class 对处理间隔之间的操作有一些支持 - 对我来说比使用 Joda-Time 的简单间隔 class 更具吸引力。
最后,您还可以选择使用我的库 Time4J,其中包含 range-package。我现在将展示一个完整的解决方案来解决您的问题:
// our test interval
PlainDate start = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate end = SystemClock.inLocalView().today();
DateInterval test = DateInterval.between(start, end);
IntervalCollection<PlainDate> icTest = IntervalCollection.onDateAxis().plus(test);
// two intervals for your GOOD case
PlainDate s1 = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate e1 = PlainDate.of(2014, Month.JANUARY, 31);
DateInterval i1 = DateInterval.between(s1, e1);
PlainDate s2 = PlainDate.of(2013, Month.MAY, 31);
PlainDate e2 = end; // today
DateInterval i2 = DateInterval.between(s2, e2);
IntervalCollection<PlainDate> goodCase =
IntervalCollection.onDateAxis().plus(i1).plus(i2);
boolean covered = icTest.minus(goodCase).isEmpty();
System.out.println("Good case: " + covered); // true
// two intervals for your BAD case
PlainDate s3 = PlainDate.of(2013, Month.JANUARY, 31);
PlainDate e3 = PlainDate.of(2014, Month.JANUARY, 31);
DateInterval i3 = DateInterval.between(s3, e3);
PlainDate s4 = PlainDate.of(2014, Month.MARCH, 31);
PlainDate e4 = end; // today
DateInterval i4 = DateInterval.between(s4, e4);
IntervalCollection<PlainDate> badCase =
IntervalCollection.onDateAxis().plus(i3).plus(i4);
covered = icTest.minus(badCase).isEmpty();
System.out.println("Bad case: " + covered); // false
代码的最大部分只是区间构造。真正的区间运算本身是由这个小得惊人的代码片段完成的:
boolean covered =
IntervalCollection.onDateAxis().plus(test).minus(
IntervalCollection.onDateAxis().plus(i1).plus(i2)
).isEmpty();
说明:如果test减去i1和i2的余数为空,则测试区间被区间i1和i2覆盖
顺便提一下:Time4J中的日期区间默认是闭区间。但是,如果您确实需要,可以将这些间隔更改为半开间隔(只需在给定的日期间隔内调用 withOpenEnd()
)。
如果您打算稍后迁移到 Java-8,您只需将 Time4J 版本更新到版本行 4.x(版本 v3.x 适用于 Java-6) 并非常容易地转换为 Java-8 类型,如 java.time.LocalDate
(例如:PlainDate.from(localDate)
或 LocalDate ld = plainDate.toTemporalAccessor()
),因此您可以继续使用 Time4J 以获得额外功能即使在未来也不在标准 Java 范围内。