我有一个自定义java同步,它通过在tomcat上运行的SOAP服务按日期范围获取数据。 例如:
getDataByDateRange(startDate,endDate)
getDataByDateRange('2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000')
我想编写一个控制程序来检查任何类型的运行时或服务器错误是否遗漏了任何范围。
如何找到缺少的日期范围?
感谢。
视觉示例:
TimeLine : [------------------------------------------------------------------]
Processed Dates: [----1---][---2----]---[-3-][--4---]---[----5---][---6--]-----------
Missing Dates : -------------------[-1-]-----------[-2-]----------------[-----3----]
TimeLine:
1: '2016-01-01 10:00:00.00000','2016-02-01 09:00:00.00000'
Processed Dates:
1: '2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000'
2: '2016-01-01 11:00:00.00000','2016-01-01 12:00:00.00000'
3: '2016-01-01 13:00:00.00000','2016-01-01 13:30:00.00000'
4: '2016-01-01 13:30:00.00000','2016-01-01 14:30:00.00000'
5: '2016-01-01 15:30:00.00000','2016-01-01 16:30:00.00000'
6: '2016-01-01 16:30:00.00000','2016-01-01 17:00:00.00000'
Missing Dates:
1: '2016-01-01 12:00:00.00000','2016-01-01 13:00:00.00000'
2: '2016-01-01 14:30:00.00000','2016-01-01 15:30:00.00000'
3: '2016-01-01 17:00:00.00000','2016-01-02 09:00:00.00000'
答案 0 :(得分:2)
根据你的评论我发布我以前的评论作为答案。此解决方案使用我的库Time4J(包括range-module):
// prepare parser
ChronoFormatter<PlainTimestamp> f =
ChronoFormatter.ofTimestampPattern( // five decimal digits
"uuuu-MM-dd HH:mm:ss.SSSSS", PatternType.CLDR, Locale.ROOT);
// parse input to intervals - here the overall time window
TimestampInterval timeline =
TimestampInterval.between(
f.parse("2016-01-01 10:00:00.00000"),
f.parse("2016-02-01 09:00:00.00000"));
// for more flexibility - consider a for-each-loop
TimestampInterval i1 =
TimestampInterval.between(f.parse("2016-01-01 10:00:00.00000"), f.parse("2016-01-01 11:00:00.00000"));
TimestampInterval i2 =
TimestampInterval.between(f.parse("2016-01-01 11:00:00.00000"), f.parse("2016-01-01 12:00:00.00000"));
TimestampInterval i3 =
TimestampInterval.between(f.parse("2016-01-01 13:00:00.00000"), f.parse("2016-01-01 13:30:00.00000"));
TimestampInterval i4 =
TimestampInterval.between(f.parse("2016-01-01 13:30:00.00000"), f.parse("2016-01-01 14:30:00.00000"));
TimestampInterval i5 =
TimestampInterval.between(f.parse("2016-01-01 15:30:00.00000"), f.parse("2016-01-01 16:30:00.00000"));
TimestampInterval i6 =
TimestampInterval.between(f.parse("2016-01-01 16:30:00.00000"), f.parse("2016-01-01 17:00:00.00000"));
// apply interval arithmetic
IntervalCollection<PlainTimestamp> icoll =
IntervalCollection.onTimestampAxis().plus(Arrays.asList(i1, i2, i3, i4, i5, i6));
List<ChronoInterval<PlainTimestamp>> missed = icoll.withComplement(timeline).getIntervals();
// result
System.out.println(missed);
// [[2016-01-01T12/2016-01-01T13), [2016-01-01T14:30/2016-01-01T15:30), [2016-01-01T17/2016-02-01T09)]
整个区间运算的核心仅由代码片段icoll.withComplement(timeline)
完成。其余的只是创建间隔。通过应用for-each-loop,您肯定可以再次最小化所呈现代码中的行数。
输出基于隐式使用toString()
的间隔的规范描述,例如:[2016-01-01T12/2016-01-01T13)
方括号表示闭合边界,而右边的圆括号表示开放边界。 所以我们这里有半开时间戳间隔的标准情况(没有时区)。虽然其他间隔类型是可能的,但我选择了那种类型,因为它对应于输入字符串的类型。
如果您打算在应用的其他部分将此解决方案与Joda-Time 结合使用,请记住a)两个库之间还没有任何特殊的桥接可用和b)转换失去微秒精度(Joda-Time仅支持毫秒)和c)Time4J比Joda-Time(几乎所有东西)都有更多的功率。无论如何,你可以做转换(如果你不想更大程度地重写你的应用程序,那就很重要):
ChronoInterval<PlainTimestamp> missed0 = missed.get(0);
PlainTimestamp tsp = missed0.getStart().getTemporal();
LocalDateTime ldt = // joda-equivalent
new LocalDateTime(
tsp.getYear(), tsp.getMonth(), tsp.getDayOfMonth(),
tsp.getHour(), tsp.getMinute(), tsp.getSecond(), tsp.get(PlainTime.MILLI_OF_SECOND));
System.out.println(ldt); // 2016-01-01T10:00:00.000
关于仅限Joda的解决方案:
Joda-Time仅支持instant intervals,而不支持没有时区的时间戳间隔。但是,您可以通过将时区硬连线到UTC(使用固定偏移量)来模拟丢失的间隔类型。
另一个问题是缺少对五位小数的支持。你可以通过这个黑客来规避它:
DateTime start =
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
.withZoneUTC()
.parseDateTime("2016-01-01 16:30:00.00000".substring(0, 23));
System.out.println(start); // 2016-01-01T16:30:00.000Z
DateTime end = ...;
Interval interval = new Interval(start, end);
解决方案的另一个更重要的元素几乎缺失 - 区间算术。您必须先按开始时间(然后是结束时刻)对时间间隔进行排序。排序后,您可以遍历所有间隔,以便找到间隙。 Joda-Time可以为您做的最好的事情就是为您提供isBefore(anotherInstant)等方法,您可以在自己的解决方案中使用这些方法。但它变得非常臃肿。
答案 1 :(得分:1)
我刚刚发布了IntervalTree
- 它似乎与这类问题配合得很好。
请参阅minimise
方法了解您的目标。
/**
* Title: IntervlTree
*
* Description: Implements a static Interval Tree. i.e. adding and removal are not possible.
*
* This implementation uses longs to bound the intervals but could just as easily use doubles or any other linear value.
*
* @author OldCurmudgeon
* @version 1.0
* @param <T> - The Intervals to work with.
*/
public class IntervalTree<T extends IntervalTree.Interval> {
// My intervals.
private final List<T> intervals;
// My center value. All my intervals contain this center.
private final long center;
// My interval range.
private final long lBound;
private final long uBound;
// My left tree. All intervals that end below my center.
private final IntervalTree<T> left;
// My right tree. All intervals that start above my center.
private final IntervalTree<T> right;
public IntervalTree(List<T> intervals) {
if (intervals == null) {
throw new NullPointerException();
}
// Initially, my root contains all intervals.
this.intervals = intervals;
// Find my center.
center = findCenter();
/*
* Builds lefts out of all intervals that end below my center.
* Builds rights out of all intervals that start above my center.
* What remains contains all the intervals that contain my center.
*/
// Lefts contains all intervals that end below my center point.
final List<T> lefts = new ArrayList<>();
// Rights contains all intervals that start above my center point.
final List<T> rights = new ArrayList<>();
long uB = Long.MIN_VALUE;
long lB = Long.MAX_VALUE;
for (T i : intervals) {
long start = i.getStart();
long end = i.getEnd();
if (end < center) {
lefts.add(i);
} else if (start > center) {
rights.add(i);
} else {
// One of mine.
lB = Math.min(lB, start);
uB = Math.max(uB, end);
}
}
// Remove all those not mine.
intervals.removeAll(lefts);
intervals.removeAll(rights);
uBound = uB;
lBound = lB;
// Build the subtrees.
left = lefts.size() > 0 ? new IntervalTree<>(lefts) : null;
right = rights.size() > 0 ? new IntervalTree<>(rights) : null;
// Build my ascending and descending arrays.
/**
* @todo Build my ascending and descending arrays.
*/
}
/*
* Returns a list of all intervals containing the point.
*/
List<T> query(long point) {
// Check my range.
if (point >= lBound) {
if (point <= uBound) {
// Gather all intersecting ones.
List<T> found = intervals
.stream()
.filter((i) -> (i.getStart() <= point && point <= i.getEnd()))
.collect(Collectors.toList());
// Gather others.
if (point < center && left != null) {
found.addAll(left.query(point));
}
if (point > center && right != null) {
found.addAll(right.query(point));
}
return found;
} else {
// To right.
return right != null ? right.query(point) : Collections.<T>emptyList();
}
} else {
// To left.
return left != null ? left.query(point) : Collections.<T>emptyList();
}
}
/**
* Blends the two lists together.
*
* If the ends touch then make them one.
*
* @param a
* @param b
* @return
*/
static List<Interval> blend(List<Interval> a, List<Interval> b) {
// Either empty - return the other.
if (a.isEmpty()) {
return b;
}
if (b.isEmpty()) {
return a;
}
// Where does a end and b start.
Interval aEnd = a.get(a.size() - 1);
Interval bStart = b.get(0);
ArrayList<Interval> blended = new ArrayList<>();
// Do they meet/cross?
if (aEnd.getEnd() >= bStart.getStart() - 1) {
// Yes! merge them.
// Remove the last.
blended.addAll(a.subList(0, a.size() - 1));
// Add a combined one.
blended.add(new SimpleInterval(aEnd.getStart(), bStart.getEnd()));
// Add all but the first.
blended.addAll(b.subList(1, b.size()));
} else {
// Just join them.
blended.addAll(a);
blended.addAll(b);
}
return blended;
}
static List<Interval> blend(List<Interval> a, List<Interval> b, List<Interval>... more) {
List<Interval> blended = blend(a, b);
for (List<Interval> l : more) {
blended = blend(blended, l);
}
return blended;
}
List<Interval> minimise() {
// Calculate min of left and right.
List<Interval> minLeft = left != null ? left.minimise() : Collections.EMPTY_LIST;
List<Interval> minRight = right != null ? right.minimise() : Collections.EMPTY_LIST;
// My contribution.
long meLeft = minLeft.isEmpty() ? lBound : Math.max(lBound, minLeft.get(minLeft.size() - 1).getEnd());
long meRight = minRight.isEmpty() ? uBound : Math.min(uBound, minRight.get(0).getEnd());
return blend(minLeft,
Collections.singletonList(new SimpleInterval(meLeft, meRight)),
minRight);
}
private long findCenter() {
//return average();
return median();
}
protected long median() {
if (intervals.isEmpty()) {
return 0;
}
// Choose the median of all centers. Could choose just ends etc or anything.
long[] points = new long[intervals.size()];
int x = 0;
for (T i : intervals) {
// Take the mid point.
points[x++] = (i.getStart() + i.getEnd()) / 2;
}
Arrays.sort(points);
return points[points.length / 2];
}
/*
* What an interval looks like.
*/
public interface Interval {
public long getStart();
public long getEnd();
}
/*
* A simple implemementation of an interval.
*/
public static class SimpleInterval implements Interval {
private final long start;
private final long end;
public SimpleInterval(long start, long end) {
this.start = start;
this.end = end;
}
@Override
public long getStart() {
return start;
}
@Override
public long getEnd() {
return end;
}
@Override
public String toString() {
return "{" + start + "," + end + "}";
}
}
/**
* Not called by App, so you will have to call this directly.
*
* @param args
*/
public static void main(String[] args) {
/**
* @todo Needs MUCH more rigorous testing.
*/
// Test data.
long[][] data = {
{1, 4}, {2, 5}, {5, 7}, {10, 11}, {13, 20}, {19, 21},};
List<Interval> intervals = new ArrayList<>();
for (long[] pair : data) {
intervals.add(new SimpleInterval(pair[0], pair[1]));
}
// Build it.
IntervalTree<Interval> test = new IntervalTree<>(intervals);
// Test it.
System.out.println("Normal test: ---");
for (long i = 0; i < 10; i++) {
List<Interval> intersects = test.query(i);
System.out.println("Point " + i + " intersects:");
intersects.stream().forEach((t) -> {
System.out.println(t.toString());
});
}
// Check minimise.
List<Interval> min = test.minimise();
System.out.println("Minimise test: ---");
System.out.println(min);
// Check for empty list.
intervals.clear();
test = new IntervalTree<>(intervals);
// Test it.
System.out.println("Empty test: ---");
for (long i = 0; i < 10; i++) {
List<Interval> intersects = test.query(i);
System.out.println("Point " + i + " intersects:");
intersects.stream().forEach((t) -> {
System.out.println(t.toString());
});
}
}
}
这接近你要找的东西。这里有一些代码可以将您的范围最小化为3
。
static String[][] dates = {{"2016-01-01 10:00:00.00000", "2016-01-01 11:00:00.00000"}, {"2016-01-01 11:00:00.00000", "2016-01-01 12:00:00.00000"}, {"2016-01-01 13:00:00.00000", "2016-01-01 13:30:00.00000"}, {"2016-01-01 13:30:00.00000", "2016-01-01 14:30:00.00000"}, {"2016-01-01 15:30:00.00000", "2016-01-01 16:30:00.00000"}, {"2016-01-01 16:30:00.00000", "2016-01-01 17:00:00.00000"}};
static List<IntervalTree.SimpleInterval> ranges = new ArrayList<>();
static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
static {
for (String[] pair : dates) {
try {
ranges.add(new IntervalTree.SimpleInterval(df.parse(pair[0]).getTime(), df.parse(pair[1]).getTime()));
} catch (ParseException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public void test() {
IntervalTree tree = new IntervalTree<>(ranges);
List<IntervalTree.Interval> min = tree.minimise();
//System.out.println("min->" + min);
for (IntervalTree.Interval i : min) {
System.out.println(df.format(new Date(i.getStart())) + " - " + df.format(new Date(i.getEnd())));
}
}
打印
2016-01-01 10:00:00.0 - 2016-01-01 12:00:00.0
2016-01-01 13:00:00.0 - 2016-01-01 14:30:00.0
2016-01-01 15:30:00.0 - 2016-01-01 17:00:00.0
这是您Processed Date
加入三个日期范围的所有内容。
答案 2 :(得分:0)
鉴于日期范围的频率是一小时,您可以从范围开始日期开始,迭代到范围结束日期,并编写一个方法来检查带日期的条目。您可以使用DateUtils添加小时数,如下面的伪代码所示:
Date startDate = startDate;
Date endDate = endDate;
while (startDate.before(endDate){
if(!exists(startDate, DateUtils.addHours(startDate, 1), entries)){
//Add into missing entries
}
startDate = DateUtils.addHours(startDate, 1);
}