Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not create recurring tasks before today #3542

Merged
merged 1 commit into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/recur.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,10 @@ void handleRecurrence ()

////////////////////////////////////////////////////////////////////////////////
// Determine a start date (due), an optional end date (until), and an increment
// period (recur). Then generate a set of corresponding dates.
// period (recur). Then generate a set of corresponding dates. Only recurrences
// in the future are returned; see #3501.
//
// Returns false if the parent recurring task is depleted.
// Returns false if the parent recurring task is deleted.
bool generateDueDates (Task& parent, std::vector <Datetime>& allDue)
{
// Determine due date, recur period and until date.
Expand All @@ -158,9 +159,12 @@ bool generateDueDates (Task& parent, std::vector <Datetime>& allDue)
auto recurrence_limit = Context::getContext ().config.getInteger ("recurrence.limit");
int recurrence_counter = 0;
Datetime now;
Datetime today = now.startOfDay();
for (Datetime i = due; ; i = getNextRecurrence (i, recur))
{
allDue.push_back (i);
// Do not add tasks before today (but allow today for the common `due:today` form).
if (i >= today)
allDue.push_back (i);

if (specificEnd && i > until)
{
Expand Down
69 changes: 38 additions & 31 deletions test/recurrence.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,14 +289,15 @@ def setUp(self):
self.t = Task()
self.t.config("report.id_pri_proj.columns", "id,priority,project")
self.t.config("report.id_pri_proj.labels", "ID,P,Proj")
self.t.config("recurrence.limit", "3")

def test_modify_due_propagate(self):
"""932: Verify due date modifications propagate"""

# add a recurring task with multiple child tasks
# - modify a child task and test for propagation
# - modify the parent task and test for propagation
self.t("add R due:yesterday recur:daily")
self.t("add R due:tomorrow recur:daily")
self.t("list") # GC/handleRecurrence

self.t("2 modify project:P", input="y\n")
Expand All @@ -316,7 +317,8 @@ class TestBug955(TestCase):
def setUp(self):
self.t = Task()

self.t("add foo due:now recur:1day")
self.t.config("recurrence.limit", "2")
self.t("add foo due:tomorrow recur:1day")
code, out, err = self.t("ls")
self.assertRegex(out, re.compile("^2 tasks", re.MULTILINE))

Expand Down Expand Up @@ -612,47 +614,52 @@ def setUp(self):

def test_annual_creep(self):
"""Verify 'annual' recurring tasks don't creep"""
self.t.config("recurrence.limit", "17")
self.t.config("dateformat", "YMD")
self.t.config("report.annual.labels", "ID,Due")
self.t.config("report.annual.columns", "id,due")
self.t.config("report.annual.filter", "status:pending")
self.t.config("report.annual.sort", "due+")

this_year = time.gmtime(time.time())[0]
def jan1(year_offset):
return f"{this_year+year_offset}0101"

# If a task is added with a due date ten years ago, with an annual recurrence,
# then the synthetic tasks in between then and now have a due date that creeps.
#
# ID Due Description
# -- ---------- -----------
# 4 1/1/2002 foo
# 5 1/1/2003 foo
# 6 1/1/2004 foo
# 7 1/1/2005 foo
# 8 1/1/2006 foo
# 9 1/1/2007 foo
# 10 1/1/2008 foo
# 11 1/1/2009 foo
# 12 1/1/2010 foo
# 2 1/1/2000 foo
# 3 1/1/2001 foo

self.t("add foo due:20000101 recur:annual until:20150101")
# 4 1/1/2102 foo
# 5 1/1/2103 foo
# 6 1/1/2104 foo
# 7 1/1/2105 foo
# 8 1/1/2106 foo
# 9 1/1/2107 foo
# 10 1/1/2108 foo
# 11 1/1/2109 foo
# 12 1/1/2110 foo
# 2 1/1/2100 foo
# 3 1/1/2101 foo

self.t(f"add foo due:{jan1(1)} recur:annual until:{jan1(16)}")
code, out, err = self.t("annual")
self.assertIn(" 2 20000101", out)
self.assertIn(" 3 20010101", out)
self.assertIn(" 4 20020101", out)
self.assertIn(" 5 20030101", out)
self.assertIn(" 6 20040101", out)
self.assertIn(" 7 20050101", out)
self.assertIn(" 8 20060101", out)
self.assertIn(" 9 20070101", out)
self.assertIn("10 20080101", out)
self.assertIn("11 20090101", out)
self.assertIn("12 20100101", out)
self.assertIn("13 20110101", out)
self.assertIn("14 20120101", out)
self.assertIn("15 20130101", out)
self.assertIn("16 20140101", out)
self.assertIn("17 20150101", out)
self.assertIn(f" 2 {jan1(1)}", out)
self.assertIn(f" 3 {jan1(2)}", out)
self.assertIn(f" 4 {jan1(3)}", out)
self.assertIn(f" 5 {jan1(4)}", out)
self.assertIn(f" 6 {jan1(5)}", out)
self.assertIn(f" 7 {jan1(6)}", out)
self.assertIn(f" 8 {jan1(7)}", out)
self.assertIn(f" 9 {jan1(8)}", out)
self.assertIn(f"10 {jan1(9)}", out)
self.assertIn(f"11 {jan1(10)}", out)
self.assertIn(f"12 {jan1(11)}", out)
self.assertIn(f"13 {jan1(12)}", out)
self.assertIn(f"14 {jan1(13)}", out)
self.assertIn(f"15 {jan1(14)}", out)
self.assertIn(f"16 {jan1(15)}", out)
self.assertIn(f"17 {jan1(16)}", out)


# TODO Wait a recurring task
Expand Down
Loading