Repository: bokeh/bokeh

Issue:
datetime (convert_datetime_type) seems to add in extra milliseconds
# READ AND FOLLOW THESE INSTRUCTIONS CAREFULLY

*ISSUES THAT DO NOT CONTAIN NECESSARY INFORMATION MAY BE CLOSED, IMMEDIATELY*

The issue tracker is NOT the place for general support. For questions and
technical assistance, come ask the [Bokeh mailing list](https://groups.google.com/a/continuum.io/forum/#!forum/bokeh) or join the chat on [Gitter](https://gitter.im/bokeh/bokeh). For feature requests, please provide a detailed description or proposal of the new capability or behavior.

For defects or deficiencies, please provide ALL OF THE FOLLOWING:

#### ALL software version info (bokeh, python, notebook, OS, browser, any other relevant packages)
version_json = '''
{
 "date": "2017-06-13T09:22:29-0500",
 "dirty": false,
 "error": null,
 "full-revisionid": "6070bf96d9367150f423a4fa792a7d242421236b",
 "version": "0.12.6"
}

#### Description of expected behavior and the observed behavior
the function (snippet shown)
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.

that converts a datetime to json for use in plotting seems to not compute the timestamp properly.


#### Complete, minimal, self-contained example code that reproduces the issue
when I do this by hand with a datetime that has non-zero microseconds,

For the unix timestamp 
1501131481.011143 . which has a converted d
```
>>> import datetime
>>> dt = datetime.datetime.fromtimestamp(1501131481.011143)
>>> dt
datetime.datetime(2017, 7, 26, 21, 58, 1, 11143)
>>> DT_EPOCH=datetime.datetime.utcfromtimestamp(0)
>>> dt-DT_EPOCH
datetime.timedelta(17373, 79081, 11143)
>>> (dt-DT_EPOCH).total_seconds()
1501106281.011143
>>> (dt-DT_EPOCH).total_seconds()*1000.
1501106281011.143
>>> dt.microsecond/1000.
11.143
>>> (dt-DT_EPOCH).total_seconds()*1000.+dt.microsecond/1000.
1501106281022.2861
>>> ((dt-DT_EPOCH).total_seconds()*1000.+dt.microsecond/1000.) - (1501131481.011143 * 1000.)
-25199988.856933594
```
If anything, the microseconds should be zero since we're just comparing milliseconds_since_epoch to seconds_since_epoch * 1000.  The ~25,200,000 difference is due to the naive datetime conversion.  That's a 7 hour offset from GMT.

It seems that total_seconds() already gives the fractional seconds, so no need to add in microseconds.

While this doesn't make much difference for data points with second resolution, subsecond resolution results in shifting.

(i'll submit a patch if it isn't already fixed.)

#### Stack traceback and/or browser JavaScript console output

#### Screenshots or screencasts of the bug in action



Discussion:
Time zone issues should be handled at this point but a mailing list post recently noted the issue with the sub-second component. After a (very) brief look, it seems like the milliseconds component wa doubled which would be explained by the hypotheses above. 

Resolving patch:
diff --git a/bokeh/util/serialization.py b/bokeh/util/serialization.py
--- a/bokeh/util/serialization.py
+++ b/bokeh/util/serialization.py
@@ -101,7 +101,7 @@ def convert_datetime_type(obj):
     # Datetime (datetime is a subclass of date)
     elif isinstance(obj, dt.datetime):
         diff = obj.replace(tzinfo=None) - DT_EPOCH
-        return diff.total_seconds() * 1000. + obj.microsecond / 1000.
+        return diff.total_seconds() * 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
--- a/bokeh/util/tests/test_serialization.py
+++ b/bokeh/util/tests/test_serialization.py
@@ -52,6 +52,8 @@ def test_is_datetime_type():
     assert bus.is_datetime_type(bus._pd_timestamp(3000000))
 
 def test_convert_datetime_type():
+    assert bus.convert_datetime_type(datetime.datetime(2018, 1, 3, 15, 37, 59, 922452)) == 1514993879922.452
+    assert bus.convert_datetime_type(datetime.datetime(2018, 1, 3, 15, 37, 59)) == 1514993879000.0
     assert bus.convert_datetime_type(datetime.datetime(2016, 5, 11)) == 1462924800000.0
     assert bus.convert_datetime_type(datetime.timedelta(3000)) == 259200000000.0
     assert bus.convert_datetime_type(datetime.date(2016, 5, 11)) == 1462924800000.0