mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-06-26 10:54:28 +02:00
Let Interval inherit from Range
- Remove 1 level of indirection - Remove public field Interval::range (instead use interval directly)
This commit is contained in:
parent
0d33700336
commit
9dd106116c
30 changed files with 289 additions and 280 deletions
|
@ -116,7 +116,7 @@ void Database::addInterval (const Interval& interval, bool verbose)
|
||||||
|
|
||||||
// Get the index into _files for the appropriate Datafile, which may be
|
// Get the index into _files for the appropriate Datafile, which may be
|
||||||
// created on demand.
|
// created on demand.
|
||||||
auto df = getDatafile (interval.range.start.year (), interval.range.start.month ());
|
auto df = getDatafile (interval.start.year (), interval.start.month ());
|
||||||
_files[df].addInterval (interval);
|
_files[df].addInterval (interval);
|
||||||
recordIntervalAction ("", interval.json ());
|
recordIntervalAction ("", interval.json ());
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ void Database::deleteInterval (const Interval& interval)
|
||||||
|
|
||||||
// Get the index into _files for the appropriate Datafile, which may be
|
// Get the index into _files for the appropriate Datafile, which may be
|
||||||
// created on demand.
|
// created on demand.
|
||||||
auto df = getDatafile (interval.range.start.year (), interval.range.start.month ());
|
auto df = getDatafile (interval.start.year (), interval.start.month ());
|
||||||
|
|
||||||
_files[df].deleteInterval (interval);
|
_files[df].deleteInterval (interval);
|
||||||
|
|
||||||
|
@ -347,7 +347,8 @@ std::vector <Range> Database::segmentRange (const Range& range)
|
||||||
// Capture date after incrementing month.
|
// Capture date after incrementing month.
|
||||||
Datetime segmentEnd (start_y, start_m, 1);
|
Datetime segmentEnd (start_y, start_m, 1);
|
||||||
auto segment = Range (segmentStart, segmentEnd);
|
auto segment = Range (segmentStart, segmentEnd);
|
||||||
if (range.intersects (segment)) {
|
if (range.intersects (segment))
|
||||||
|
{
|
||||||
segments.push_back (segment);
|
segments.push_back (segment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,7 +367,7 @@ void Database::initializeTagDatabase ()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* json = (json::object*) json::parse (content);
|
auto* json = (json::object*) json::parse (content);
|
||||||
|
|
||||||
for (auto& pair : json->_data)
|
for (auto& pair : json->_data)
|
||||||
{
|
{
|
||||||
auto key = pair.first;
|
auto key = pair.first;
|
||||||
|
|
|
@ -90,7 +90,7 @@ std::vector <std::string> Datafile::allLines ()
|
||||||
void Datafile::addInterval (const Interval& interval)
|
void Datafile::addInterval (const Interval& interval)
|
||||||
{
|
{
|
||||||
// Note: end date might be zero.
|
// Note: end date might be zero.
|
||||||
assert (interval.range.startsWithin (_range));
|
assert (interval.startsWithin (_range));
|
||||||
|
|
||||||
if (! _lines_loaded)
|
if (! _lines_loaded)
|
||||||
load_lines ();
|
load_lines ();
|
||||||
|
@ -108,7 +108,7 @@ void Datafile::addInterval (const Interval& interval)
|
||||||
void Datafile::deleteInterval (const Interval& interval)
|
void Datafile::deleteInterval (const Interval& interval)
|
||||||
{
|
{
|
||||||
// Note: end date might be zero.
|
// Note: end date might be zero.
|
||||||
assert (interval.range.startsWithin (_range));
|
assert (interval.startsWithin (_range));
|
||||||
|
|
||||||
if (! _lines_loaded)
|
if (! _lines_loaded)
|
||||||
load_lines ();
|
load_lines ();
|
||||||
|
|
|
@ -54,7 +54,7 @@ void Interval::initialize (const std::string& line)
|
||||||
if (tokens.size () > 1 &&
|
if (tokens.size () > 1 &&
|
||||||
tokens[1].length () == 16)
|
tokens[1].length () == 16)
|
||||||
{
|
{
|
||||||
range.start = Datetime (tokens[1]);
|
start = Datetime (tokens[1]);
|
||||||
offset = 1;
|
offset = 1;
|
||||||
|
|
||||||
// Optional '-' <iso>
|
// Optional '-' <iso>
|
||||||
|
@ -62,7 +62,7 @@ void Interval::initialize (const std::string& line)
|
||||||
tokens[2] == "-" &&
|
tokens[2] == "-" &&
|
||||||
tokens[3].length () == 16)
|
tokens[3].length () == 16)
|
||||||
{
|
{
|
||||||
range.end = Datetime (tokens[3]);
|
end = Datetime (tokens[3]);
|
||||||
offset = 3;
|
offset = 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,8 +85,8 @@ void Interval::initialize (const std::string& line)
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
bool Interval::empty () const
|
bool Interval::empty () const
|
||||||
{
|
{
|
||||||
return range.start.toEpoch () == 0 &&
|
return start.toEpoch () == 0 &&
|
||||||
range.end.toEpoch () == 0 &&
|
end.toEpoch () == 0 &&
|
||||||
_tags.empty ();
|
_tags.empty ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,11 +121,11 @@ std::string Interval::serialize () const
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
out << "inc";
|
out << "inc";
|
||||||
|
|
||||||
if (range.start.toEpoch ())
|
if (start.toEpoch ())
|
||||||
out << " " << range.start.toISO ();
|
out << " " << start.toISO ();
|
||||||
|
|
||||||
if (range.end.toEpoch ())
|
if (end.toEpoch ())
|
||||||
out << " - " << range.end.toISO ();
|
out << " - " << end.toISO ();
|
||||||
|
|
||||||
if (! _tags.empty ())
|
if (! _tags.empty ())
|
||||||
{
|
{
|
||||||
|
@ -143,14 +143,14 @@ std::string Interval::json () const
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
out << '{';
|
out << '{';
|
||||||
|
|
||||||
if (range.is_started ())
|
if (is_started ())
|
||||||
out << "\"start\":\"" << range.start.toISO () << "\"";
|
out << "\"start\":\"" << start.toISO () << "\"";
|
||||||
|
|
||||||
if (range.is_ended ())
|
if (is_ended ())
|
||||||
{
|
{
|
||||||
if (range.is_started ())
|
if (is_started ())
|
||||||
out << ',';
|
out << ',';
|
||||||
out << "\"end\":\"" << range.end.toISO () << "\"";
|
out << "\"end\":\"" << end.toISO () << "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! _tags.empty ())
|
if (! _tags.empty ())
|
||||||
|
@ -164,8 +164,8 @@ std::string Interval::json () const
|
||||||
tags += "\"" + escape (tag, '"') + "\"";
|
tags += "\"" + escape (tag, '"') + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (range.start.toEpoch () ||
|
if (start.toEpoch () ||
|
||||||
range.end.toEpoch ())
|
end.toEpoch ())
|
||||||
out << ',';
|
out << ',';
|
||||||
|
|
||||||
out << "\"tags\":["
|
out << "\"tags\":["
|
||||||
|
@ -187,11 +187,11 @@ std::string Interval::dump () const
|
||||||
if (id)
|
if (id)
|
||||||
out << " @" << id;
|
out << " @" << id;
|
||||||
|
|
||||||
if (range.start.toEpoch ())
|
if (start.toEpoch ())
|
||||||
out << " " << range.start.toISOLocalExtended ();
|
out << " " << start.toISOLocalExtended ();
|
||||||
|
|
||||||
if (range.end.toEpoch ())
|
if (end.toEpoch ())
|
||||||
out << " - " << range.end.toISOLocalExtended ();
|
out << " - " << end.toISOLocalExtended ();
|
||||||
|
|
||||||
if (! _tags.empty ())
|
if (! _tags.empty ())
|
||||||
{
|
{
|
||||||
|
@ -206,6 +206,12 @@ std::string Interval::dump () const
|
||||||
return out.str ();
|
return out.str ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interval::setRange (const Range& range)
|
||||||
|
{
|
||||||
|
start = range.start;
|
||||||
|
end = range.end;
|
||||||
|
}
|
||||||
|
|
||||||
Interval Interval::fromJson (std::string jsonString)
|
Interval Interval::fromJson (std::string jsonString)
|
||||||
{
|
{
|
||||||
Interval interval = Interval ();
|
Interval interval = Interval ();
|
||||||
|
@ -226,9 +232,9 @@ Interval Interval::fromJson (std::string jsonString)
|
||||||
}
|
}
|
||||||
|
|
||||||
json::string* start = (json::string*) json->_data["start"];
|
json::string* start = (json::string*) json->_data["start"];
|
||||||
interval.range.start = Datetime(start->_data);
|
interval.start = Datetime(start->_data);
|
||||||
json::string* end = (json::string*) json->_data["end"];
|
json::string* end = (json::string*) json->_data["end"];
|
||||||
interval.range.end = (end != nullptr) ? Datetime(end->_data) : 0;
|
interval.end = (end != nullptr) ? Datetime(end->_data) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return interval;
|
return interval;
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class Interval
|
class Interval : public Range
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Interval () = default;
|
Interval () = default;
|
||||||
|
@ -43,6 +43,8 @@ public:
|
||||||
void tag (const std::string&);
|
void tag (const std::string&);
|
||||||
void untag (const std::string&);
|
void untag (const std::string&);
|
||||||
|
|
||||||
|
void setRange (const Range& range);
|
||||||
|
|
||||||
std::string serialize () const;
|
std::string serialize () const;
|
||||||
std::string json () const;
|
std::string json () const;
|
||||||
std::string dump () const;
|
std::string dump () const;
|
||||||
|
@ -50,7 +52,6 @@ public:
|
||||||
static Interval fromJson (std::string json);
|
static Interval fromJson (std::string json);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Range range {};
|
|
||||||
int id {0};
|
int id {0};
|
||||||
bool synthetic {false};
|
bool synthetic {false};
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ public:
|
||||||
std::vector <Range> subtract (const Range&) const;
|
std::vector <Range> subtract (const Range&) const;
|
||||||
time_t total () const;
|
time_t total () const;
|
||||||
|
|
||||||
std::string dump () const;
|
virtual std::string dump () const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Datetime start {0};
|
Datetime start {0};
|
||||||
|
|
|
@ -36,7 +36,7 @@ int CmdCancel (
|
||||||
// If there is an open interval, cancel it by deleting it..
|
// If there is an open interval, cancel it by deleting it..
|
||||||
auto latest = getLatestInterval (database);
|
auto latest = getLatestInterval (database);
|
||||||
|
|
||||||
if (!latest.range.is_open ())
|
if (!latest.is_open ())
|
||||||
{
|
{
|
||||||
if (rules.getBoolean ("verbose"))
|
if (rules.getBoolean ("verbose"))
|
||||||
std::cout << "There is no active time tracking.\n";
|
std::cout << "There is no active time tracking.\n";
|
||||||
|
|
|
@ -55,12 +55,12 @@ int CmdChartDay (
|
||||||
{
|
{
|
||||||
// Create a filter, and if empty, choose the current day.
|
// Create a filter, and if empty, choose the current day.
|
||||||
auto filter = getFilter (cli);
|
auto filter = getFilter (cli);
|
||||||
if (! filter.range.is_started ())
|
if (! filter.is_started ())
|
||||||
{
|
{
|
||||||
if (rules.has ("reports.day.range"))
|
if (rules.has ("reports.day.range"))
|
||||||
expandIntervalHint (rules.get ("reports.day.range"), filter.range);
|
expandIntervalHint (rules.get ("reports.day.range"), filter);
|
||||||
else
|
else
|
||||||
filter.range = Range (Datetime ("today"), Datetime ("tomorrow"));
|
filter.setRange (Range (Datetime ("today"), Datetime ("tomorrow")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderChart (cli, "day", filter, rules, database);
|
return renderChart (cli, "day", filter, rules, database);
|
||||||
|
@ -74,12 +74,12 @@ int CmdChartWeek (
|
||||||
{
|
{
|
||||||
// Create a filter, and if empty, choose the current week.
|
// Create a filter, and if empty, choose the current week.
|
||||||
auto filter = getFilter (cli);
|
auto filter = getFilter (cli);
|
||||||
if (! filter.range.is_started ())
|
if (! filter.is_started ())
|
||||||
{
|
{
|
||||||
if (rules.has ("reports.week.range"))
|
if (rules.has ("reports.week.range"))
|
||||||
expandIntervalHint (rules.get ("reports.week.range"), filter.range);
|
expandIntervalHint (rules.get ("reports.week.range"), filter);
|
||||||
else
|
else
|
||||||
filter.range = Range (Datetime ("sow"), Datetime ("eow"));
|
filter.setRange (Range (Datetime ("sow"), Datetime ("eow")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderChart (cli, "week", filter, rules, database);
|
return renderChart (cli, "week", filter, rules, database);
|
||||||
|
@ -93,12 +93,12 @@ int CmdChartMonth (
|
||||||
{
|
{
|
||||||
// Create a filter, and if empty, choose the current month.
|
// Create a filter, and if empty, choose the current month.
|
||||||
auto filter = getFilter (cli);
|
auto filter = getFilter (cli);
|
||||||
if (! filter.range.is_started ())
|
if (! filter.is_started ())
|
||||||
{
|
{
|
||||||
if (rules.has ("reports.month.range"))
|
if (rules.has ("reports.month.range"))
|
||||||
expandIntervalHint (rules.get ("reports.month.range"), filter.range);
|
expandIntervalHint (rules.get ("reports.month.range"), filter);
|
||||||
else
|
else
|
||||||
filter.range = Range (Datetime ("som"), Datetime ("eom"));
|
filter.setRange (Range (Datetime ("som"), Datetime ("eom")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderChart (cli, "month", filter, rules, database);
|
return renderChart (cli, "month", filter, rules, database);
|
||||||
|
@ -113,7 +113,7 @@ int renderChart (
|
||||||
Database& database)
|
Database& database)
|
||||||
{
|
{
|
||||||
// Load the data.
|
// Load the data.
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
auto tracked = getTracked (database, rules, filter);
|
auto tracked = getTracked (database, rules, filter);
|
||||||
|
|
||||||
// Map tags to colors.
|
// Map tags to colors.
|
||||||
|
@ -162,7 +162,7 @@ int renderChart (
|
||||||
|
|
||||||
// Each day is rendered separately.
|
// Each day is rendered separately.
|
||||||
time_t total_work = 0;
|
time_t total_work = 0;
|
||||||
for (Datetime day = filter.range.start; day < filter.range.end; day++)
|
for (Datetime day = filter.start; day < filter.end; day++)
|
||||||
{
|
{
|
||||||
// Render the exclusion blocks.
|
// Render the exclusion blocks.
|
||||||
int num_lines = 1;
|
int num_lines = 1;
|
||||||
|
@ -246,24 +246,24 @@ static void determineHourRange (
|
||||||
// Get the extreme time range for the filtered data.
|
// Get the extreme time range for the filtered data.
|
||||||
first_hour = 23;
|
first_hour = 23;
|
||||||
last_hour = 0;
|
last_hour = 0;
|
||||||
for (Datetime day = filter.range.start; day < filter.range.end; day++)
|
for (Datetime day = filter.start; day < filter.end; day++)
|
||||||
{
|
{
|
||||||
auto day_range = getFullDay (day);
|
auto day_range = getFullDay (day);
|
||||||
|
|
||||||
for (auto& track : tracked)
|
for (auto& track : tracked)
|
||||||
{
|
{
|
||||||
if (day_range.overlaps (track.range))
|
if (day_range.overlaps (track))
|
||||||
{
|
{
|
||||||
Interval clipped = clip (track, day_range);
|
Interval clipped = clip (track, day_range);
|
||||||
if (track.range.is_open ())
|
if (track.is_open ())
|
||||||
clipped.range.end = Datetime ();
|
clipped.end = Datetime ();
|
||||||
|
|
||||||
if (clipped.range.start.hour () < first_hour)
|
if (clipped.start.hour () < first_hour)
|
||||||
first_hour = clipped.range.start.hour ();
|
first_hour = clipped.start.hour ();
|
||||||
|
|
||||||
if (! clipped.range.is_open () &&
|
if (! clipped.is_open () &&
|
||||||
clipped.range.end.hour () > last_hour)
|
clipped.end.hour () > last_hour)
|
||||||
last_hour = clipped.range.end.hour ();
|
last_hour = clipped.end.hour ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -521,28 +521,28 @@ static void renderInterval (
|
||||||
|
|
||||||
// Ignore any track that doesn't overlap with day.
|
// Ignore any track that doesn't overlap with day.
|
||||||
auto day_range = getFullDay (day);
|
auto day_range = getFullDay (day);
|
||||||
if (!day_range.overlaps (track.range) ||
|
if (! day_range.overlaps (track) ||
|
||||||
(track.range.is_open () && day > now))
|
(track.is_open () && day > now))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If the track is open and day is today, then closed the track now, otherwise
|
// If the track is open and day is today, then closed the track now, otherwise
|
||||||
// it will be rendered until midnight.
|
// it will be rendered until midnight.
|
||||||
Interval clipped = clip (track, day_range);
|
Interval clipped = clip (track, day_range);
|
||||||
if (track.range.is_open ())
|
if (track.is_open ())
|
||||||
{
|
{
|
||||||
if (day_range.start.sameDay (now))
|
if (day_range.start.sameDay (now))
|
||||||
clipped.range.end = now;
|
clipped.end = now;
|
||||||
else
|
else
|
||||||
clipped.range.end = day_range.end;
|
clipped.end = day_range.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto start_mins = (clipped.range.start.hour () - first_hour) * 60 + clipped.range.start.minute ();
|
auto start_mins = (clipped.start.hour () - first_hour) * 60 + clipped.start.minute ();
|
||||||
auto end_mins = (clipped.range.end.hour () - first_hour) * 60 + clipped.range.end.minute ();
|
auto end_mins = (clipped.end.hour () - first_hour) * 60 + clipped.end.minute ();
|
||||||
|
|
||||||
if (clipped.range.end.hour () == 0)
|
if (clipped.end.hour () == 0)
|
||||||
end_mins += (clipped.range.end.day() + (clipped.range.end.month () - clipped.range.start.month () - 1) * clipped.range.start.day ()) * 24 * 60;
|
end_mins += (clipped.end.day() + (clipped.end.month () - clipped.start.month () - 1) * clipped.start.day ()) * 24 * 60;
|
||||||
|
|
||||||
work = clipped.range.total ();
|
work = clipped.total ();
|
||||||
|
|
||||||
auto start_block = quantizeToNMinutes (start_mins, cell) / cell;
|
auto start_block = quantizeToNMinutes (start_mins, cell) / cell;
|
||||||
auto end_block = quantizeToNMinutes (end_mins == start_mins ? start_mins + 60 : end_mins, cell) / cell;
|
auto end_block = quantizeToNMinutes (end_mins == start_mins ? start_mins + 60 : end_mins, cell) / cell;
|
||||||
|
@ -584,7 +584,7 @@ static void renderInterval (
|
||||||
|
|
||||||
// An open interval gets a "..." in the bottom right corner, or
|
// An open interval gets a "..." in the bottom right corner, or
|
||||||
// whatever fits.
|
// whatever fits.
|
||||||
if (track.range.is_open ())
|
if (track.is_open ())
|
||||||
lines.back ().add ("+", start_offset + width - 1, colorTrack);
|
lines.back ().add ("+", start_offset + width - 1, colorTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -608,8 +608,8 @@ std::string renderHolidays (
|
||||||
auto date = entry.substr (last_dot + 1);
|
auto date = entry.substr (last_dot + 1);
|
||||||
std::replace (date.begin (), date.end (), '_', '-');
|
std::replace (date.begin (), date.end (), '_', '-');
|
||||||
Datetime holiday (date);
|
Datetime holiday (date);
|
||||||
if (holiday >= filter.range.start &&
|
if (holiday >= filter.start &&
|
||||||
holiday <= filter.range.end)
|
holiday <= filter.end)
|
||||||
{
|
{
|
||||||
out << Datetime (date).toString ("Y-M-D")
|
out << Datetime (date).toString ("Y-M-D")
|
||||||
<< " ["
|
<< " ["
|
||||||
|
@ -640,26 +640,26 @@ static std::string renderSummary (
|
||||||
{
|
{
|
||||||
time_t total_unavailable = 0;
|
time_t total_unavailable = 0;
|
||||||
for (auto& exclusion : exclusions)
|
for (auto& exclusion : exclusions)
|
||||||
if (filter.range.overlaps (exclusion))
|
if (filter.overlaps (exclusion))
|
||||||
total_unavailable += filter.range.intersect (exclusion).total ();
|
total_unavailable += filter.intersect (exclusion).total ();
|
||||||
|
|
||||||
time_t total_worked = 0;
|
time_t total_worked = 0;
|
||||||
if (! blank)
|
if (! blank)
|
||||||
{
|
{
|
||||||
for (auto& interval : tracked)
|
for (auto& interval : tracked)
|
||||||
{
|
{
|
||||||
if (filter.range.overlaps (interval.range))
|
if (filter.overlaps (interval))
|
||||||
{
|
{
|
||||||
Interval clipped = clip (interval, filter.range);
|
Interval clipped = clip (interval, filter);
|
||||||
if (interval.range.is_open ())
|
if (interval.is_open ())
|
||||||
clipped.range.end = Datetime ();
|
clipped.end = Datetime ();
|
||||||
|
|
||||||
total_worked += clipped.range.total ();
|
total_worked += clipped.total ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto total_available = filter.range.total () - total_unavailable;
|
auto total_available = filter.total () - total_unavailable;
|
||||||
assert (total_available >= 0);
|
assert (total_available >= 0);
|
||||||
auto total_remaining = total_available - total_worked;
|
auto total_remaining = total_available - total_worked;
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ int CmdContinue (
|
||||||
if (latest.empty ())
|
if (latest.empty ())
|
||||||
throw std::string ("There is no previous tracking to continue.");
|
throw std::string ("There is no previous tracking to continue.");
|
||||||
|
|
||||||
if (latest.range.is_open ())
|
if (latest.is_open ())
|
||||||
throw std::string ("There is already active tracking.");
|
throw std::string ("There is already active tracking.");
|
||||||
|
|
||||||
to_copy = latest;
|
to_copy = latest;
|
||||||
|
@ -75,10 +75,10 @@ int CmdContinue (
|
||||||
|
|
||||||
database.startTransaction ();
|
database.startTransaction ();
|
||||||
|
|
||||||
if (filter.range.start.toEpoch () != 0)
|
if (filter.start.toEpoch () != 0)
|
||||||
{
|
{
|
||||||
start_time = filter.range.start;
|
start_time = filter.start;
|
||||||
end_time = filter.range.end;
|
end_time = filter.end;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -86,13 +86,13 @@ int CmdContinue (
|
||||||
end_time = 0;
|
end_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (latest.range.is_open ())
|
if (latest.is_open ())
|
||||||
{
|
{
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
|
|
||||||
// Stop it, at the given start time, if applicable.
|
// Stop it, at the given start time, if applicable.
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
modified.range.end = start_time;
|
modified.end = start_time;
|
||||||
|
|
||||||
// Update database.
|
// Update database.
|
||||||
database.deleteInterval (latest);
|
database.deleteInterval (latest);
|
||||||
|
@ -106,8 +106,8 @@ int CmdContinue (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an identical interval and update the DB.
|
// Create an identical interval and update the DB.
|
||||||
to_copy.range.start = start_time;
|
to_copy.start = start_time;
|
||||||
to_copy.range.end = end_time;
|
to_copy.end = end_time;
|
||||||
|
|
||||||
validate (cli, rules, database, to_copy);
|
validate (cli, rules, database, to_copy);
|
||||||
database.addInterval (to_copy, rules.getBoolean ("verbose"));
|
database.addInterval (to_copy, rules.getBoolean ("verbose"));
|
||||||
|
|
|
@ -35,7 +35,7 @@ int CmdDefault (Rules& rules, Database& database)
|
||||||
// Load the most recent interval, summarize and display.
|
// Load the most recent interval, summarize and display.
|
||||||
auto interval = getLatestInterval (database);
|
auto interval = getLatestInterval (database);
|
||||||
|
|
||||||
if (interval.range.is_open ())
|
if (interval.is_open ())
|
||||||
{
|
{
|
||||||
if (rules.getBoolean ("verbose"))
|
if (rules.getBoolean ("verbose"))
|
||||||
{
|
{
|
||||||
|
|
|
@ -57,7 +57,7 @@ int CmdDelete (
|
||||||
if (tracked[tracked.size() - id].synthetic && dirty)
|
if (tracked[tracked.size() - id].synthetic && dirty)
|
||||||
{
|
{
|
||||||
auto latest = getLatestInterval(database);
|
auto latest = getLatestInterval(database);
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
|
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
|
|
||||||
|
|
|
@ -39,12 +39,12 @@ int CmdGaps (
|
||||||
{
|
{
|
||||||
// If filter is empty, choose 'today'.
|
// If filter is empty, choose 'today'.
|
||||||
auto filter = getFilter (cli);
|
auto filter = getFilter (cli);
|
||||||
if (! filter.range.is_started ())
|
if (! filter.is_started ())
|
||||||
{
|
{
|
||||||
if (rules.has ("reports.gaps.range"))
|
if (rules.has ("reports.gaps.range"))
|
||||||
expandIntervalHint (rules.get ("reports.gaps.range"), filter.range);
|
expandIntervalHint (rules.get ("reports.gaps.range"), filter);
|
||||||
else
|
else
|
||||||
filter.range = Range (Datetime ("today"), Datetime ("tomorrow"));
|
filter.setRange (Range (Datetime ("today"), Datetime ("tomorrow")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the :blank hint being used?
|
// Is the :blank hint being used?
|
||||||
|
@ -52,7 +52,7 @@ int CmdGaps (
|
||||||
|
|
||||||
std::vector <Range> untracked;
|
std::vector <Range> untracked;
|
||||||
if (blank)
|
if (blank)
|
||||||
untracked = subtractRanges ({filter.range}, getAllExclusions (rules, filter.range));
|
untracked = subtractRanges ({filter}, getAllExclusions (rules, filter));
|
||||||
else
|
else
|
||||||
untracked = getUntracked (database, rules, filter);
|
untracked = getUntracked (database, rules, filter);
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ int CmdGaps (
|
||||||
// Each day is rendered separately.
|
// Each day is rendered separately.
|
||||||
time_t grand_total = 0;
|
time_t grand_total = 0;
|
||||||
Datetime previous;
|
Datetime previous;
|
||||||
for (Datetime day = filter.range.start; day < filter.range.end; day++)
|
for (Datetime day = filter.start; day < filter.end; day++)
|
||||||
{
|
{
|
||||||
auto day_range = getFullDay (day);
|
auto day_range = getFullDay (day);
|
||||||
time_t daily_total = 0;
|
time_t daily_total = 0;
|
||||||
|
|
|
@ -72,7 +72,7 @@ int CmdJoin (
|
||||||
// TODO Require confirmation if tags don't match.
|
// TODO Require confirmation if tags don't match.
|
||||||
|
|
||||||
auto combined = second;
|
auto combined = second;
|
||||||
combined.range.end = first.range.end;
|
combined.end = first.end;
|
||||||
database.deleteInterval (first);
|
database.deleteInterval (first);
|
||||||
database.deleteInterval (second);
|
database.deleteInterval (second);
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ int CmdLengthen (
|
||||||
if (tracked[tracked.size () - id].synthetic && dirty)
|
if (tracked[tracked.size () - id].synthetic && dirty)
|
||||||
{
|
{
|
||||||
auto latest = getLatestInterval (database);
|
auto latest = getLatestInterval (database);
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
|
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
|
|
||||||
|
@ -90,13 +90,13 @@ int CmdLengthen (
|
||||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||||
|
|
||||||
Interval i = tracked[tracked.size () - id];
|
Interval i = tracked[tracked.size () - id];
|
||||||
if (i.range.is_open ())
|
if (i.is_open ())
|
||||||
throw format ("Cannot lengthen open interval @{1}", id);
|
throw format ("Cannot lengthen open interval @{1}", id);
|
||||||
|
|
||||||
database.deleteInterval (tracked[tracked.size () - id]);
|
database.deleteInterval (tracked[tracked.size () - id]);
|
||||||
|
|
||||||
Duration dur (delta);
|
Duration dur (delta);
|
||||||
i.range.end += dur.toTime_t ();
|
i.end += dur.toTime_t ();
|
||||||
validate (cli, rules, database, i);
|
validate (cli, rules, database, i);
|
||||||
database.addInterval (i, rules.getBoolean ("verbose"));
|
database.addInterval (i, rules.getBoolean ("verbose"));
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ int CmdMove (
|
||||||
if (tracked[tracked.size() - id].synthetic)
|
if (tracked[tracked.size() - id].synthetic)
|
||||||
{
|
{
|
||||||
auto latest = getLatestInterval(database);
|
auto latest = getLatestInterval(database);
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
|
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
|
|
||||||
|
@ -89,19 +89,19 @@ int CmdMove (
|
||||||
|
|
||||||
// Changing the start date should also change the end date by the same
|
// Changing the start date should also change the end date by the same
|
||||||
// amount.
|
// amount.
|
||||||
if (i.range.start < start)
|
if (i.start < start)
|
||||||
{
|
{
|
||||||
auto delta = start - i.range.start;
|
auto delta = start - i.start;
|
||||||
i.range.start = start;
|
i.start = start;
|
||||||
if (! i.range.is_open ())
|
if (! i.is_open ())
|
||||||
i.range.end += delta;
|
i.end += delta;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto delta = i.range.start - start;
|
auto delta = i.start - start;
|
||||||
i.range.start = start;
|
i.start = start;
|
||||||
if (! i.range.is_open ())
|
if (! i.is_open ())
|
||||||
i.range.end -= delta;
|
i.end -= delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
database.deleteInterval (tracked[tracked.size () - id]);
|
database.deleteInterval (tracked[tracked.size () - id]);
|
||||||
|
@ -112,7 +112,7 @@ int CmdMove (
|
||||||
database.endTransaction ();
|
database.endTransaction ();
|
||||||
|
|
||||||
if (rules.getBoolean ("verbose"))
|
if (rules.getBoolean ("verbose"))
|
||||||
std::cout << "Moved @" << id << " to " << i.range.start.toISOLocalExtended () << '\n';
|
std::cout << "Moved @" << id << " to " << i.start.toISOLocalExtended () << '\n';
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,8 +82,8 @@ int CmdReport (
|
||||||
auto filter = getFilter (cli);
|
auto filter = getFilter (cli);
|
||||||
auto tracked = getTracked (database, rules, filter);
|
auto tracked = getTracked (database, rules, filter);
|
||||||
|
|
||||||
rules.set ("temp.report.start", filter.range.start.toEpoch () > 0 ? filter.range.start.toISO () : "");
|
rules.set ("temp.report.start", filter.start.toEpoch () > 0 ? filter.start.toISO () : "");
|
||||||
rules.set ("temp.report.end", filter.range.end.toEpoch () > 0 ? filter.range.end.toISO () : "");
|
rules.set ("temp.report.end", filter.end.toEpoch () > 0 ? filter.end.toISO () : "");
|
||||||
rules.set ("temp.report.tags", joinQuotedIfNeeded (",", filter.tags ()));
|
rules.set ("temp.report.tags", joinQuotedIfNeeded (",", filter.tags ()));
|
||||||
rules.set ("temp.version", VERSION);
|
rules.set ("temp.version", VERSION);
|
||||||
|
|
||||||
|
|
|
@ -65,13 +65,13 @@ int CmdResize (
|
||||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||||
|
|
||||||
Interval i = tracked[tracked.size () - id];
|
Interval i = tracked[tracked.size () - id];
|
||||||
if (i.range.is_open ())
|
if (i.is_open ())
|
||||||
throw format ("Cannot resize open interval @{1}", id);
|
throw format ("Cannot resize open interval @{1}", id);
|
||||||
|
|
||||||
Duration dur (delta);
|
Duration dur (delta);
|
||||||
database.deleteInterval (tracked[tracked.size () - id]);
|
database.deleteInterval (tracked[tracked.size () - id]);
|
||||||
|
|
||||||
i.range.end = i.range.start + dur.toTime_t ();
|
i.end = i.start + dur.toTime_t ();
|
||||||
validate (cli, rules, database, i);
|
validate (cli, rules, database, i);
|
||||||
database.addInterval (i, rules.getBoolean ("verbose"));
|
database.addInterval (i, rules.getBoolean ("verbose"));
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ int CmdShorten (
|
||||||
if (tracked[tracked.size() - id].synthetic && dirty)
|
if (tracked[tracked.size() - id].synthetic && dirty)
|
||||||
{
|
{
|
||||||
auto latest = getLatestInterval(database);
|
auto latest = getLatestInterval(database);
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
|
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
|
|
||||||
|
@ -88,16 +88,16 @@ int CmdShorten (
|
||||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||||
|
|
||||||
Interval i = tracked[tracked.size () - id];
|
Interval i = tracked[tracked.size () - id];
|
||||||
if (i.range.is_open ())
|
if (i.is_open ())
|
||||||
throw format ("Cannot shorten open interval @{1}", id);
|
throw format ("Cannot shorten open interval @{1}", id);
|
||||||
|
|
||||||
Duration dur (delta);
|
Duration dur (delta);
|
||||||
if (dur > (i.range.end - i.range.start))
|
if (dur > (i.end - i.start))
|
||||||
throw format ("Cannot shorten interval @{1} by {2} because it is only {3} in length.", id, dur.formatHours (), Duration (i.range.end - i.range.start).formatHours ());
|
throw format ("Cannot shorten interval @{1} by {2} because it is only {3} in length.", id, dur.formatHours (), Duration (i.end - i.start).formatHours ());
|
||||||
|
|
||||||
database.deleteInterval (tracked[tracked.size () - id]);
|
database.deleteInterval (tracked[tracked.size () - id]);
|
||||||
|
|
||||||
i.range.end -= dur.toTime_t ();
|
i.end -= dur.toTime_t ();
|
||||||
validate (cli, rules, database, i);
|
validate (cli, rules, database, i);
|
||||||
database.addInterval (i, rules.getBoolean ("verbose"));
|
database.addInterval (i, rules.getBoolean ("verbose"));
|
||||||
|
|
||||||
|
|
|
@ -59,19 +59,19 @@ int CmdSplit (
|
||||||
Interval first = tracked[tracked.size () - id];
|
Interval first = tracked[tracked.size () - id];
|
||||||
Interval second = first;
|
Interval second = first;
|
||||||
|
|
||||||
if (first.range.is_open ())
|
if (first.is_open ())
|
||||||
{
|
{
|
||||||
Datetime midpoint;
|
Datetime midpoint;
|
||||||
midpoint -= (midpoint - first.range.start) / 2;
|
midpoint -= (midpoint - first.start) / 2;
|
||||||
first.range.end = midpoint;
|
first.end = midpoint;
|
||||||
second.range.start = midpoint;
|
second.start = midpoint;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Datetime midpoint = first.range.start;
|
Datetime midpoint = first.start;
|
||||||
midpoint += (first.range.end - first.range.start) / 2;
|
midpoint += (first.end - first.start) / 2;
|
||||||
first.range.end = midpoint;
|
first.end = midpoint;
|
||||||
second.range.start = midpoint;
|
second.start = midpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
database.deleteInterval (tracked[tracked.size () - id]);
|
database.deleteInterval (tracked[tracked.size () - id]);
|
||||||
|
|
|
@ -41,7 +41,7 @@ int CmdStart (
|
||||||
database.startTransaction ();
|
database.startTransaction ();
|
||||||
|
|
||||||
// If the latest interval is open, close it.
|
// If the latest interval is open, close it.
|
||||||
if (latest.range.is_open ())
|
if (latest.is_open ())
|
||||||
{
|
{
|
||||||
// If the new interval tags match those of the currently open interval, then
|
// If the new interval tags match those of the currently open interval, then
|
||||||
// do nothing - the tags are already being tracked.
|
// do nothing - the tags are already being tracked.
|
||||||
|
@ -55,16 +55,16 @@ int CmdStart (
|
||||||
|
|
||||||
// Stop it, at the given start time, if applicable.
|
// Stop it, at the given start time, if applicable.
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
if (filter.range.start.toEpoch () != 0)
|
if (filter.start.toEpoch () != 0)
|
||||||
modified.range.end = filter.range.start;
|
modified.end = filter.start;
|
||||||
else
|
else
|
||||||
modified.range.end = Datetime ();
|
modified.end = Datetime ();
|
||||||
|
|
||||||
// Update database.
|
// Update database.
|
||||||
database.deleteInterval (latest);
|
database.deleteInterval (latest);
|
||||||
validate (cli, rules, database, modified);
|
validate (cli, rules, database, modified);
|
||||||
|
|
||||||
for (auto& interval : flatten (modified, getAllExclusions (rules, modified.range)))
|
for (auto& interval : flatten (modified, getAllExclusions (rules, modified)))
|
||||||
{
|
{
|
||||||
database.addInterval (interval, rules.getBoolean ("verbose"));
|
database.addInterval (interval, rules.getBoolean ("verbose"));
|
||||||
|
|
||||||
|
@ -75,10 +75,10 @@ int CmdStart (
|
||||||
|
|
||||||
// Now add the new open interval.
|
// Now add the new open interval.
|
||||||
Interval now;
|
Interval now;
|
||||||
if (filter.range.start.toEpoch () != 0)
|
if (filter.start.toEpoch () != 0)
|
||||||
now.range.start = filter.range.start;
|
now.start = filter.start;
|
||||||
else
|
else
|
||||||
now.range.start = Datetime ();
|
now.start = Datetime ();
|
||||||
|
|
||||||
for (auto& tag : filter.tags ())
|
for (auto& tag : filter.tags ())
|
||||||
now.tag (tag);
|
now.tag (tag);
|
||||||
|
|
|
@ -51,32 +51,32 @@ int CmdStop (
|
||||||
auto latest = getLatestInterval (database);
|
auto latest = getLatestInterval (database);
|
||||||
|
|
||||||
// Verify the interval is open.
|
// Verify the interval is open.
|
||||||
if (! latest.range.is_open ())
|
if (! latest.is_open ())
|
||||||
throw std::string ("There is no active time tracking.");
|
throw std::string ("There is no active time tracking.");
|
||||||
|
|
||||||
database.startTransaction ();
|
database.startTransaction ();
|
||||||
|
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
|
|
||||||
// If a stop date is specified (and occupies filter.range.start) then use
|
// If a stop date is specified (and occupies filter.start) then use
|
||||||
// that instead of the current time.
|
// that instead of the current time.
|
||||||
if (filter.range.start.toEpoch () != 0)
|
if (filter.start.toEpoch () != 0)
|
||||||
{
|
{
|
||||||
if (modified.range.start >= filter.range.start)
|
if (modified.start >= filter.start)
|
||||||
throw std::string ("The end of a date range must be after the start.");
|
throw std::string ("The end of a date range must be after the start.");
|
||||||
|
|
||||||
modified.range.end = filter.range.start;
|
modified.end = filter.start;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
modified.range.end = Datetime ();
|
modified.end = Datetime ();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the interval.
|
// Close the interval.
|
||||||
database.deleteInterval (latest);
|
database.deleteInterval (latest);
|
||||||
validate (cli, rules, database, modified);
|
validate (cli, rules, database, modified);
|
||||||
|
|
||||||
for (auto& interval : flatten (modified, getAllExclusions (rules, modified.range)))
|
for (auto& interval : flatten (modified, getAllExclusions (rules, modified)))
|
||||||
{
|
{
|
||||||
database.addInterval (interval, rules.getBoolean ("verbose"));
|
database.addInterval (interval, rules.getBoolean ("verbose"));
|
||||||
|
|
||||||
|
@ -100,8 +100,8 @@ int CmdStop (
|
||||||
if (! filter.tags ().empty () &&
|
if (! filter.tags ().empty () &&
|
||||||
modified.tags ().size () != latest.tags ().size ())
|
modified.tags ().size () != latest.tags ().size ())
|
||||||
{
|
{
|
||||||
modified.range.start = modified.range.end;
|
modified.start = modified.end;
|
||||||
modified.range.end = {0};
|
modified.end = {0};
|
||||||
validate (cli, rules, database, modified);
|
validate (cli, rules, database, modified);
|
||||||
database.addInterval (modified, rules.getBoolean ("verbose"));
|
database.addInterval (modified, rules.getBoolean ("verbose"));
|
||||||
if (rules.getBoolean ("verbose"))
|
if (rules.getBoolean ("verbose"))
|
||||||
|
|
|
@ -43,11 +43,11 @@ int CmdSummary (
|
||||||
{
|
{
|
||||||
// Create a filter, and if empty, choose 'today'.
|
// Create a filter, and if empty, choose 'today'.
|
||||||
auto filter = getFilter (cli);
|
auto filter = getFilter (cli);
|
||||||
if (! filter.range.is_started ())
|
if (! filter.is_started ())
|
||||||
filter.range = Range (Datetime ("today"), Datetime ("tomorrow"));
|
filter.setRange (Range (Datetime ("today"), Datetime ("tomorrow")));
|
||||||
|
|
||||||
if (! filter.range.is_ended())
|
if (! filter.is_ended())
|
||||||
filter.range.end = filter.range.start + Duration("1d").toTime_t();
|
filter.end = filter.start + Duration("1d").toTime_t();
|
||||||
|
|
||||||
// Load the data.
|
// Load the data.
|
||||||
auto tracked = getTracked (database, rules, filter);
|
auto tracked = getTracked (database, rules, filter);
|
||||||
|
@ -58,11 +58,11 @@ int CmdSummary (
|
||||||
{
|
{
|
||||||
std::cout << "No filtered data found";
|
std::cout << "No filtered data found";
|
||||||
|
|
||||||
if (filter.range.is_started ())
|
if (filter.is_started ())
|
||||||
{
|
{
|
||||||
std::cout << " in the range " << filter.range.start.toISOLocalExtended ();
|
std::cout << " in the range " << filter.start.toISOLocalExtended ();
|
||||||
if (filter.range.is_ended ())
|
if (filter.is_ended ())
|
||||||
std::cout << " - " << filter.range.end.toISOLocalExtended ();
|
std::cout << " - " << filter.end.toISOLocalExtended ();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! filter.tags ().empty ())
|
if (! filter.tags ().empty ())
|
||||||
|
@ -102,7 +102,7 @@ int CmdSummary (
|
||||||
// Each day is rendered separately.
|
// Each day is rendered separately.
|
||||||
time_t grand_total = 0;
|
time_t grand_total = 0;
|
||||||
Datetime previous;
|
Datetime previous;
|
||||||
for (Datetime day = filter.range.start; day < filter.range.end; day++)
|
for (Datetime day = filter.start; day < filter.end; day++)
|
||||||
{
|
{
|
||||||
auto day_range = getFullDay (day);
|
auto day_range = getFullDay (day);
|
||||||
time_t daily_total = 0;
|
time_t daily_total = 0;
|
||||||
|
@ -111,7 +111,7 @@ int CmdSummary (
|
||||||
for (auto& track : subset (day_range, tracked))
|
for (auto& track : subset (day_range, tracked))
|
||||||
{
|
{
|
||||||
// Make sure the track only represents one day.
|
// Make sure the track only represents one day.
|
||||||
if ((track.range.is_open () && day > Datetime ()))
|
if ((track.is_open () && day > Datetime ()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
row = table.addRow ();
|
row = table.addRow ();
|
||||||
|
@ -125,8 +125,8 @@ int CmdSummary (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersect track with day.
|
// Intersect track with day.
|
||||||
auto today = day_range.intersect (track.range);
|
auto today = day_range.intersect (track);
|
||||||
if (track.range.is_open () && day <= Datetime () && today.end > Datetime ())
|
if (track.is_open () && day <= Datetime () && today.end > Datetime ())
|
||||||
today.end = Datetime ();
|
today.end = Datetime ();
|
||||||
|
|
||||||
std::string tags = join(", ", track.tags());
|
std::string tags = join(", ", track.tags());
|
||||||
|
@ -136,7 +136,7 @@ int CmdSummary (
|
||||||
|
|
||||||
table.set (row, (ids ? 4 : 3), tags);
|
table.set (row, (ids ? 4 : 3), tags);
|
||||||
table.set (row, (ids ? 5 : 4), today.start.toString ("h:N:S"));
|
table.set (row, (ids ? 5 : 4), today.start.toString ("h:N:S"));
|
||||||
table.set (row, (ids ? 6 : 5), (track.range.is_open () ? "-" : today.end.toString ("h:N:S")));
|
table.set (row, (ids ? 6 : 5), (track.is_open () ? "-" : today.end.toString ("h:N:S")));
|
||||||
table.set (row, (ids ? 7 : 6), Duration (today.total ()).formatHours ());
|
table.set (row, (ids ? 7 : 6), Duration (today.total ()).formatHours ());
|
||||||
|
|
||||||
daily_total += today.total ();
|
daily_total += today.total ();
|
||||||
|
|
|
@ -63,7 +63,7 @@ int CmdTag (
|
||||||
if (tracked[tracked.size() - id].synthetic && dirty)
|
if (tracked[tracked.size() - id].synthetic && dirty)
|
||||||
{
|
{
|
||||||
auto latest = getLatestInterval(database);
|
auto latest = getLatestInterval(database);
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
|
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ int CmdTag (
|
||||||
throw std::string ("There is no active time tracking.");
|
throw std::string ("There is no active time tracking.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tracked.back ().range.is_open ())
|
if (!tracked.back ().is_open ())
|
||||||
{
|
{
|
||||||
throw std::string ("At least one ID must be specified. See 'timew help tag'.");
|
throw std::string ("At least one ID must be specified. See 'timew help tag'.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ int CmdTrack (
|
||||||
|
|
||||||
// If this is not a proper closed interval, then the user is trying to make
|
// If this is not a proper closed interval, then the user is trying to make
|
||||||
// the 'track' command behave like 'start', so delegate to CmdStart.
|
// the 'track' command behave like 'start', so delegate to CmdStart.
|
||||||
if (! filter.range.is_started () ||
|
if (! filter.is_started () ||
|
||||||
! filter.range.is_ended ())
|
! filter.is_ended ())
|
||||||
return CmdStart (cli, rules, database);
|
return CmdStart (cli, rules, database);
|
||||||
|
|
||||||
database.startTransaction ();
|
database.startTransaction ();
|
||||||
|
@ -47,7 +47,7 @@ int CmdTrack (
|
||||||
// Validation must occur before flattening.
|
// Validation must occur before flattening.
|
||||||
validate (cli, rules, database, filter);
|
validate (cli, rules, database, filter);
|
||||||
|
|
||||||
for (auto& interval : flatten (filter, getAllExclusions (rules, filter.range)))
|
for (auto& interval : flatten (filter, getAllExclusions (rules, filter)))
|
||||||
{
|
{
|
||||||
database.addInterval (interval, rules.getBoolean ("verbose"));
|
database.addInterval (interval, rules.getBoolean ("verbose"));
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ int CmdUntag (
|
||||||
if (tracked[tracked.size() - id].synthetic && dirty)
|
if (tracked[tracked.size() - id].synthetic && dirty)
|
||||||
{
|
{
|
||||||
auto latest = getLatestInterval(database);
|
auto latest = getLatestInterval(database);
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
|
|
||||||
Interval modified {latest};
|
Interval modified {latest};
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ int CmdUntag (
|
||||||
throw std::string ("There is no active time tracking.");
|
throw std::string ("There is no active time tracking.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tracked.back ().range.is_open ())
|
if (!tracked.back ().is_open ())
|
||||||
{
|
{
|
||||||
throw std::string ("At least one ID must be specified. See 'timew help tag'.");
|
throw std::string ("At least one ID must be specified. See 'timew help tag'.");
|
||||||
}
|
}
|
||||||
|
|
105
src/data.cpp
105
src/data.cpp
|
@ -117,7 +117,7 @@ Interval getFilter (const CLI& cli)
|
||||||
if (args.size () == 1 &&
|
if (args.size () == 1 &&
|
||||||
args[0] == "<date>")
|
args[0] == "<date>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start), 0};
|
filter.setRange ({Datetime (start), 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
// from <date>
|
// from <date>
|
||||||
|
@ -125,7 +125,7 @@ Interval getFilter (const CLI& cli)
|
||||||
args[0] == "from" &&
|
args[0] == "from" &&
|
||||||
args[1] == "<date>")
|
args[1] == "<date>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start), 0};
|
filter.setRange ({Datetime (start), 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <date> to/- <date>
|
// <date> to/- <date>
|
||||||
|
@ -134,7 +134,7 @@ Interval getFilter (const CLI& cli)
|
||||||
(args[1] == "to" || args[1] == "-") &&
|
(args[1] == "to" || args[1] == "-") &&
|
||||||
args[2] == "<date>")
|
args[2] == "<date>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start), Datetime (end)};
|
filter.setRange ({Datetime (start), Datetime (end)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// from <date> to/- <date>
|
// from <date> to/- <date>
|
||||||
|
@ -144,7 +144,7 @@ Interval getFilter (const CLI& cli)
|
||||||
(args[2] == "to" || args[2] == "-") &&
|
(args[2] == "to" || args[2] == "-") &&
|
||||||
args[3] == "<date>")
|
args[3] == "<date>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start), Datetime (end)};
|
filter.setRange ({Datetime (start), Datetime (end)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <date> for <duration>
|
// <date> for <duration>
|
||||||
|
@ -153,7 +153,7 @@ Interval getFilter (const CLI& cli)
|
||||||
args[1] == "for" &&
|
args[1] == "for" &&
|
||||||
args[2] == "<duration>")
|
args[2] == "<duration>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start), Datetime (start) + Duration (duration).toTime_t ()};
|
filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
|
||||||
}
|
}
|
||||||
|
|
||||||
// from <date> for <duration>
|
// from <date> for <duration>
|
||||||
|
@ -163,7 +163,7 @@ Interval getFilter (const CLI& cli)
|
||||||
args[2] == "for" &&
|
args[2] == "for" &&
|
||||||
args[3] == "<duration>")
|
args[3] == "<duration>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start), Datetime (start) + Duration (duration).toTime_t ()};
|
filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <duration> before <date>
|
// <duration> before <date>
|
||||||
|
@ -172,7 +172,7 @@ Interval getFilter (const CLI& cli)
|
||||||
args[1] == "before" &&
|
args[1] == "before" &&
|
||||||
args[2] == "<date>")
|
args[2] == "<date>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start) - Duration (duration).toTime_t (), Datetime (start)};
|
filter.setRange ({Datetime (start) - Duration (duration).toTime_t (), Datetime (start)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <duration> after <date>
|
// <duration> after <date>
|
||||||
|
@ -181,7 +181,7 @@ Interval getFilter (const CLI& cli)
|
||||||
args[1] == "after" &&
|
args[1] == "after" &&
|
||||||
args[2] == "<date>")
|
args[2] == "<date>")
|
||||||
{
|
{
|
||||||
filter.range = {Datetime (start), Datetime (start) + Duration (duration).toTime_t ()};
|
filter.setRange ({Datetime (start), Datetime (start) + Duration (duration).toTime_t ()});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <duration> ago
|
// <duration> ago
|
||||||
|
@ -189,7 +189,7 @@ Interval getFilter (const CLI& cli)
|
||||||
args[0] == "<duration>" &&
|
args[0] == "<duration>" &&
|
||||||
args[1] == "ago")
|
args[1] == "ago")
|
||||||
{
|
{
|
||||||
filter.range = {now - Duration (duration).toTime_t (), 0};
|
filter.setRange ({now - Duration (duration).toTime_t (), 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
// for <duration>
|
// for <duration>
|
||||||
|
@ -197,14 +197,14 @@ Interval getFilter (const CLI& cli)
|
||||||
args[0] == "for" &&
|
args[0] == "for" &&
|
||||||
args[1] == "<duration>")
|
args[1] == "<duration>")
|
||||||
{
|
{
|
||||||
filter.range = {now - Duration (duration).toTime_t (), now};
|
filter.setRange ({now - Duration (duration).toTime_t (), now});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <duration>
|
// <duration>
|
||||||
else if (args.size () == 1 &&
|
else if (args.size () == 1 &&
|
||||||
args[0] == "<duration>")
|
args[0] == "<duration>")
|
||||||
{
|
{
|
||||||
filter.range = {now - Duration (duration).toTime_t (), now};
|
filter.setRange ({now - Duration (duration).toTime_t (), now});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unrecognized date range construct.
|
// Unrecognized date range construct.
|
||||||
|
@ -213,10 +213,10 @@ Interval getFilter (const CLI& cli)
|
||||||
throw std::string ("Unrecognized date range: '") + join (" ", args) + "'.";
|
throw std::string ("Unrecognized date range: '") + join (" ", args) + "'.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.range.start > now)
|
if (filter.start > now)
|
||||||
throw std::string ("Time tracking cannot be set in the future.");
|
throw std::string ("Time tracking cannot be set in the future.");
|
||||||
|
|
||||||
if (filter.range.end != 0 && filter.range.start > filter.range.end)
|
if (filter.end != 0 && filter.start > filter.end)
|
||||||
throw std::string ("The end of a date range must be after the start.");
|
throw std::string ("The end of a date range must be after the start.");
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
|
@ -359,7 +359,8 @@ std::vector <Interval> subset (
|
||||||
{
|
{
|
||||||
std::vector <Interval> all;
|
std::vector <Interval> all;
|
||||||
for (auto& interval : intervals) {
|
for (auto& interval : intervals) {
|
||||||
if (range.intersects (interval.range)) {
|
if (range.intersects (interval))
|
||||||
|
{
|
||||||
all.push_back (interval);
|
all.push_back (interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,29 +377,29 @@ std::vector <Interval> flatten (
|
||||||
|
|
||||||
std::vector <Range> enclosed;
|
std::vector <Range> enclosed;
|
||||||
for (auto& e : exclusions)
|
for (auto& e : exclusions)
|
||||||
if (interval.range.encloses (e))
|
if (interval.encloses (e))
|
||||||
enclosed.push_back (e);
|
enclosed.push_back (e);
|
||||||
|
|
||||||
Datetime now;
|
Datetime now;
|
||||||
for (auto& result : subtractRanges ({interval.range}, enclosed))
|
for (auto& result : subtractRanges ({interval}, enclosed))
|
||||||
{
|
{
|
||||||
Interval chunk {interval};
|
Interval chunk {interval};
|
||||||
chunk.range = result;
|
chunk.setRange (result);
|
||||||
|
|
||||||
// Only historical data is included.
|
// Only historical data is included.
|
||||||
if (chunk.range.start <= now)
|
if (chunk.start <= now)
|
||||||
{
|
{
|
||||||
// Closed chunk ranges in the future need to be adjusted.
|
// Closed chunk ranges in the future need to be adjusted.
|
||||||
if (! chunk.range.is_open () &&
|
if (! chunk.is_open () &&
|
||||||
chunk.range.end > now)
|
chunk.end > now)
|
||||||
{
|
{
|
||||||
// If the interval is open, so must be chunk.
|
// If the interval is open, so must be chunk.
|
||||||
if (interval.range.is_open ())
|
if (interval.is_open ())
|
||||||
chunk.range.end = {0};
|
chunk.end = {0};
|
||||||
|
|
||||||
// Otherwise truncate to now.
|
// Otherwise truncate to now.
|
||||||
else
|
else
|
||||||
chunk.range.end = now;
|
chunk.end = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
all.push_back (chunk);
|
all.push_back (chunk);
|
||||||
|
@ -496,17 +497,17 @@ Range outerRange (const std::vector <Interval>& intervals)
|
||||||
Range outer;
|
Range outer;
|
||||||
for (auto& interval : intervals)
|
for (auto& interval : intervals)
|
||||||
{
|
{
|
||||||
if (interval.range.start < outer.start || outer.start.toEpoch () == 0)
|
if (interval.start < outer.start || outer.start.toEpoch () == 0)
|
||||||
outer.start = interval.range.start;
|
outer.start = interval.start;
|
||||||
|
|
||||||
// Deliberately mixed start/end.
|
// Deliberately mixed start/end.
|
||||||
if (interval.range.start > outer.end)
|
if (interval.start > outer.end)
|
||||||
outer.end = interval.range.start;
|
outer.end = interval.start;
|
||||||
|
|
||||||
if (interval.range.end > outer.end)
|
if (interval.end > outer.end)
|
||||||
outer.end = interval.range.end;
|
outer.end = interval.end;
|
||||||
|
|
||||||
if (! interval.range.is_ended ())
|
if (! interval.is_ended ())
|
||||||
outer.end = Datetime ();
|
outer.end = Datetime ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,10 +518,10 @@ Range outerRange (const std::vector <Interval>& intervals)
|
||||||
// An interval matches a filter interval if the start/end overlaps, and all
|
// An interval matches a filter interval if the start/end overlaps, and all
|
||||||
// filter interval tags are found in the interval.
|
// filter interval tags are found in the interval.
|
||||||
//
|
//
|
||||||
// [1] interval.range.end.toEpoch () == 0
|
// [1] interval.end.toEpoch () == 0
|
||||||
// [2] interval.range.end > filter.range.start
|
// [2] interval.end > filter.start
|
||||||
// [3] filter.range.end.toEpoch () == 0
|
// [3] filter.end.toEpoch () == 0
|
||||||
// [4] interval.range.start < filter.range.end
|
// [4] interval.start < filter.end
|
||||||
//
|
//
|
||||||
// Match: (1 || 2) && (3 || 4)
|
// Match: (1 || 2) && (3 || 4)
|
||||||
|
|
||||||
|
@ -552,9 +553,9 @@ Range outerRange (const std::vector <Interval>& intervals)
|
||||||
//
|
//
|
||||||
bool matchesFilter (const Interval& interval, const Interval& filter)
|
bool matchesFilter (const Interval& interval, const Interval& filter)
|
||||||
{
|
{
|
||||||
if ((filter.range.start.toEpoch () == 0 &&
|
if ((filter.start.toEpoch () == 0 &&
|
||||||
filter.range.end.toEpoch () == 0
|
filter.end.toEpoch () == 0
|
||||||
) || interval.range.intersects (filter.range))
|
) || interval.intersects (filter))
|
||||||
{
|
{
|
||||||
for (auto& tag : filter.tags ())
|
for (auto& tag : filter.tags ())
|
||||||
if (! interval.hasTag (tag))
|
if (! interval.hasTag (tag))
|
||||||
|
@ -567,7 +568,7 @@ bool matchesFilter (const Interval& interval, const Interval& filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Take an interval and clip it to the range.
|
// Take an interval and clip it to the
|
||||||
Interval clip (const Interval& interval, const Range& range)
|
Interval clip (const Interval& interval, const Range& range)
|
||||||
{
|
{
|
||||||
if (! range.is_started () ||
|
if (! range.is_started () ||
|
||||||
|
@ -575,13 +576,13 @@ Interval clip (const Interval& interval, const Range& range)
|
||||||
return interval;
|
return interval;
|
||||||
|
|
||||||
Interval clipped {interval};
|
Interval clipped {interval};
|
||||||
if (clipped.range.start.toEpoch () &&
|
if (clipped.start.toEpoch () &&
|
||||||
clipped.range.start < range.start)
|
clipped.start < range.start)
|
||||||
clipped.range.start = range.start;
|
clipped.start = range.start;
|
||||||
|
|
||||||
if (clipped.range.end.toEpoch () &&
|
if (clipped.end.toEpoch () &&
|
||||||
clipped.range.end > range.end)
|
clipped.end > range.end)
|
||||||
clipped.range.end = range.end;
|
clipped.end = range.end;
|
||||||
|
|
||||||
return clipped;
|
return clipped;
|
||||||
}
|
}
|
||||||
|
@ -600,16 +601,16 @@ std::vector <Interval> getTracked (
|
||||||
// [earliest start, infinity)
|
// [earliest start, infinity)
|
||||||
//
|
//
|
||||||
// Avoid assigning a zero-width range - leave it unstarted instead.
|
// Avoid assigning a zero-width range - leave it unstarted instead.
|
||||||
if (! filter.range.is_started ())
|
if (! filter.is_started ())
|
||||||
{
|
{
|
||||||
auto outer = outerRange (inclusions);
|
auto outer = outerRange (inclusions);
|
||||||
if (outer.total ())
|
if (outer.total ())
|
||||||
filter.range = outer;
|
filter.setRange (outer);
|
||||||
|
|
||||||
// Use an infinite range instead of the last end date; this prevents
|
// Use an infinite range instead of the last end date; this prevents
|
||||||
// issues when there is an empty range [q, q) at the end of a filter
|
// issues when there is an empty range [q, q) at the end of a filter
|
||||||
// [p, q), in which case there is no overlap or intersection.
|
// [p, q), in which case there is no overlap or intersection.
|
||||||
filter.range.end = 0;
|
filter.end = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector <Interval> intervals = inclusions;
|
std::vector <Interval> intervals = inclusions;
|
||||||
|
@ -618,11 +619,11 @@ std::vector <Interval> getTracked (
|
||||||
{
|
{
|
||||||
auto latest = inclusions.back ();
|
auto latest = inclusions.back ();
|
||||||
|
|
||||||
if (latest.range.is_open ())
|
if (latest.is_open ())
|
||||||
{
|
{
|
||||||
// Get the set of expanded exclusions that overlap the range defined by the
|
// Get the set of expanded exclusions that overlap the range defined by the
|
||||||
// timeline.
|
// timeline.
|
||||||
auto exclusions = getAllExclusions (rules, filter.range);
|
auto exclusions = getAllExclusions (rules, filter);
|
||||||
if (! exclusions.empty ())
|
if (! exclusions.empty ())
|
||||||
{
|
{
|
||||||
intervals.pop_back ();
|
intervals.pop_back ();
|
||||||
|
@ -630,7 +631,7 @@ std::vector <Interval> getTracked (
|
||||||
for (auto& interval : flatten (latest, exclusions))
|
for (auto& interval : flatten (latest, exclusions))
|
||||||
{
|
{
|
||||||
if (latest.synthetic ||
|
if (latest.synthetic ||
|
||||||
latest.range != interval.range)
|
latest != interval)
|
||||||
interval.synthetic = true;
|
interval.synthetic = true;
|
||||||
|
|
||||||
intervals.push_back (interval);
|
intervals.push_back (interval);
|
||||||
|
@ -656,9 +657,9 @@ std::vector <Range> getUntracked (
|
||||||
{
|
{
|
||||||
std::vector <Range> inclusion_ranges;
|
std::vector <Range> inclusion_ranges;
|
||||||
for (auto& i : subset (filter, getAllInclusions (database)))
|
for (auto& i : subset (filter, getAllInclusions (database)))
|
||||||
inclusion_ranges.push_back (i.range);
|
inclusion_ranges.push_back (i);
|
||||||
|
|
||||||
auto available = subtractRanges ({filter.range}, getAllExclusions (rules, filter.range));
|
auto available = subtractRanges ({filter}, getAllExclusions (rules, filter));
|
||||||
auto untracked = subtractRanges (available, inclusion_ranges);
|
auto untracked = subtractRanges (available, inclusion_ranges);
|
||||||
debug (format ("Loaded {1} untracked ranges", untracked.size ()));
|
debug (format ("Loaded {1} untracked ranges", untracked.size ()));
|
||||||
return untracked;
|
return untracked;
|
||||||
|
|
22
src/dom.cpp
22
src/dom.cpp
|
@ -49,29 +49,29 @@ bool domGet (
|
||||||
// dom.active
|
// dom.active
|
||||||
if (pig.eos ())
|
if (pig.eos ())
|
||||||
{
|
{
|
||||||
value = latest.range.is_open () ? "1" : "0";
|
value = latest.is_open () ? "1" : "0";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dom.active.start
|
// dom.active.start
|
||||||
if (pig.skipLiteral (".start") &&
|
if (pig.skipLiteral (".start") &&
|
||||||
latest.range.is_open ())
|
latest.is_open ())
|
||||||
{
|
{
|
||||||
value = latest.range.start.toISOLocalExtended ();
|
value = latest.start.toISOLocalExtended ();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dom.active.duration
|
// dom.active.duration
|
||||||
if (pig.skipLiteral (".duration") &&
|
if (pig.skipLiteral (".duration") &&
|
||||||
latest.range.is_open ())
|
latest.is_open ())
|
||||||
{
|
{
|
||||||
value = Duration (latest.range.total ()).formatISO ();
|
value = Duration (latest.total ()).formatISO ();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dom.active.tag.count
|
// dom.active.tag.count
|
||||||
if (pig.skipLiteral (".tag.count") &&
|
if (pig.skipLiteral (".tag.count") &&
|
||||||
latest.range.is_open ())
|
latest.is_open ())
|
||||||
{
|
{
|
||||||
value = format ("{1}", latest.tags ().size ());
|
value = format ("{1}", latest.tags ().size ());
|
||||||
return true;
|
return true;
|
||||||
|
@ -79,7 +79,7 @@ bool domGet (
|
||||||
|
|
||||||
// dom.active.json
|
// dom.active.json
|
||||||
if (pig.skipLiteral (".json") &&
|
if (pig.skipLiteral (".json") &&
|
||||||
latest.range.is_open ())
|
latest.is_open ())
|
||||||
{
|
{
|
||||||
value = latest.json ();
|
value = latest.json ();
|
||||||
return true;
|
return true;
|
||||||
|
@ -130,24 +130,24 @@ bool domGet (
|
||||||
// dom.tracked.N.start
|
// dom.tracked.N.start
|
||||||
if (pig.skipLiteral ("start"))
|
if (pig.skipLiteral ("start"))
|
||||||
{
|
{
|
||||||
value = tracked[count - n].range.start.toISOLocalExtended ();
|
value = tracked[count - n].start.toISOLocalExtended ();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dom.tracked.N.end
|
// dom.tracked.N.end
|
||||||
if (pig.skipLiteral ("end"))
|
if (pig.skipLiteral ("end"))
|
||||||
{
|
{
|
||||||
if (tracked[count -n].range.is_open ())
|
if (tracked[count -n].is_open ())
|
||||||
value = "";
|
value = "";
|
||||||
else
|
else
|
||||||
value = tracked[count - n].range.end.toISOLocalExtended ();
|
value = tracked[count - n].end.toISOLocalExtended ();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dom.tracked.N.duration
|
// dom.tracked.N.duration
|
||||||
if (pig.skipLiteral ("duration"))
|
if (pig.skipLiteral ("duration"))
|
||||||
{
|
{
|
||||||
value = Duration (tracked[count - n].range.total ()).formatISO ();
|
value = Duration (tracked[count - n].total ()).formatISO ();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ std::string intervalSummarize (
|
||||||
{
|
{
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
|
|
||||||
if (interval.range.is_started ())
|
if (interval.is_started ())
|
||||||
{
|
{
|
||||||
// Walk backwards through the inclusions, and stop as soon as the tags
|
// Walk backwards through the inclusions, and stop as soon as the tags
|
||||||
// no longer match interval. This means the 'total' is the sum of all time
|
// no longer match interval. This means the 'total' is the sum of all time
|
||||||
|
@ -96,7 +96,7 @@ std::string intervalSummarize (
|
||||||
std::vector <Interval>::reverse_iterator i;
|
std::vector <Interval>::reverse_iterator i;
|
||||||
for (i = inclusions.rbegin (); i != inclusions.rend (); i++)
|
for (i = inclusions.rbegin (); i != inclusions.rend (); i++)
|
||||||
if (interval.tags () == i->tags ())
|
if (interval.tags () == i->tags ())
|
||||||
total_recorded += i->range.total ();
|
total_recorded += i->total ();
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -113,11 +113,11 @@ std::string intervalSummarize (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interval open.
|
// Interval open.
|
||||||
if (interval.range.is_open ())
|
if (interval.is_open ())
|
||||||
{
|
{
|
||||||
out << "Tracking " << tags << '\n'
|
out << "Tracking " << tags << '\n'
|
||||||
<< " Started " << interval.range.start.toISOLocalExtended () << '\n'
|
<< " Started " << interval.start.toISOLocalExtended () << '\n'
|
||||||
<< " Current " << minimalDelta (interval.range.start, Datetime ()) << '\n'
|
<< " Current " << minimalDelta (interval.start, Datetime ()) << '\n'
|
||||||
<< " Total " << std::setw (19) << std::setfill (' ') << total.formatHours () << '\n';
|
<< " Total " << std::setw (19) << std::setfill (' ') << total.formatHours () << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,8 +125,8 @@ std::string intervalSummarize (
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
out << "Recorded " << tags << '\n'
|
out << "Recorded " << tags << '\n'
|
||||||
<< " Started " << interval.range.start.toISOLocalExtended () << '\n'
|
<< " Started " << interval.start.toISOLocalExtended () << '\n'
|
||||||
<< " Ended " << minimalDelta (interval.range.start, interval.range.end) << '\n'
|
<< " Ended " << minimalDelta (interval.start, interval.end) << '\n'
|
||||||
<< " Total " << std::setw (19) << std::setfill (' ') << total.formatHours () << '\n';
|
<< " Total " << std::setw (19) << std::setfill (' ') << total.formatHours () << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,7 +456,7 @@ std::vector <Interval> getOverlaps (
|
||||||
|
|
||||||
std::vector <Interval> overlaps;
|
std::vector <Interval> overlaps;
|
||||||
for (auto& track : tracked)
|
for (auto& track : tracked)
|
||||||
if (interval.range.overlaps (track.range))
|
if (interval.overlaps (track))
|
||||||
overlaps.push_back (track);
|
overlaps.push_back (track);
|
||||||
|
|
||||||
return overlaps;
|
return overlaps;
|
||||||
|
|
|
@ -43,40 +43,40 @@ void autoFill (
|
||||||
Database& database,
|
Database& database,
|
||||||
Interval& interval)
|
Interval& interval)
|
||||||
{
|
{
|
||||||
// An empty filter allows scanning beyond interval.range.
|
// An empty filter allows scanning beyond interval range.
|
||||||
Interval range_filter;
|
Interval range_filter;
|
||||||
|
|
||||||
// Look backwards from interval.range.start to a boundary.
|
// Look backwards from interval.start to a boundary.
|
||||||
auto tracked = getTracked (database, rules, range_filter);
|
auto tracked = getTracked (database, rules, range_filter);
|
||||||
for (auto earlier = tracked.rbegin (); earlier != tracked.rend (); ++earlier)
|
for (auto earlier = tracked.rbegin (); earlier != tracked.rend (); ++earlier)
|
||||||
{
|
{
|
||||||
if (! earlier->range.is_open () &&
|
if (! earlier->is_open () &&
|
||||||
earlier->range.end <= interval.range.start)
|
earlier->end <= interval.start)
|
||||||
{
|
{
|
||||||
interval.range.start = earlier->range.end;
|
interval.start = earlier->end;
|
||||||
if (rules.getBoolean ("verbose"))
|
if (rules.getBoolean ("verbose"))
|
||||||
std::cout << "Backfilled "
|
std::cout << "Backfilled "
|
||||||
<< (interval.id ? format ("@{1} ", interval.id) : "")
|
<< (interval.id ? format ("@{1} ", interval.id) : "")
|
||||||
<< "to "
|
<< "to "
|
||||||
<< interval.range.start.toISOLocalExtended ()
|
<< interval.start.toISOLocalExtended ()
|
||||||
<< "\n";
|
<< "\n";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the interval is closed, scan forwards for the next boundary.
|
// If the interval is closed, scan forwards for the next boundary.
|
||||||
if (! interval.range.is_open ())
|
if (! interval.is_open ())
|
||||||
{
|
{
|
||||||
for (auto& later : tracked)
|
for (auto& later : tracked)
|
||||||
{
|
{
|
||||||
if (interval.range.end <= later.range.start)
|
if (interval.end <= later.start)
|
||||||
{
|
{
|
||||||
interval.range.end = later.range.start;
|
interval.end = later.start;
|
||||||
if (rules.getBoolean ("verbose"))
|
if (rules.getBoolean ("verbose"))
|
||||||
std::cout << "Filled "
|
std::cout << "Filled "
|
||||||
<< (interval.id ? format ("@{1} ", interval.id) : "")
|
<< (interval.id ? format ("@{1} ", interval.id) : "")
|
||||||
<< "to "
|
<< "to "
|
||||||
<< interval.range.end.toISOLocalExtended ()
|
<< interval.end.toISOLocalExtended ()
|
||||||
<< "\n";
|
<< "\n";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -116,21 +116,21 @@ static void autoAdjust (
|
||||||
// implement overwrite resolution, i.e. the new interval overwrites existing intervals
|
// implement overwrite resolution, i.e. the new interval overwrites existing intervals
|
||||||
for (auto& overlap : overlaps)
|
for (auto& overlap : overlaps)
|
||||||
{
|
{
|
||||||
bool start_within_overlap = interval.range.startsWithin (overlap.range);
|
bool start_within_overlap = interval.startsWithin (overlap);
|
||||||
bool end_within_overlap = interval.range.endsWithin (overlap.range);
|
bool end_within_overlap = interval.endsWithin (overlap);
|
||||||
|
|
||||||
if (start_within_overlap && !end_within_overlap)
|
if (start_within_overlap && !end_within_overlap)
|
||||||
{
|
{
|
||||||
// start date of new interval within old interval
|
// start date of new interval within old interval
|
||||||
Interval modified {overlap};
|
Interval modified {overlap};
|
||||||
modified.range.end = interval.range.start;
|
modified.end = interval.start;
|
||||||
database.modifyInterval (overlap, modified, rules.getBoolean ("verbose"));
|
database.modifyInterval (overlap, modified, rules.getBoolean ("verbose"));
|
||||||
}
|
}
|
||||||
else if (!start_within_overlap && end_within_overlap)
|
else if (!start_within_overlap && end_within_overlap)
|
||||||
{
|
{
|
||||||
// end date of new interval within old interval
|
// end date of new interval within old interval
|
||||||
Interval modified {overlap};
|
Interval modified {overlap};
|
||||||
modified.range.start = interval.range.end;
|
modified.start = interval.end;
|
||||||
database.modifyInterval (overlap, modified, rules.getBoolean ("verbose"));
|
database.modifyInterval (overlap, modified, rules.getBoolean ("verbose"));
|
||||||
}
|
}
|
||||||
else if (!start_within_overlap && !end_within_overlap)
|
else if (!start_within_overlap && !end_within_overlap)
|
||||||
|
@ -144,10 +144,10 @@ static void autoAdjust (
|
||||||
Interval split2 {overlap};
|
Interval split2 {overlap};
|
||||||
Interval split1 {overlap};
|
Interval split1 {overlap};
|
||||||
|
|
||||||
split1.range.end = interval.range.start;
|
split1.end = interval.start;
|
||||||
split2.range.start = interval.range.end;
|
split2.start = interval.end;
|
||||||
|
|
||||||
if (split1.range.is_empty ())
|
if (split1.is_empty ())
|
||||||
{
|
{
|
||||||
database.deleteInterval (overlap);
|
database.deleteInterval (overlap);
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ static void autoAdjust (
|
||||||
database.modifyInterval (overlap, split1, rules.getBoolean ("verbose"));
|
database.modifyInterval (overlap, split1, rules.getBoolean ("verbose"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! split2.range.is_empty ())
|
if (! split2.is_empty ())
|
||||||
{
|
{
|
||||||
database.addInterval (split2, rules.getBoolean ("verbose"));
|
database.addInterval (split2, rules.getBoolean ("verbose"));
|
||||||
}
|
}
|
||||||
|
@ -174,8 +174,8 @@ void validate (
|
||||||
{
|
{
|
||||||
// Create a filter, and if empty, choose 'today'.
|
// Create a filter, and if empty, choose 'today'.
|
||||||
auto filter = getFilter (cli);
|
auto filter = getFilter (cli);
|
||||||
if (! filter.range.is_started ())
|
if (! filter.is_started ())
|
||||||
filter.range = Range (Datetime ("today"), Datetime ("tomorrow"));
|
filter.setRange (Range (Datetime ("today"), Datetime ("tomorrow")));
|
||||||
|
|
||||||
// All validation performed here.
|
// All validation performed here.
|
||||||
if (findHint (cli, ":fill"))
|
if (findHint (cli, ":fill"))
|
||||||
|
|
|
@ -46,8 +46,8 @@ void test_flatten (
|
||||||
Interval tmp;
|
Interval tmp;
|
||||||
tmp.initialize (output[i]);
|
tmp.initialize (output[i]);
|
||||||
|
|
||||||
t.is (tmp.range.start.toISO (), results[i].range.start.toISO (), "flatten: " + label + " start matches");
|
t.is (tmp.start.toISO (), results[i].start.toISO (), "flatten: " + label + " start matches");
|
||||||
t.is (tmp.range.end.toISO (), results[i].range.end.toISO (), "flatten: " + label + " end matches");
|
t.is (tmp.end.toISO (), results[i].end.toISO (), "flatten: " + label + " end matches");
|
||||||
t.ok (tmp.tags () == results[i].tags (), "flatten: " + label + " tags match");
|
t.ok (tmp.tags () == results[i].tags (), "flatten: " + label + " tags match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,12 +201,12 @@ int main (int, char**)
|
||||||
|
|
||||||
// bool matchesFilter (const Interval& interval, const Interval& filter);
|
// bool matchesFilter (const Interval& interval, const Interval& filter);
|
||||||
Interval refOpen;
|
Interval refOpen;
|
||||||
refOpen.range = Range (Datetime (2016, 6, 1), Datetime (0));
|
refOpen.setRange (Range (Datetime (2016, 6, 1), Datetime (0)));
|
||||||
refOpen.tag ("tag1");
|
refOpen.tag ("tag1");
|
||||||
refOpen.tag ("tag2");
|
refOpen.tag ("tag2");
|
||||||
|
|
||||||
Interval refClosed;
|
Interval refClosed;
|
||||||
refClosed.range = Range (Datetime (2016, 6, 1), Datetime (2016, 6, 30));
|
refClosed.setRange (Range (Datetime (2016, 6, 1), Datetime (2016, 6, 30)));
|
||||||
refClosed.tag ("tag1");
|
refClosed.tag ("tag1");
|
||||||
refClosed.tag ("tag2");
|
refClosed.tag ("tag2");
|
||||||
|
|
||||||
|
@ -235,15 +235,15 @@ int main (int, char**)
|
||||||
Interval i;
|
Interval i;
|
||||||
i.tag ("tag1");
|
i.tag ("tag1");
|
||||||
i.tag ("tag2");
|
i.tag ("tag2");
|
||||||
i.range = testA; t.notok (matchesFilter (i, refClosed), "matchesFilter A <!> refClosed");
|
i.setRange (testA); t.notok (matchesFilter (i, refClosed), "matchesFilter A <!> refClosed");
|
||||||
i.range = testB; t.ok (matchesFilter (i, refClosed), "matchesFilter B <=> refClosed");
|
i.setRange (testB); t.ok (matchesFilter (i, refClosed), "matchesFilter B <=> refClosed");
|
||||||
i.range = testC; t.ok (matchesFilter (i, refClosed), "matchesFilter C <=> refClosed");
|
i.setRange (testC); t.ok (matchesFilter (i, refClosed), "matchesFilter C <=> refClosed");
|
||||||
i.range = testD; t.ok (matchesFilter (i, refClosed), "matchesFilter D <=> refClosed");
|
i.setRange (testD); t.ok (matchesFilter (i, refClosed), "matchesFilter D <=> refClosed");
|
||||||
i.range = testE; t.notok (matchesFilter (i, refClosed), "matchesFilter E <!> refClosed");
|
i.setRange (testE); t.notok (matchesFilter (i, refClosed), "matchesFilter E <!> refClosed");
|
||||||
i.range = testF; t.ok (matchesFilter (i, refClosed), "matchesFilter F <=> refClosed");
|
i.setRange (testF); t.ok (matchesFilter (i, refClosed), "matchesFilter F <=> refClosed");
|
||||||
i.range = testG; t.ok (matchesFilter (i, refClosed), "matchesFilter G <=> refClosed");
|
i.setRange (testG); t.ok (matchesFilter (i, refClosed), "matchesFilter G <=> refClosed");
|
||||||
i.range = testH; t.ok (matchesFilter (i, refClosed), "matchesFilter H <=> refClosed");
|
i.setRange (testH); t.ok (matchesFilter (i, refClosed), "matchesFilter H <=> refClosed");
|
||||||
i.range = testI; t.notok (matchesFilter (i, refClosed), "matchesFilter I <!> refClosed");
|
i.setRange (testI); t.notok (matchesFilter (i, refClosed), "matchesFilter I <!> refClosed");
|
||||||
|
|
||||||
// this [...
|
// this [...
|
||||||
// A [--------)
|
// A [--------)
|
||||||
|
@ -255,15 +255,15 @@ int main (int, char**)
|
||||||
// G [...
|
// G [...
|
||||||
// H [...
|
// H [...
|
||||||
// I [...
|
// I [...
|
||||||
i.range = testA; t.notok (matchesFilter (i, refOpen), "matchesFilter A <!> refOpen");
|
i.setRange (testA); t.notok (matchesFilter (i, refOpen), "matchesFilter A <!> refOpen");
|
||||||
i.range = testB; t.ok (matchesFilter (i, refOpen), "matchesFilter B <=> refOpen");
|
i.setRange (testB); t.ok (matchesFilter (i, refOpen), "matchesFilter B <=> refOpen");
|
||||||
i.range = testC; t.ok (matchesFilter (i, refOpen), "matchesFilter C <=> refOpen");
|
i.setRange (testC); t.ok (matchesFilter (i, refOpen), "matchesFilter C <=> refOpen");
|
||||||
i.range = testD; t.ok (matchesFilter (i, refOpen), "matchesFilter D <=> refOpen");
|
i.setRange (testD); t.ok (matchesFilter (i, refOpen), "matchesFilter D <=> refOpen");
|
||||||
i.range = testE; t.ok (matchesFilter (i, refOpen), "matchesFilter E <=> refOpen");
|
i.setRange (testE); t.ok (matchesFilter (i, refOpen), "matchesFilter E <=> refOpen");
|
||||||
i.range = testF; t.ok (matchesFilter (i, refOpen), "matchesFilter F <=> refOpen");
|
i.setRange (testF); t.ok (matchesFilter (i, refOpen), "matchesFilter F <=> refOpen");
|
||||||
i.range = testG; t.ok (matchesFilter (i, refOpen), "matchesFilter G <=> refOpen");
|
i.setRange (testG); t.ok (matchesFilter (i, refOpen), "matchesFilter G <=> refOpen");
|
||||||
i.range = testH; t.ok (matchesFilter (i, refOpen), "matchesFilter H <=> refOpen");
|
i.setRange (testH); t.ok (matchesFilter (i, refOpen), "matchesFilter H <=> refOpen");
|
||||||
i.range = testI; t.ok (matchesFilter (i, refOpen), "matchesFilter I <=> refOpen");
|
i.setRange (testI); t.ok (matchesFilter (i, refOpen), "matchesFilter I <=> refOpen");
|
||||||
|
|
||||||
// Range getFullDay (const Datetime&);
|
// Range getFullDay (const Datetime&);
|
||||||
auto r1 = getFullDay (Datetime ("20160501T203112"));
|
auto r1 = getFullDay (Datetime ("20160501T203112"));
|
||||||
|
|
|
@ -37,19 +37,19 @@ int main (int, char**)
|
||||||
// bool is_ended () const;
|
// bool is_ended () const;
|
||||||
Interval i1;
|
Interval i1;
|
||||||
t.is (i1.empty (), true, "Interval().empty -> true");
|
t.is (i1.empty (), true, "Interval().empty -> true");
|
||||||
t.is (i1.range.is_started (), false, "Interval().is_started -> false");
|
t.is (i1.is_started (), false, "Interval().is_started -> false");
|
||||||
t.is (i1.range.is_ended (), false, "Interval().is_ended -> false");
|
t.is (i1.is_ended (), false, "Interval().is_ended -> false");
|
||||||
|
|
||||||
// void start (Datetime);
|
// void start (Datetime);
|
||||||
i1.range.start = Datetime ();
|
i1.start = Datetime ();
|
||||||
t.is (i1.empty (), false, "Interval().empty -> false");
|
t.is (i1.empty (), false, "Interval().empty -> false");
|
||||||
t.is (i1.range.is_started (), true, "Interval(start=now).is_started -> true");
|
t.is (i1.is_started (), true, "Interval(start=now).is_started -> true");
|
||||||
t.is (i1.range.is_ended (), false, "Interval(start=now).is_ended -> false");
|
t.is (i1.is_ended (), false, "Interval(start=now).is_ended -> false");
|
||||||
|
|
||||||
// void end (Datetime);
|
// void end (Datetime);
|
||||||
i1.range.end = Datetime ();
|
i1.end = Datetime ();
|
||||||
t.is (i1.range.is_started (), true, "Interval(start=now,end=now).is_started -> true");
|
t.is (i1.is_started (), true, "Interval(start=now,end=now).is_started -> true");
|
||||||
t.is (i1.range.is_ended (), true, "Interval(start=now,end=now).is_ended -> true");
|
t.is (i1.is_ended (), true, "Interval(start=now,end=now).is_ended -> true");
|
||||||
|
|
||||||
// std::set <std::string> tags () const;
|
// std::set <std::string> tags () const;
|
||||||
// void tag (const std::string&);
|
// void tag (const std::string&);
|
||||||
|
@ -73,9 +73,9 @@ int main (int, char**)
|
||||||
t.is (i3.serialize (), "inc # foo", "Interval().serialize -> 'inc # foo'");
|
t.is (i3.serialize (), "inc # foo", "Interval().serialize -> 'inc # foo'");
|
||||||
i3.tag ("bar");
|
i3.tag ("bar");
|
||||||
t.is (i3.serialize (), "inc # bar foo", "Interval().serialize -> 'inc # bar foo'");
|
t.is (i3.serialize (), "inc # bar foo", "Interval().serialize -> 'inc # bar foo'");
|
||||||
i3.range.start = Datetime(1);
|
i3.start = Datetime(1);
|
||||||
t.is (i3.serialize (), "inc 19700101T000001Z # bar foo", "Interval(Datetime(1)).serialize -> 'inc 19700101T000001Z # bar foo'");
|
t.is (i3.serialize (), "inc 19700101T000001Z # bar foo", "Interval(Datetime(1)).serialize -> 'inc 19700101T000001Z # bar foo'");
|
||||||
i3.range.end = Datetime(2);
|
i3.end = Datetime(2);
|
||||||
t.is (i3.serialize (), "inc 19700101T000001Z - 19700101T000002Z # bar foo", "Interval(Datetime(1)).serialize -> 'inc 19700101T000001Z - 19700101T000002Z # bar foo'");
|
t.is (i3.serialize (), "inc 19700101T000001Z - 19700101T000002Z # bar foo", "Interval(Datetime(1)).serialize -> 'inc 19700101T000001Z - 19700101T000002Z # bar foo'");
|
||||||
i3.tag ("Trans-Europe Express");
|
i3.tag ("Trans-Europe Express");
|
||||||
t.is (i3.serialize (), "inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo", "Interval(Datetime(1)).serialize -> 'inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo'");
|
t.is (i3.serialize (), "inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo", "Interval(Datetime(1)).serialize -> 'inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo'");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue