obsidian-tasks icon indicating copy to clipboard operation
obsidian-tasks copied to clipboard

Understand and document what recurrence does when advancing to non-existent dates

Open claremacrae opened this issue 3 years ago • 10 comments
trafficstars

This arises from testing done, investigating improvements around #612

I added some experimental test cases exploring recurrence around month boundaries and leap years.

Expected Behavior

Based on no information whatsoever, I expected that if applying a recurrence rule resulted in an invalid date, the code would make small adjustments to the new date, to try to honour the rule.

So that, for example, if a due date was 31st Jan, and the recurrence was monthly, it would advance to 28th Feb (or 29th if in a leap year)

Current Behavior

These tests all initially failed, and now they pass, because I set nextDue to match the current behaviour.

(Note that they have a not-yet-committed description field to describe the purpose of the tests...)

        // Cases where adding 1 increment falls on a date that does not exist
        {
            description:
                'every month - due 31 March, and so 31 April would not exist: beware it skips forward 2 months',
            interval: 'every month',
            due: '2021-03-31',
            // What I expected: nextDue: '2021-04-30',
            nextDue: '2021-05-31',
        },
        {
            description:
                'every month - due 29 January, and so 29 February would not exist: beware it skips forward 2 months',
            interval: 'every month',
            due: '2021-01-29',
            // What I expected: nextDue: '2021-02-28',
            nextDue: '2021-03-29',
        },

        // Testing yearly repetition around leap days
        {
            description:
                'yearly, due on leap day 29th February: beware it skips forward 4 years',
            interval: 'every year',
            due: '2020-02-29',
            // What I expected: nextDue: '2021-02-28',
            nextDue: '2024-02-29',
        },

Steps to Reproduce

Context (Environment)

  • Obsidian version: 0.14.6
  • Tasks version: 1.5.1
  • [x] ~~I have tried it with all other plugins disabled and the error still occurs~~ Not applicable

Possible Solution

In the first instance, I think some documentation is called far. I will basically review all my recurring tasks to check for ones after 28th of the month.

Maybe rules can be expressed such as every month on the last day ???

claremacrae avatar Apr 20 '22 19:04 claremacrae

I will basically review all my recurring tasks to check for ones after 28th of the month.

It's easy to check one's due dates and make sure there are none at the end of the month.

I think a perhaps more serious manifestation of this is with when done as the next date is determined by the date when the task was completed.

So any monthly when done tasks completed on these dates will skip forward two months:

  • 29, 30, 31 January
  • 31 March
  • 31 May
  • 31 August
  • 31 October

And any yearly when done task completed on this date will skip forward four years:

  • 29 February

Sample test code:

        {
            description:
                'every month when done - and completed 31st March: beware it skips forward 2 months',
            interval: 'every month when done',
            due: '2021-03-17',
            today: '2021-03-31',
            // What I expected: nextDue: '2021-04-30',
            nextDue: '2021-05-31',
        },

claremacrae avatar Apr 20 '22 19:04 claremacrae

Good catch. Thank you @claremacrae. We should indeed add this to the documentation.

By the way, every month on the last day works (as does every month on the 2nd last Friday, etc.).

Again, I tried this in Google Calendar and if I set up an event as Monthly on day 31, it does indeed skip months with fewer days. That's per the specification:

 Recurrence rules may generate recurrence instances with an invalid
 date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM
 on a day where the local time is moved forward by an hour at 1:00
 AM).  Such recurrence instances MUST be ignored and MUST NOT be
 counted as part of the recurrence set.

schemar avatar Apr 20 '22 20:04 schemar

Thanks that's really helpful. It's the every month when done bit that will be hard to document and work around...

claremacrae avatar Apr 20 '22 20:04 claremacrae

Thanks that's really helpful. It's the every month when done bit that will be hard to document and work around...

Indeed. Maybe we should not allow every month when done and recommend every 4 weeks when done or whatever instead? I wish I still had my Todoist account. I would like to try every! month on the 31st to see how they implemented it :sweat_smile:

schemar avatar Apr 20 '22 20:04 schemar

Indeed. Maybe we should not allow every month when done and recommend every 4 weeks when done or whatever instead?

I do think that is a really good idea. The more we can detect pitfalls and encourage users in safer directions, the better, I feel...

claremacrae avatar Apr 20 '22 20:04 claremacrae

And for ‘every year when done’ we should recommend ‘every 52 weeks when done’

claremacrae avatar Apr 20 '22 22:04 claremacrae

Oh, that's wild. Personally, I mostly set up recurrences measured in days/weeks, can confirm it works reliably at that granularity.

mauleb avatar Apr 20 '22 22:04 mauleb

Is this fixed by #944 ?

AnnaKornfeldSimpson avatar Jul 30 '22 03:07 AnnaKornfeldSimpson

Is this fixed by #944 ?

I don't think it is. For example, if every month when done is used, and the task is completed on the 31st, for several months of the year the new instance will skip a month.

claremacrae avatar Jul 30 '22 06:07 claremacrae

I've added the Help Wanted label to this.

The place to document the issues and tips raised here is in: https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/docs/getting-started/recurring-tasks.md

Which is rendered here. https://obsidian-tasks-group.github.io/obsidian-tasks/getting-started/recurring-tasks/

Steps would be the following, which assume knowledge of creating git branches and GitHub pull requests:

  1. Fork this repository
  2. Check out the gh-pages branch
  3. Read the instructions on writing and viewing the docs at https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/docs/README.md
  4. Create a branch off of gh-pages
  5. Edit the file https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/docs/getting-started/recurring-tasks.md
  6. Add as much info as needed from the comments in this ticket to convey to users the severity of the issue, how it especially affects when done searches, and the workaround of using a fixed length interval, of a number of days or weeks
  7. Ideally, generate the docs site locally and review your changes, including the formatting (optional, if you are unable to do this for some reason)
  8. Create a pull request, to merge your changes in to the gh-pages branch

claremacrae avatar Aug 31 '22 05:08 claremacrae

This has been significantly improved in release 1.15.1.

Note that if you say every month on the 31st, it will still skip months. Use every month on the last instead.

claremacrae avatar Oct 08 '22 22:10 claremacrae