Repository: pimutils/todoman
Pull request #182 (2017-03-07 01:52:51)

Date parsing tests


Patch:
diff --git a/todoman/formatters.py b/todoman/formatters.py
index 09c16d2..7073f65 100644
--- a/todoman/formatters.py
+++ b/todoman/formatters.py
@@ -5,13 +5,15 @@ from time import mktime
 import click
 import humanize
 import parsedatetime
+import pytz
 from dateutil.tz import tzlocal
 from tabulate import tabulate
 
 
 class DefaultFormatter:
 
-    def __init__(self, date_format, time_format, dt_separator):
+    def __init__(self, date_format='%Y-%m-%d', time_format='%H:%M',
+                 dt_separator=' ', tz_override=None):
         self.date_format = date_format
         self.time_format = time_format
         self.dt_separator = dt_separator
@@ -19,8 +21,8 @@ class DefaultFormatter:
             date_format, time_format
         )))
 
-        self._localtimezone = tzlocal()
-        self.now = datetime.datetime.now().replace(tzinfo=self._localtimezone)
+        self.tz = tz_override or tzlocal()
+        self.now = datetime.datetime.now().replace(tzinfo=self.tz)
 
         self._parsedatetime_calendar = parsedatetime.Calendar()
 
@@ -133,7 +135,7 @@ class DefaultFormatter:
             return None
 
         rv = self._parse_datetime_naive(dt)
-        return rv.replace(tzinfo=self._localtimezone)
+        return rv.replace(tzinfo=self.tz)
 
     def _parse_datetime_naive(self, dt):
         try:
@@ -181,9 +183,6 @@ class HumanizedFormatter(DefaultFormatter):
 
 class PorcelainFormatter(DefaultFormatter):
 
-    def __init__(self, *args, **kwargs):
-        pass
-
     def _todo_as_dict(self, todo):
         return dict(
             completed=todo.is_completed,
@@ -227,6 +226,6 @@ class PorcelainFormatter(DefaultFormatter):
 
     def parse_datetime(self, value):
         if value:
-            return datetime.datetime.fromtimestamp(value)
+            return datetime.datetime.fromtimestamp(value, tz=pytz.UTC)
         else:
             return None


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 4ed93b4..0acb9eb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,7 +9,7 @@ from dateutil.tz import tzlocal
 from hypothesis import HealthCheck, settings, Verbosity
 
 from todoman import model
-from todoman.formatters import DefaultFormatter
+from todoman.formatters import DefaultFormatter, HumanizedFormatter
 
 
 @pytest.fixture
@@ -75,6 +75,7 @@ def todo_factory(default_database):
     def inner(
             description=None,
             due=None,
+            id=None,
             location=None,
             priority=None,
             summary='YARR!',
@@ -84,6 +85,7 @@ def todo_factory(default_database):
 
         todo.description = description
         todo.due = due
+        todo.id = id
         todo.location = location
         todo.priority = priority
         todo.summary = summary
@@ -97,7 +99,14 @@ def todo_factory(default_database):
 
 @pytest.fixture
 def default_formatter():
-    return DefaultFormatter("%Y-%m-%d", "%H:%M", ' ')
+    formatter = DefaultFormatter(tz_override=pytz.timezone('CET'))
+    return formatter
+
+
+@pytest.fixture
+def humanized_formatter():
+    formatter = HumanizedFormatter(tz_override=pytz.timezone('CET'))
+    return formatter
 
 
 settings.register_profile("ci", settings(
diff --git a/tests/test_cli.py b/tests/test_cli.py
index db80fcc..4b7ab9c 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -11,6 +11,8 @@ from hypothesis import given
 from todoman.cli import cli
 from todoman.model import Database, FileTodo
 
+# TODO: test --grep
+
 
 def test_list(tmpdir, runner, create):
     result = runner.invoke(cli, ['list'], catch_exceptions=False)
@@ -499,4 +501,12 @@ def test_humanize_interactive(runner):
     assert result.output.strip() == \
         "Error: --porcelain and --humanize cannot be used at the same time."
 
-# TODO: test --grep
+
+def test_due_bad_date(runner):
+    result = runner.invoke(cli, ['new', '--due', 'Not a date', 'Blargh!'])
+
+    assert result.exception
+    assert (
+        'Error: Invalid value for "--due" / "-d": Time description not '
+        'recognized: Not a date' == result.output.strip().splitlines()[-1]
+    )
diff --git a/tests/test_formatter.py b/tests/test_formatter.py
index caa6011..f430ca9 100644
--- a/tests/test_formatter.py
+++ b/tests/test_formatter.py
@@ -1,6 +1,8 @@
-from datetime import date, datetime, timedelta
+from datetime import date, datetime, time, timedelta
 
 import pytest
+import pytz
+from freezegun import freeze_time
 
 from todoman.cli import cli
 
@@ -72,3 +74,59 @@ def test_detailed_format(runner, todo_factory):
     )
     assert not result.exception
     assert result.output.strip() == expected
+
+
+def test_parse_time(default_formatter):
+    tz = pytz.timezone('CET')
+    parsed = default_formatter.parse_datetime('12:00')
+    expected = datetime.combine(
+        date.today(),
+        time(hour=12, minute=0),
+    ).replace(tzinfo=tz)
+    assert parsed == expected
+
+
+def test_parse_datetime(default_formatter):
+    tz = pytz.timezone('CET')
+
+    parsed = default_formatter.parse_datetime('2017-03-05')
+    assert parsed == datetime(2017, 3, 5).replace(tzinfo=tz)
+
+    parsed = default_formatter.parse_datetime('2017-03-05 12:00')
+    assert parsed == datetime(2017, 3, 5, 12).replace(tzinfo=tz)
+
+    # Notes. will round to the NEXT matching date, so we need to freeze time
+    # for this one:
+    with freeze_time('2017-03-04'):
+        parsed = default_formatter.parse_datetime(
+            'Mon Mar  6 22:50:52 -03 2017'
+        )
+    assert parsed == datetime(2017, 3, 6, 20, 17).replace(tzinfo=tz)
+
+    assert default_formatter.parse_datetime('') is None
+
+    assert default_formatter.parse_datetime(None) is None
+
+
+def test_humanized_parse_datetime(humanized_formatter):
+    tz = pytz.timezone('CET')
+
+    humanized_formatter.now = datetime(2017, 3, 6, 22, 17).replace(tzinfo=tz)
+    dt = datetime(2017, 3, 6, 20, 17).replace(tzinfo=tz)
+
+    assert humanized_formatter.format_datetime(dt) == '2 hours ago'
+    assert humanized_formatter.format_datetime(None) == ''
+
+
+def test_simple_action(default_formatter, todo_factory):
+    todo = todo_factory()
+    assert default_formatter.simple_action('Delete', todo) == \
+        'Delete "YARR!"'
+
+
+def test_formatting_parsing_consitency(default_formatter):
+    tz = pytz.timezone('CET')
+    dt = datetime(2017, 3, 8, 21, 6).replace(tzinfo=tz)
+
+    formatted = default_formatter.format_datetime(dt)
+    assert default_formatter.parse_datetime(formatted) == dt
diff --git a/tests/test_porcelain.py b/tests/test_porcelain.py
index 9e28206..a3d91f4 100644
--- a/tests/test_porcelain.py
+++ b/tests/test_porcelain.py
@@ -1,6 +1,10 @@
 import json
+from datetime import datetime
+
+import pytz
 
 from todoman.cli import cli
+from todoman.formatters import PorcelainFormatter
 
 
 def test_list_all(tmpdir, runner, create):
@@ -136,3 +140,48 @@ def test_show(tmpdir, runner, create):
     assert not result.exception
     assert result.output.strip() == json.dumps(expected, indent=4,
                                                sort_keys=True)
+
+
+def test_simple_action(todo_factory):
+    formatter = PorcelainFormatter()
+    todo = todo_factory(id=7, location='Downtown')
+
+    expected = {
+        "completed": False,
+        "due": None,
+        "id": 7,
+        "list": "default",
+        "percent": 0,
+        "priority": 0,
+        "summary": "YARR!"
+    }
+
+    assert formatter.simple_action('Delete', todo) == \
+        json.dumps(expected, indent=4, sort_keys=True)
+
+
+def test_format_datetime():
+    formatter = PorcelainFormatter()
+
+    dt = datetime(2017, 3, 8, 0, 0, 17, 457955, tzinfo=pytz.UTC)
+    t = 1488931217
+
+    assert formatter.format_datetime(dt) == t
+
+
+def test_parse_datetime():
+    formatter = PorcelainFormatter()
+
+    expected = datetime(2017, 3, 6, 23, 22, 21, 610429, tzinfo=pytz.UTC)
+    assert formatter.parse_datetime(1488842541.610429) == expected
+
+    assert formatter.parse_datetime(None) is None
+    assert formatter.parse_datetime('') is None
+
+
+def test_formatting_parsing_consitency():
+    tz = pytz.timezone('CET')
+    dt = datetime(2017, 3, 8, 21, 6, 19).replace(tzinfo=tz)
+
+    formatter = PorcelainFormatter(tz_override=tz)
+    assert formatter.parse_datetime(formatter.format_datetime(dt)) == dt
diff --git a/tests/test_ui.py b/tests/test_ui.py
index 4669a89..88f4698 100644
--- a/tests/test_ui.py
+++ b/tests/test_ui.py
@@ -47,7 +47,7 @@ def test_todo_editor_due(default_database, todo_factory, default_formatter):
 
     todo = todo_factory(due=datetime(2017, 3, 4, 14))
     lists = list(default_database.lists())
-    default_formatter._localtimezone = tz
+    default_formatter.tz = tz
 
     editor = TodoEditor(todo, lists, default_formatter)
     assert editor._due.edit_text == '2017-03-04 14:00'