Skip to content

Commit a92f381

Browse files
committed
Ensure UTC datetimes (via intercom#102).
1 parent c591bd5 commit a92f381

File tree

6 files changed

+30
-21
lines changed

6 files changed

+30
-21
lines changed

intercom/request.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from . import errors
44
from datetime import datetime
5+
from pytz import utc
56

67
import certifi
78
import json
@@ -93,7 +94,8 @@ def set_rate_limit_details(self, resp):
9394
if remaining:
9495
rate_limit_details['remaining'] = int(remaining)
9596
if reset:
96-
rate_limit_details['reset_at'] = datetime.fromtimestamp(int(reset))
97+
reset_at = datetime.utcfromtimestamp(int(reset)).replace(tzinfo=utc)
98+
rate_limit_details['reset_at'] = reset_at
9799
self.rate_limit_details = rate_limit_details
98100

99101
def raise_errors_on_failure(self, resp):

intercom/traits/api_resource.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# -*- coding: utf-8 -*-
22

3+
import calendar
34
import datetime
45
import time
56

67
from intercom.lib.flat_store import FlatStore
78
from intercom.lib.typed_json_deserializer import JsonDeserializer
9+
from pytz import utc
810

911

1012
def type_field(attribute):
@@ -29,7 +31,7 @@ def datetime_value(value):
2931

3032
def to_datetime_value(value):
3133
if value:
32-
return datetime.datetime.fromtimestamp(int(value))
34+
return datetime.datetime.utcfromtimestamp(int(value)).replace(tzinfo=utc)
3335

3436

3537
class Resource(object):
@@ -105,7 +107,7 @@ def __setattr__(self, attribute, value):
105107
elif self._flat_store_attribute(attribute):
106108
value_to_set = FlatStore(value)
107109
elif timestamp_field(attribute) and datetime_value(value):
108-
value_to_set = time.mktime(value.timetuple())
110+
value_to_set = calendar.timegm(value.utctimetuple())
109111
else:
110112
value_to_set = value
111113
if attribute != 'changed_attributes':

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#
44
certifi
55
inflection==0.3.0
6+
pytz==2016.7
67
requests==2.6.0
78
urllib3==1.10.2
89
six==1.9.0

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@
3636
],
3737
packages=find_packages(),
3838
include_package_data=True,
39-
install_requires=["requests", "inflection", "certifi", "six"],
39+
install_requires=["requests", "inflection", "certifi", "six", "pytz"],
4040
zip_safe=False
4141
)

tests/unit/test_user.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3+
import calendar
34
import json
45
import mock
56
import time
@@ -35,13 +36,13 @@ def it_to_dict_itself(self):
3536
as_dict = user.to_dict()
3637
eq_(as_dict["email"], "[email protected]")
3738
eq_(as_dict["user_id"], "12345")
38-
eq_(as_dict["created_at"], time.mktime(created_at.timetuple()))
39+
eq_(as_dict["created_at"], calendar.timegm(created_at.utctimetuple()))
3940
eq_(as_dict["name"], "Jim Bob")
4041

4142
@istest
4243
def it_presents_created_at_and_last_impression_at_as_datetime(self):
4344
now = datetime.utcnow()
44-
now_ts = time.mktime(now.timetuple())
45+
now_ts = calendar.timegm(now.utctimetuple())
4546
user = User.from_api(
4647
{'created_at': now_ts, 'last_impression_at': now_ts})
4748
self.assertIsInstance(user.created_at, datetime)
@@ -63,9 +64,9 @@ def it_presents_a_complete_user_record_correctly(self):
6364
eq_('Joe Schmoe', user.name)
6465
eq_('the-app-id', user.app_id)
6566
eq_(123, user.session_count)
66-
eq_(1401970114, time.mktime(user.created_at.timetuple()))
67-
eq_(1393613864, time.mktime(user.remote_created_at.timetuple()))
68-
eq_(1401970114, time.mktime(user.updated_at.timetuple()))
67+
eq_(1401970114, calendar.timegm(user.created_at.utctimetuple()))
68+
eq_(1393613864, calendar.timegm(user.remote_created_at.utctimetuple()))
69+
eq_(1401970114, calendar.timegm(user.updated_at.utctimetuple()))
6970

7071
Avatar = create_class_instance('Avatar') # noqa
7172
Company = create_class_instance('Company') # noqa
@@ -82,14 +83,14 @@ def it_presents_a_complete_user_record_correctly(self):
8283
eq_('bbbbbbbbbbbbbbbbbbbbbbbb', user.companies[0].id)
8384
eq_('the-app-id', user.companies[0].app_id)
8485
eq_('Company 1', user.companies[0].name)
85-
eq_(1390936440, time.mktime(
86-
user.companies[0].remote_created_at.timetuple()))
87-
eq_(1401970114, time.mktime(
88-
user.companies[0].created_at.timetuple()))
89-
eq_(1401970114, time.mktime(
90-
user.companies[0].updated_at.timetuple()))
91-
eq_(1401970113, time.mktime(
92-
user.companies[0].last_request_at.timetuple()))
86+
eq_(1390936440, calendar.timegm(
87+
user.companies[0].remote_created_at.utctimetuple()))
88+
eq_(1401970114, calendar.timegm(
89+
user.companies[0].created_at.utctimetuple()))
90+
eq_(1401970114, calendar.timegm(
91+
user.companies[0].updated_at.utctimetuple()))
92+
eq_(1401970113, calendar.timegm(
93+
user.companies[0].last_request_at.utctimetuple()))
9394
eq_(0, user.companies[0].monthly_spend)
9495
eq_(0, user.companies[0].session_count)
9596
eq_(1, user.companies[0].user_count)
@@ -134,7 +135,7 @@ def it_allows_update_last_request_at(self):
134135
@istest
135136
def it_allows_easy_setting_of_custom_data(self):
136137
now = datetime.utcnow()
137-
now_ts = time.mktime(now.timetuple())
138+
now_ts = calendar.timegm(now.utctimetuple())
138139

139140
user = User()
140141
user.custom_attributes["mad"] = 123
@@ -324,7 +325,7 @@ def it_gets_sets_rw_keys(self):
324325
'name': 'Bob Smith',
325326
'last_seen_ip': '1.2.3.4',
326327
'last_seen_user_agent': 'ie6',
327-
'created_at': time.mktime(created_at.timetuple())
328+
'created_at': calendar.timegm(created_at.utctimetuple())
328329
}
329330
user = User(**payload)
330331
expected_keys = ['custom_attributes']

tests/unit/traits/test_api_resource.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from nose.tools import eq_
99
from nose.tools import ok_
1010
from nose.tools import istest
11+
from pytz import utc
1112

1213

1314
class IntercomTraitsApiResource(unittest.TestCase):
@@ -34,7 +35,8 @@ def it_does_not_set_type_on_parsing_json(self):
3435

3536
@istest
3637
def it_coerces_time_on_parsing_json(self):
37-
eq_(datetime.fromtimestamp(1374056196), self.api_resource.created_at)
38+
dt = datetime.utcfromtimestamp(1374056196).replace(tzinfo=utc)
39+
eq_(dt, self.api_resource.created_at)
3840

3941
@istest
4042
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):
5557
@istest
5658
def it_exposes_dates_correctly_for_dynamically_defined_getters(self):
5759
self.api_resource.foo_at = 1401200468
58-
eq_(datetime.fromtimestamp(1401200468), self.api_resource.foo_at)
60+
dt = datetime.utcfromtimestamp(1401200468).replace(tzinfo=utc)
61+
eq_(dt, self.api_resource.foo_at)
5962

6063
@istest
6164
def it_throws_regular_error_when_non_existant_getter_is_called_that_is_backed_by_an_instance_variable(self): # noqa

0 commit comments

Comments
 (0)