mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-07-07 20:06:39 +02:00
Handle zero-width intervals correctly
Fixes #101 Fixes #165 Closes #164
This commit is contained in:
parent
0273987ae6
commit
4325ffa136
5 changed files with 74 additions and 15 deletions
|
@ -380,7 +380,10 @@ std::vector <Range> 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;
|
||||
|
|
|
@ -90,7 +90,7 @@ std::vector <std::string> 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 ();
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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 <Range> subtract (const Range&) const;
|
||||
time_t total () const;
|
||||
|
|
22
src/data.cpp
22
src/data.cpp
|
@ -343,9 +343,11 @@ std::vector <Range> subset (
|
|||
const std::vector <Range>& ranges)
|
||||
{
|
||||
std::vector <Range> 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 <Interval> subset (
|
|||
const std::vector <Interval>& intervals)
|
||||
{
|
||||
std::vector <Interval> 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 <Interval>& 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 <Interval> getTracked (
|
|||
|
||||
if (! inclusions.empty ()) {
|
||||
auto latest = inclusions.back();
|
||||
if (latest.range.is_open()) {;
|
||||
if (latest.range.is_open()) {
|
||||
filter.range.end = 0;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue