diff --git a/intercom/traits/api_resource.py b/intercom/traits/api_resource.py index af4090f3..5a8e81a2 100644 --- a/intercom/traits/api_resource.py +++ b/intercom/traits/api_resource.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- +import calendar import datetime import time +from pytz import utc from intercom.lib.flat_store import FlatStore from intercom.lib.typed_json_deserializer import JsonDeserializer @@ -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): @@ -93,7 +95,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..7a291aa3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ inflection==0.3.0 requests==2.6.0 urllib3==1.10.2 six==1.9.0 +pytz==2015.4 diff --git a/setup.py b/setup.py index 7af6c2de..c665ad08 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,6 @@ classifiers=[], 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 59add0cf..a46833a4 100644 --- a/tests/unit/test_user.py +++ b/tests/unit/test_user.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import calendar import json import mock @@ -32,13 +33,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) @@ -60,9 +61,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 @@ -79,14 +80,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) @@ -129,7 +130,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 @@ -317,7 +318,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'] @@ -392,7 +393,7 @@ def setUp(self): # noqa 'custom_attributes': { 'mad': 123, 'another': 432, - 'other': time.mktime(created_at.timetuple()), + 'other': calendar.timegm(created_at.utctimetuple()), 'thing': 'yay' } } diff --git a/tests/unit/traits/test_api_resource.py b/tests/unit/traits/test_api_resource.py index 92a73ee5..1324bba9 100644 --- a/tests/unit/traits/test_api_resource.py +++ b/tests/unit/traits/test_api_resource.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- +import calendar import unittest from datetime import datetime +from pytz import utc from intercom.traits.api_resource import Resource from nose.tools import assert_raises from nose.tools import eq_ @@ -34,7 +36,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 +58,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