taskwarrior/src/recur2.cpp
Rosen Penev 4aaaa8dcc8 clang-tidy: use bool literals
Found with modernize-use-bool-literals

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-04-23 00:11:52 -04:00

373 lines
9.8 KiB
C++

////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2021, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#include <Datetime.h>
#include <Duration.h>
#include <Context.h>
#include <format.h>
#include <unicode.h>
#include <main.h>
////////////////////////////////////////////////////////////////////////////////
// Checklist:
// - last: Most recently generated instance integer. The first instance
// generated is '1'.
// - Sync must merge duplicate N.
// - Remove recurrence.limit. Now always 1. It is not something that can be done
// with rtype:chained tasks.
// - Handle until.
////////////////////////////////////////////////////////////////////////////////
// Given an old-style task, upgrades it perfectly.
// Note: Works for both parent and child.
static Task upgradeTask (const Task&)
{
Task upgraded;
// TODO Convert 'mask' to 'last' <-- length (mask)
// TODO Convert 'parent' to 'template'.
// TODO Convert 'imask' to 'index'.
// TODO Add 'rtype:periodic'.
return upgraded;
}
////////////////////////////////////////////////////////////////////////////////
// Calculates the due date for a new instance N.
static Datetime generateNextDueDate (
const Datetime& first,
const std::string& period,
const int n)
{
auto y = first.year ();
auto m = first.month ();
auto d = first.day ();
auto hh = first.hour ();
auto mm = first.minute ();
auto ss = first.second ();
Duration dur (period);
auto normalized = dur.formatISO ();
Context::getContext ().debug (" period " + period + " --> " + normalized);
if (! dur._year &&
dur._month &&
! dur._weeks &&
! dur._day &&
! dur._hours &&
! dur._minutes &&
! dur._seconds)
{
m += dur._month * n;
while (m > 12)
{
y += 1;
m -= 12;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d, hh, mm, ss);
}
/*
// Some periods are difficult, because they can be vague.
if (period == "monthly" ||
period == "P1M")
{
if (++m > 12)
{
m -= 12;
++y;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d, ho, mi, se);
}
else if (period == "weekdays")
{
auto dow = current.dayOfWeek ();
int days;
if (dow == 5) days = 3;
else if (dow == 6) days = 2;
else days = 1;
return current + (days * 86400);
}
else if (unicodeLatinDigit (period[0]) &&
period[period.length () - 1] == 'm')
{
int increment = strtol (period.substr (0, period.length () - 1).c_str (), NULL, 10);
m += increment;
while (m > 12)
{
m -= 12;
++y;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d, ho, mi, se);
}
else if (period[0] == 'P' &&
Lexer::isAllDigits (period.substr (1, period.length () - 2)) &&
period[period.length () - 1] == 'M')
{
int increment = strtol (period.substr (0, period.length () - 1).c_str (), NULL, 10);
m += increment;
while (m > 12)
{
m -= 12;
++y;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d);
}
else if (period == "quarterly" ||
period == "P3M")
{
m += 3;
if (m > 12)
{
m -= 12;
++y;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d, ho, mi, se);
}
else if (unicodeLatinDigit (period[0]) && period[period.length () - 1] == 'q')
{
int increment = strtol (period.substr (0, period.length () - 1).c_str (), NULL, 10);
m += 3 * increment;
while (m > 12)
{
m -= 12;
++y;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d, ho, mi, se);
}
else if (period == "semiannual" ||
period == "P6M")
{
m += 6;
if (m > 12)
{
m -= 12;
++y;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d, ho, mi, se);
}
else if (period == "bimonthly" ||
period == "P2M")
{
m += 2;
if (m > 12)
{
m -= 12;
++y;
}
while (! Datetime::valid (y, m, d))
--d;
return Datetime (y, m, d, ho, mi, se);
}
else if (period == "biannual" ||
period == "biyearly" ||
period == "P2Y")
{
y += 2;
return Datetime (y, m, d, ho, mi, se);
}
else if (period == "annual" ||
period == "yearly" ||
period == "P1Y")
{
y += 1;
// If the due data just happens to be 2/29 in a leap year, then simply
// incrementing y is going to create an invalid date.
if (m == 2 && d == 29)
d = 28;
return Datetime (y, m, d, ho, mi, se);
}
// Add the period to current, and we're done.
std::string::size_type idx = 0;
Duration p;
if (! p.parse (period, idx))
throw std::string (format ("The recurrence value '{1}' is not valid.", period));
return current + p.toTime_t ();
*/
Datetime due;
return due;
}
////////////////////////////////////////////////////////////////////////////////
// Calculates the due date for a new instance N.
static std::vector <Datetime> generateAllDueDates (const Task& templateTask)
{
std::vector <Datetime> dueDates;
// Determine due date, recur period and until date.
Datetime due (templateTask.get_date ("due"));
Context::getContext ().debug (" due " + due.toISOLocalExtended ());
auto recur = templateTask.get ("recur");
Context::getContext ().debug (" recur " + recur);
auto lastN = std::max (1, templateTask.get_int ("last"));
Context::getContext ().debug (format (" last {1}", lastN));
bool end_in_sight = false;
Datetime until;
if (templateTask.get ("until") != "")
{
until = Datetime (templateTask.get ("until"));
end_in_sight = true;
Context::getContext ().debug (" until " + until.toISOLocalExtended ());
}
auto recurrence_limit = Context::getContext ().config.getInteger ("recurrence.limit");
Context::getContext ().debug (format (" recurrence.limit {1}", recurrence_limit));
int recurrence_counter = 0;
Datetime now;
while (true)
{
Datetime nextDue = generateNextDueDate (due, recur, lastN);
// TODO Safety.
if (dueDates.size () && dueDates.back () == nextDue)
break;
// If nextDue > until, it means there are no more tasks to generate, so
// this templateTask is finished.
if (end_in_sight && nextDue > until)
break;
if (nextDue > now)
++recurrence_counter;
if (recurrence_counter >= recurrence_limit)
break;
dueDates.push_back (nextDue);
}
return dueDates;
}
////////////////////////////////////////////////////////////////////////////////
static void synthesizeTasks (const Task& templateTask)
{
Context::getContext ().debug ("synthesizeTasks start");
Context::getContext ().debug (" template " + templateTask.get ("uuid"));
// TODO 'due' = starting point
// TODO 'recur' = frequency
// TODO 'last' = index of most recently synthesized instance
auto all = generateAllDueDates (templateTask);
for (auto& date : all)
Context::getContext ().debug (" date " + date.toISOLocalExtended ());
// TODO Create task instances for each period between N and now.
Context::getContext ().debug ("synthesizeTasks end");
}
////////////////////////////////////////////////////////////////////////////////
// Generates all necessary recurring task instances.
void handleRecurrence2 ()
{
// Note: Disabling recurrence is currently a workaround for TD-44, TW-1520.
if (Context::getContext ().config.getBoolean ("recurrence"))
for (auto& t : Context::getContext ().tdb2.pending.get_tasks ())
if (t.getStatus () == Task::recurring)
synthesizeTasks (t);
}
////////////////////////////////////////////////////////////////////////////////
// Delete expired tasks.
void handleUntil ()
{
Datetime now;
auto tasks = Context::getContext ().tdb2.pending.get_tasks ();
for (auto& t : tasks)
{
// TODO What about expiring template tasks?
if (t.getStatus () == Task::pending &&
t.has ("until"))
{
auto until = Datetime (t.get_date ("until"));
if (until < now)
{
Context::getContext ().debug (format ("handleUntil: recurrence expired until {1} < now {2}", until.toISOLocalExtended (), now.toISOLocalExtended ()));
t.setStatus (Task::deleted);
Context::getContext ().tdb2.modify(t);
Context::getContext ().footnote (onExpiration (t));
}
}
}
}
////////////////////////////////////////////////////////////////////////////////