From 0516cc10c229e4e0625c5a8ed3e1e145ff153fe4 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sun, 2 Feb 2014 15:08:01 -0800 Subject: [PATCH] Serialize incoming zoned date/datetime instances into strings of the appropriate format before relaying to taskwarrior. --- taskw/test/test_utils.py | 71 +++++++++++++++++++++++++++++++++++++++- taskw/utils.py | 26 +++++++++++---- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/taskw/test/test_utils.py b/taskw/test/test_utils.py index f2eab0b..1f08729 100644 --- a/taskw/test/test_utils.py +++ b/taskw/test/test_utils.py @@ -1,8 +1,13 @@ +import datetime import random +import dateutil.tz from nose.tools import eq_ +import pytz -from taskw.utils import decode_task, encode_task +from taskw.utils import ( + decode_task, encode_task, encode_task_experimental, DATE_FORMAT +) TASK = {'description': "task 2 http://www.google.com/", 'entry': "1325011643", @@ -88,3 +93,67 @@ def test_ordering(self): task1 = dict(shuffled(TASK.items())) task2 = dict(shuffled(TASK.items())) eq_(encode_task(task1), encode_task(task2)) + + def test_encodes_dates(self): + arbitrary_date = datetime.date(2014, 3, 2) + task = { + 'arbitrary_field': arbitrary_date + } + + actual_encoded_task = encode_task_experimental(task) + expected_encoded_task = encode_task_experimental( + { + 'arbitrary_field': arbitrary_date.strftime(DATE_FORMAT) + } + ) + + eq_( + actual_encoded_task, + expected_encoded_task, + ) + + def test_encodes_naive_datetimes(self): + arbitrary_naive_datetime = datetime.datetime.now() + task = { + 'arbitrary_field': arbitrary_naive_datetime + } + + actual_encoded_task = encode_task_experimental(task) + expected_encoded_task = encode_task_experimental( + { + 'arbitrary_field': ( + arbitrary_naive_datetime + .replace(tzinfo=dateutil.tz.tzlocal()) + .astimezone(pytz.utc).strftime(DATE_FORMAT) + ) + } + ) + + eq_( + actual_encoded_task, + expected_encoded_task, + ) + + def test_encodes_zoned_datetimes(self): + arbitrary_timezone = pytz.timezone('America/Los_Angeles') + arbitrary_zoned_datetime = datetime.datetime.now().replace( + tzinfo=arbitrary_timezone + ) + task = { + 'arbitrary_field': arbitrary_zoned_datetime + } + + actual_encoded_task = encode_task_experimental(task) + expected_encoded_task = encode_task_experimental( + { + 'arbitrary_field': ( + arbitrary_zoned_datetime + .astimezone(pytz.utc).strftime(DATE_FORMAT) + ) + } + ) + + eq_( + actual_encoded_task, + expected_encoded_task, + ) diff --git a/taskw/utils.py b/taskw/utils.py index 7ade682..bf3ac30 100644 --- a/taskw/utils.py +++ b/taskw/utils.py @@ -1,15 +1,21 @@ """ Various utilties """ +import datetime import re from operator import itemgetter -import six -import datetime try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict +import dateutil.tz +import pytz +import six + +DATE_FORMAT = '%Y%m%dT%H%M%SZ' + + encode_replacements = OrderedDict([ ('\\', '\\\\'), ('\"', '&dquot;'), @@ -48,12 +54,18 @@ def encode_task_experimental(task): if 'tags' in task: task['tags'] = ','.join(task['tags']) for k in task: - for unsafe, safe in six.iteritems(encode_replacements_experimental): - if isinstance(task[k], basestring): - task[k] = task[k].replace(unsafe, safe) - if isinstance(task[k], datetime.datetime): - task[k] = task[k].strftime("%Y%m%dT%M%H%SZ") + if not task[k].tzinfo: + # Dates not having timezone information should be + # assumed to be in local time + task[k] = task[k].replace(tzinfo=dateutil.tz.tzlocal()) + # All times should be converted to UTC before serializing + task[k] = task[k].astimezone(pytz.utc).strftime(DATE_FORMAT) + elif isinstance(task[k], datetime.date): + task[k] = task[k].strftime(DATE_FORMAT) + elif isinstance(task[k], six.string_types): + for unsafe, safe in six.iteritems(encode_replacements_experimental): + task[k] = task[k].replace(unsafe, safe) # Then, format it as a string return "%s\n" % " ".join([