From 4325ffa1369b5c91497fe9f394d5b7f48f0e77e0 Mon Sep 17 00:00:00 2001 From: Janik Rabe Date: Thu, 23 Aug 2018 10:08:10 +0200 Subject: [PATCH] Handle zero-width intervals correctly Fixes #101 Fixes #165 Closes #164 --- src/Database.cpp | 5 ++++- src/Datafile.cpp | 4 ++-- src/Range.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++- src/Range.h | 2 ++ src/data.cpp | 22 +++++++++---------- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/Database.cpp b/src/Database.cpp index d4e9c711..d3b7d3b2 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -380,7 +380,10 @@ std::vector Database::segmentRange (const Range& range) // Capture date after incrementing month. Datetime segmentEnd (start_y, start_m, 1); - segments.push_back (Range (segmentStart, segmentEnd)); + auto segment = Range (segmentStart, segmentEnd); + if (range.intersects (segment)) { + segments.push_back (segment); + } } return segments; diff --git a/src/Datafile.cpp b/src/Datafile.cpp index 4685dfee..7b1bfbd1 100644 --- a/src/Datafile.cpp +++ b/src/Datafile.cpp @@ -90,7 +90,7 @@ std::vector Datafile::allLines () void Datafile::addInterval (const Interval& interval) { // Note: end date might be zero. - assert (_range.overlap (interval.range)); + assert (_range.segmentContains (interval.range)); if (! _lines_loaded) load_lines (); @@ -108,7 +108,7 @@ void Datafile::addInterval (const Interval& interval) void Datafile::deleteInterval (const Interval& interval) { // Note: end date might be zero. - assert (_range.overlap (interval.range)); + assert (_range.segmentContains (interval.range)); if (! _lines_loaded) load_lines (); diff --git a/src/Range.cpp b/src/Range.cpp index 9f6db0c6..44edbb52 100644 --- a/src/Range.cpp +++ b/src/Range.cpp @@ -179,6 +179,41 @@ bool Range::encloses (const Range& other) const return false; } + +//////////////////////////////////////////////////////////////////////////////// +// This is a standard enclosure check, except that the other range +// need not have an end time, even if this one does. +// +// Detect the following enclosure cases: +// +// this [--------) +// A [----) +// B [... +// +// this [... +// A [----) +// B [--------) +// C [--------) +// D [... +// E [... +// +bool Range::segmentContains (const Range& other) const +{ + if (is_started ()) { + if (is_ended ()) { + if (other.is_started () && other.start >= start) { + return ! other.is_ended () || other.end <= end; + } + } else { + if (other.is_started () && other.start >= start) { + return true; + } + } + } + + return false; +} + //////////////////////////////////////////////////////////////////////////////// // Calculate the following intersection cases: // @@ -216,7 +251,7 @@ Range Range::intersect (const Range& other) const if (is_ended ()) { if (other.is_ended ()) - result.end = end < other.end ? end : other.end; + result.end = end < other.end ? end : other.end; else result.end = end; } @@ -229,9 +264,28 @@ Range Range::intersect (const Range& other) const return result; } + // If there is an intersection but no overlap, we have a zero-width + // interval [p, p) and another interval [p, q), where q >= p. + if (intersects (other)) { + return Range {start, start}; + } + return Range {}; } +//////////////////////////////////////////////////////////////////////////////// +bool Range::intersects (const Range &other) const +{ + if (overlap (other)) { + return true; + } + + // A half-closed zero-width interval [p, p) may have the same + // starting point as another interval without overlapping it. + // We consider p to be an element of a range [p, p). + return (is_started () && other.is_started () && start == other.start); +} + //////////////////////////////////////////////////////////////////////////////// // If the ranges do not overlap, the result is *this. // diff --git a/src/Range.h b/src/Range.h index 3002618e..680b39f5 100644 --- a/src/Range.h +++ b/src/Range.h @@ -51,7 +51,9 @@ public: bool overlap (const Range&) const; bool encloses (const Range&) const; + bool segmentContains (const Range&) const; Range intersect (const Range&) const; + bool intersects (const Range&) const; Range combine (const Range&) const; std::vector subtract (const Range&) const; time_t total () const; diff --git a/src/data.cpp b/src/data.cpp index 0009e4e6..0dd26d4a 100644 --- a/src/data.cpp +++ b/src/data.cpp @@ -343,9 +343,11 @@ std::vector subset ( const std::vector & ranges) { std::vector all; - for (auto& r : ranges) - if (range.overlap (r)) + for (auto& r : ranges) { + if (range.intersects (r)) { all.push_back (r); + } + } return all; } @@ -356,9 +358,11 @@ std::vector subset ( const std::vector & intervals) { std::vector all; - for (auto& interval : intervals) - if (range.overlap (interval.range)) + for (auto& interval : intervals) { + if (range.intersects (interval.range)) { all.push_back (interval); + } + } return all; } @@ -549,12 +553,8 @@ Range outerRange (const std::vector & intervals) bool matchesFilter (const Interval& interval, const Interval& filter) { if ((filter.range.start.toEpoch () == 0 && - filter.range.end.toEpoch () == 0) - - || - - ((interval.range.end.toEpoch () == 0 || interval.range.end > filter.range.start) && - (filter.range.end.toEpoch () == 0 || interval.range.start < filter.range.end))) + filter.range.end.toEpoch () == 0 + ) || interval.range.intersects (filter.range)) { for (auto& tag : filter.tags ()) if (! interval.hasTag (tag)) @@ -608,7 +608,7 @@ std::vector getTracked ( if (! inclusions.empty ()) { auto latest = inclusions.back(); - if (latest.range.is_open()) {; + if (latest.range.is_open()) { filter.range.end = 0; } }