From a92f381cc89049fc7ab672ff9ade30b336c4dfaf Mon Sep 17 00:00:00 2001 From: John Keyes Date: Wed, 16 Nov 2016 12:28:37 +0000 Subject: [PATCH] Ensure UTC datetimes (via #102). --- intercom/request.py | 4 +++- intercom/traits/api_resource.py | 6 +++-- requirements.txt | 1 + setup.py | 2 +- tests/unit/test_user.py | 31 +++++++++++++------------- tests/unit/traits/test_api_resource.py | 7 ++++-- 6 files changed, 30 insertions(+), 21 deletions(-) diff --git a/intercom/request.py b/intercom/request.py index dfca9925..97161490 100644 --- a/intercom/request.py +++ b/intercom/request.py @@ -2,6 +2,7 @@ from . import errors from datetime import datetime +from pytz import utc import certifi import json @@ -93,7 +94,8 @@ def set_rate_limit_details(self, resp): if remaining: rate_limit_details['remaining'] = int(remaining) if reset: - rate_limit_details['reset_at'] = datetime.fromtimestamp(int(reset)) + reset_at = datetime.utcfromtimestamp(int(reset)).replace(tzinfo=utc) + rate_limit_details['reset_at'] = reset_at self.rate_limit_details = rate_limit_details def raise_errors_on_failure(self, resp): diff --git a/intercom/traits/api_resource.py b/intercom/traits/api_resource.py index 524efbc7..e9ac2b70 100644 --- a/intercom/traits/api_resource.py +++ b/intercom/traits/api_resource.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- +import calendar import datetime import time from intercom.lib.flat_store import FlatStore from intercom.lib.typed_json_deserializer import JsonDeserializer +from pytz import utc def type_field(attribute): @@ -29,7 +31,7 @@ def datetime_value(value): def to_datetime_value(value): if value: - return datetime.datetime.fromtimestamp(int(value)) + return datetime.datetime.utcfromtimestamp(int(value)).replace(tzinfo=utc) class Resource(object): @@ -105,7 +107,7 @@ def __setattr__(self, attribute, value): elif self._flat_store_attribute(attribute): value_to_set = FlatStore(value) elif timestamp_field(attribute) and datetime_value(value): - value_to_set = time.mktime(value.timetuple()) + value_to_set = calendar.timegm(value.utctimetuple()) else: value_to_set = value if attribute != 'changed_attributes': diff --git a/requirements.txt b/requirements.txt index 571df20b..5f886cbd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ # certifi inflection==0.3.0 +pytz==2016.7 requests==2.6.0 urllib3==1.10.2 six==1.9.0 diff --git a/setup.py b/setup.py index 9fa5e18b..3b5bcde8 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,6 @@ ], packages=find_packages(), include_package_data=True, - install_requires=["requests", "inflection", "certifi", "six"], + install_requires=["requests", "inflection", "certifi", "six", "pytz"], zip_safe=False ) diff --git a/tests/unit/test_user.py b/tests/unit/test_user.py index 63132033..382674dd 100644 --- a/tests/unit/test_user.py +++ b/tests/unit/test_user.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import calendar import json import mock import time @@ -35,13 +36,13 @@ def it_to_dict_itself(self): as_dict = user.to_dict() eq_(as_dict["email"], "jim@example.com") eq_(as_dict["user_id"], "12345") - eq_(as_dict["created_at"], time.mktime(created_at.timetuple())) + eq_(as_dict["created_at"], calendar.timegm(created_at.utctimetuple())) eq_(as_dict["name"], "Jim Bob") @istest def it_presents_created_at_and_last_impression_at_as_datetime(self): now = datetime.utcnow() - now_ts = time.mktime(now.timetuple()) + now_ts = calendar.timegm(now.utctimetuple()) user = User.from_api( {'created_at': now_ts, 'last_impression_at': now_ts}) self.assertIsInstance(user.created_at, datetime) @@ -63,9 +64,9 @@ def it_presents_a_complete_user_record_correctly(self): eq_('Joe Schmoe', user.name) eq_('the-app-id', user.app_id) eq_(123, user.session_count) - eq_(1401970114, time.mktime(user.created_at.timetuple())) - eq_(1393613864, time.mktime(user.remote_created_at.timetuple())) - eq_(1401970114, time.mktime(user.updated_at.timetuple())) + eq_(1401970114, calendar.timegm(user.created_at.utctimetuple())) + eq_(1393613864, calendar.timegm(user.remote_created_at.utctimetuple())) + eq_(1401970114, calendar.timegm(user.updated_at.utctimetuple())) Avatar = create_class_instance('Avatar') # noqa Company = create_class_instance('Company') # noqa @@ -82,14 +83,14 @@ def it_presents_a_complete_user_record_correctly(self): eq_('bbbbbbbbbbbbbbbbbbbbbbbb', user.companies[0].id) eq_('the-app-id', user.companies[0].app_id) eq_('Company 1', user.companies[0].name) - eq_(1390936440, time.mktime( - user.companies[0].remote_created_at.timetuple())) - eq_(1401970114, time.mktime( - user.companies[0].created_at.timetuple())) - eq_(1401970114, time.mktime( - user.companies[0].updated_at.timetuple())) - eq_(1401970113, time.mktime( - user.companies[0].last_request_at.timetuple())) + eq_(1390936440, calendar.timegm( + user.companies[0].remote_created_at.utctimetuple())) + eq_(1401970114, calendar.timegm( + user.companies[0].created_at.utctimetuple())) + eq_(1401970114, calendar.timegm( + user.companies[0].updated_at.utctimetuple())) + eq_(1401970113, calendar.timegm( + user.companies[0].last_request_at.utctimetuple())) eq_(0, user.companies[0].monthly_spend) eq_(0, user.companies[0].session_count) eq_(1, user.companies[0].user_count) @@ -134,7 +135,7 @@ def it_allows_update_last_request_at(self): @istest def it_allows_easy_setting_of_custom_data(self): now = datetime.utcnow() - now_ts = time.mktime(now.timetuple()) + now_ts = calendar.timegm(now.utctimetuple()) user = User() user.custom_attributes["mad"] = 123 @@ -324,7 +325,7 @@ def it_gets_sets_rw_keys(self): 'name': 'Bob Smith', 'last_seen_ip': '1.2.3.4', 'last_seen_user_agent': 'ie6', - 'created_at': time.mktime(created_at.timetuple()) + 'created_at': calendar.timegm(created_at.utctimetuple()) } user = User(**payload) expected_keys = ['custom_attributes'] diff --git a/tests/unit/traits/test_api_resource.py b/tests/unit/traits/test_api_resource.py index 464a32ec..69dad9cf 100644 --- a/tests/unit/traits/test_api_resource.py +++ b/tests/unit/traits/test_api_resource.py @@ -8,6 +8,7 @@ from nose.tools import eq_ from nose.tools import ok_ from nose.tools import istest +from pytz import utc class IntercomTraitsApiResource(unittest.TestCase): @@ -34,7 +35,8 @@ def it_does_not_set_type_on_parsing_json(self): @istest def it_coerces_time_on_parsing_json(self): - eq_(datetime.fromtimestamp(1374056196), self.api_resource.created_at) + dt = datetime.utcfromtimestamp(1374056196).replace(tzinfo=utc) + eq_(dt, self.api_resource.created_at) @istest def it_dynamically_defines_accessors_for_non_existent_properties(self): @@ -55,7 +57,8 @@ def it_accepts_unix_timestamps_into_dynamically_defined_date_setters(self): @istest def it_exposes_dates_correctly_for_dynamically_defined_getters(self): self.api_resource.foo_at = 1401200468 - eq_(datetime.fromtimestamp(1401200468), self.api_resource.foo_at) + dt = datetime.utcfromtimestamp(1401200468).replace(tzinfo=utc) + eq_(dt, self.api_resource.foo_at) @istest def it_throws_regular_error_when_non_existant_getter_is_called_that_is_backed_by_an_instance_variable(self): # noqa