Repository: bokeh/bokeh

Issue:
bokeh.util.serialization.py  convert_datetime_type breaks when passed timezone aware datetime
After updating to bokeh 0.12.6 the convert_datetime_type became unable to handle datetime objecs that are timezone aware.  The subtraction of DT_EPOCH which is a naive datetime from a obj which might be aware or naive fails when obj is timezone aware.    The error indicates that subtraction of a datetime object and a naive object is not supported.

This occurs with python 2.7.

proposed fix to serialization.py attached.

$ pip freeze
appdirs==1.4.3
awsebcli==3.7.7
backports-abc==0.4
backports.ssl-match-hostname==3.5.0.1
bkcharts==0.2
blessed==1.9.5
bokeh==0.12.6
botocore==1.4.40
cement==2.8.2
certifi==2016.2.28
colorama==0.3.7
cycler==0.10.0
DateTime==4.1.1
Django==1.9.8
docker-py==1.7.2
dockerpty==0.4.1
docopt==0.6.2
docutils==0.12
futures==3.0.5
Jinja2==2.8
jmespath==0.9.0
MarkupSafe==0.23
msgpack-python==0.4.8
numpy==1.11.1
packaging==16.8
pandas==0.18.1
pathspec==0.3.4
pyasn1==0.1.9
pygobject==3.18.2
pyparsing==2.1.5
python-dateutil==2.5.3
pytz==2016.6.1
PyYAML==3.11
requests==2.9.1
rsa==3.4.2
s3transfer==0.0.1
semantic-version==2.5.0
singledispatch==3.4.0.3
six==1.10.0
tabulate==0.7.5
td-client==0.5.0
termcolor==1.1.0
texttable==0.8.4
tornado==4.4.1
tzlocal==1.2.2
urllib3==1.17
virtualenv==15.0.2
wcwidth==0.1.7
websocket-client==0.37.0
zope.interface==4.2.0

[serialization.py.txt](https://github.com/bokeh/bokeh/files/1078483/serialization.py.txt)
 


Discussion:

Patch:
diff --git a/bokeh/util/serialization.py b/bokeh/util/serialization.py
index 96c406a88..dd6d27e78 100644
--- a/bokeh/util/serialization.py
+++ b/bokeh/util/serialization.py
@@ -101,7 +101,8 @@ def convert_datetime_type(obj):
 
     # Datetime (datetime is a subclass of date)
     elif isinstance(obj, dt.datetime):
-        return (obj - DT_EPOCH).total_seconds() * 1000. + obj.microsecond / 1000.
+        diff = obj.replace(tzinfo=None) - DT_EPOCH
+        return diff.total_seconds() * 1000. + obj.microsecond / 1000.
 
     # Timedelta (timedelta is class in the datetime library)
     elif isinstance(obj, dt.timedelta):


Test patch:
diff --git a/bokeh/util/tests/test_serialization.py b/bokeh/util/tests/test_serialization.py
index c664a5c9a..501f2f679 100644
--- a/bokeh/util/tests/test_serialization.py
+++ b/bokeh/util/tests/test_serialization.py
@@ -6,6 +6,7 @@ import base64
 import pytest
 import numpy as np
 import pandas as pd
+import pytz
 
 import bokeh.util.serialization as bus
 
@@ -60,6 +61,12 @@ def test_convert_datetime_type():
     assert bus.convert_datetime_type(pd.Timedelta("3000ms")) == 3000.0
     assert bus.convert_datetime_type(bus._pd_timestamp(3000000)) == 3.0
 
+def test_convert_datetime_type_with_tz():
+    # This ensures datetimes are sent to BokehJS timezone-naive
+    # see https://github.com/bokeh/bokeh/issues/6480
+    for tz in pytz.all_timezones:
+        assert bus.convert_datetime_type(datetime.datetime(2016, 5, 11, tzinfo=datetime.tzinfo(tz))) == 1462924800000.0
+
 testing = [[float('nan'), 3], [float('-inf'), [float('inf')]]]
 expected = [['NaN', 3.0], ['-Infinity', ['Infinity']]]