diff --git a/src/main.h b/src/main.h index aa7ade643..38a217137 100644 --- a/src/main.h +++ b/src/main.h @@ -39,13 +39,13 @@ // recur.cpp void handleRecurrence (); +void handleUntil (); Datetime getNextRecurrence (Datetime&, std::string&); bool generateDueDates (Task&, std::vector &); void updateRecurrenceMask (Task&); // recur2.cpp void handleRecurrence2 (); -void handleUntil (); // nag.cpp bool nag (Task&); diff --git a/src/recur.cpp b/src/recur.cpp index 61bb169b9..627e545e9 100644 --- a/src/recur.cpp +++ b/src/recur.cpp @@ -412,3 +412,27 @@ void updateRecurrenceMask (Task& task) } //////////////////////////////////////////////////////////////////////////////// +// 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)); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/recur2.cpp b/src/recur2.cpp deleted file mode 100644 index 55c06ac54..000000000 --- a/src/recur2.cpp +++ /dev/null @@ -1,373 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// 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 -#include -#include -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -// 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 generateAllDueDates (const Task& templateTask) -{ - std::vector 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)); - } - } - } -} - -////////////////////////////////////////////////////////////////////////////////