untitaker: I don't think this has to be its own command, but rather a set of new flags on
`list` (tend to be against config settings). Particularly the porcelain flag
sounds nice to have even for other usage of `list`.

On Thu, Jan 12, 2017 at 03:21:25PM -0800, Hugo Osvaldo Barrera wrote:
> The main idea behind this is to allow external scripts to remind of soon-to-expire tasks, or just allow the user to check if they're up to date on tasks or now.
> 
> Some stuff that needs to be defined:
> 
>  * Do we want `due_in` to be a flag or configuration setting? I'm inclined to make it the latter, though both might be possible.
>  * Do we want `overdue` to be a flag or configuration setting? Same doubt here.
> 
> Pending:
> 
> * [ ] Tests:
>   * Include ones with tz-aware tasks, and tz-naive tasks.
> * [ ] A `--porcelain` flag, to keep the format permanently frozen so scripts can parse this easily.
> 
> I think it's safe to say that this would close #67
> 
> Please, chime in on the functionality (I'll continue polishing the code in the meantime, it's a mere prototype)
> You can view, comment on, or merge this pull request online at:
> 
>   https://github.com/pimutils/todoman/pull/68
> 
> -- Commit Summary --
> 
>   * Implement the remind command
> 
> -- File Changes --
> 
>     M todoman/cli.py (39)
> 
> -- Patch Links --
> 
> https://github.com/pimutils/todoman/pull/68.patch
> https://github.com/pimutils/todoman/pull/68.diff
> 
> -- 
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly or view it on GitHub:
> https://github.com/pimutils/todoman/pull/68

hobarrera: Good call. It's also *a lot* less code than I'd expect. It's a shame that the filtering `for` loop keeps getting uglier, but I have come plans in mind for that one. ^^
diff --git a/todoman/cli.py b/todoman/cli.py
index e59f488..0703c85 100644
--- a/todoman/cli.py
+++ b/todoman/cli.py
@@ -304,7 +304,13 @@ def move(ctx, list, ids):
 @click.option('--reverse/--no-reverse', default=True,
               help='Sort tasks in reverse order (see --sort). '
               'Defaults to true.')
-def list(ctx, lists, all, urgent, location, category, grep, sort, reverse):
+@click.option('--due', default=None, help='Only show tasks due in DUE hours',
+              type=int)
+# TODO: we might want a `porcelain` flag here to print this is a
+# machine-friendly format that NEVER CHANGES!
+def list(
+    ctx, lists, all, urgent, location, category, grep, sort, reverse, due,
+         ):
     """
     List unfinished tasks.
 
@@ -323,6 +329,7 @@ def list(ctx, lists, all, urgent, location, category, grep, sort, reverse):
 
     db = ctx.obj['db']
     todos = db.todos(
+        due=due,
         all=all,
         category=category,
         grep=grep,
diff --git a/todoman/model.py b/todoman/model.py
index 91e515f..b9293bf 100644
--- a/todoman/model.py
+++ b/todoman/model.py
@@ -502,7 +502,7 @@ class Cache:
         )
 
     def todos(self, all=False, lists=[], urgent=False, location='',
-              category='', grep='', sort=[], reverse=True):
+              category='', grep='', sort=[], reverse=True, due=None):
         list_map = {list.name: list for list in self.lists()}
 
         extra_where = []
@@ -532,6 +532,10 @@ class Cache:
             # params.append(grep)
             extra_where.append('AND summary LIKE ?')
             params.append('%{}%'.format(grep))
+        if due:
+            max_due = datetime.now() + timedelta(hours=due)
+            extra_where.append('AND due IS NOT NULL AND due < ?')
+            params.append(max_due)
 
         if sort:
             order = []

List by "today", "this week"
When I use `todo list` I get a very long list of all my tasks. I know I can filter by lists, but is it possible to filter by "this week", "today", "overdue"? I'd like to have a short list of today's (and overdue) tasks so I get a good feeling when I've done all for today :-)
diff --git a/tests/test_filtering.py b/tests/test_filtering.py
index c8bd520..c1a234f 100644
--- a/tests/test_filtering.py
+++ b/tests/test_filtering.py
@@ -1,4 +1,7 @@
+from datetime import datetime, timedelta
+
 from todoman.cli import cli
+from todoman.model import Database
 
 
 def test_all(tmpdir, runner, create):
@@ -154,3 +157,45 @@ def test_filtering_lists(tmpdir, runner, create):
     assert not result.exception
     assert len(result.output.splitlines()) == 1
     assert 'todo two' in result.output
+
+
+def test_due_aware(tmpdir, runner, create):
+    now = datetime.now()
+
+    for i in [1, 23, 25, 48]:
+        due = now + timedelta(hours=i)
+        create(
+            'test_{}.ics'.format(i),
+            'SUMMARY:{}\n'
+            'DUE;VALUE=DATE-TIME;TZID=CET:{}\n'.format(
+                i, due.strftime("%Y%m%dT%H%M%S"),
+            )
+        )
+
+    db = Database([tmpdir.join('default')], tmpdir.join('cache.sqlite'))
+    todos = list(db.todos(due=24))
+
+    assert len(todos) == 2
+    assert todos[0].summary == "23"
+    assert todos[1].summary == "1"
+
+
+def test_due_naive(tmpdir, runner, create):
+    now = datetime.now()
+
+    for i in [1, 23, 25, 48]:
+        due = now + timedelta(hours=i)
+        create(
+            'test_{}.ics'.format(i),
+            'SUMMARY:{}\n'
+            'DUE;VALUE=DATE-TIME:{}\n'.format(
+                i, due.strftime("%Y%m%dT%H%M%S"),
+            )
+        )
+
+    db = Database([tmpdir.join('default')], tmpdir.join('cache.sqlite'))
+    todos = list(db.todos(due=24))
+
+    assert len(todos) == 2
+    assert todos[0].summary == "23"
+    assert todos[1].summary == "1"

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov hypothesis
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
atomicwrites==1.4.1
attrs==25.3.0
click==8.1.8
coverage==7.8.0
exceptiongroup==1.2.2
hypothesis==6.130.5
icalendar==6.1.3
iniconfig==2.1.0
packaging==24.2
parsedatetime==2.6
pluggy==1.5.0
pytest==8.3.5
pytest-cov==6.0.0
python-dateutil==2.9.0.post0
pyxdg==0.28
six==1.17.0
sortedcontainers==2.4.0
-e git+https://github.com/pimutils/todoman.git@e6c27480329dceb6182454e06b39a5270317c3b9#egg=todoman
tomli==2.2.1
typing_extensions==4.13.0
tzdata==2025.2
urwid==2.6.16
wcwidth==0.2.13

name: todoman
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - atomicwrites==1.4.1
      - attrs==25.3.0
      - click==8.1.8
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - hypothesis==6.130.5
      - icalendar==6.1.3
      - iniconfig==2.1.0
      - packaging==24.2
      - parsedatetime==2.6
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - python-dateutil==2.9.0.post0
      - pyxdg==0.28
      - six==1.17.0
      - sortedcontainers==2.4.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - tzdata==2025.2
      - urwid==2.6.16
      - wcwidth==0.2.13
prefix: /opt/conda/envs/todoman

tests/test_filtering.py::test_due_aware tests/test_filtering.py::test_due_naive
tests/test_filtering.py::test_all tests/test_filtering.py::test_urgent tests/test_filtering.py::test_location tests/test_filtering.py::test_category tests/test_filtering.py::test_grep tests/test_filtering.py::test_filtering_lists
todoman/cli.py todoman/model.py
todoman/cli.py todoman/model.py