Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/bokeh/core/property/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
from __future__ import annotations

import logging # isort:skip
from bokeh.core.property.bases import Init, Property
from bokeh.core.property.singletons import Undefined
from bokeh.util.serialization import convert_date_to_datetime

log = logging.getLogger(__name__)

#-----------------------------------------------------------------------------
Expand Down Expand Up @@ -89,12 +93,23 @@ def __init__(self, default: Init[str | datetime.date | datetime.datetime] = Unde
def transform(self, value: Any) -> Any:
value = super().transform(value)


# Fast path: already a datetime.datetime
if isinstance(value, datetime.datetime):
# UTC-ize if needed (handled by convert_date_to_datetime)
return convert_date_to_datetime(value)

# String to datetime conversion
if isinstance(value, str):
value = datetime.datetime.fromisoformat(value)

# Handled by serialization in protocol.py for now, except for Date
return convert_date_to_datetime(value)

# datetime.date but not datetime.datetime
if isinstance(value, datetime.date):
value = convert_date_to_datetime(value)
return convert_date_to_datetime(value)


return value

Expand Down
18 changes: 14 additions & 4 deletions src/bokeh/util/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def __getattr__(name: str) -> Any:
NP_EPOCH = np.datetime64(0, 'ms')
NP_MS_DELTA = np.timedelta64(1, 'ms')

DT_EPOCH = dt.datetime.fromtimestamp(0, tz=dt.timezone.utc)
DT_EPOCH = dt.datetime(1970, 1, 1, tzinfo=dt.timezone.utc)

__doc__ = format_docstring(__doc__, binary_array_types="\n".join(f"* ``np.{x}``" for x in BINARY_ARRAY_TYPES))

Expand Down Expand Up @@ -135,16 +135,26 @@ def is_timedelta_type(obj: Any) -> TypeGuard[dt.timedelta | np.timedelta64]:
return isinstance(obj, (dt.timedelta, np.timedelta64))

def convert_date_to_datetime(obj: dt.date) -> float:
''' Convert a date object to a datetime
""" Convert a date object to a datetime


Args:
obj (date) : the object to convert

Returns:
datetime

'''
return (dt.datetime(*obj.timetuple()[:6], tzinfo=dt.timezone.utc) - DT_EPOCH).total_seconds() * 1000
"""
# Fast path if obj is already a datetime *with* tzinfo UTC
if isinstance(obj, dt.datetime):
if obj.tzinfo is None:
obj = obj.replace(tzinfo=dt.timezone.utc)
else:
obj = obj.astimezone(dt.timezone.utc)
return (obj - DT_EPOCH).total_seconds() * 1000
# obj is a date (not datetime)
obj_dt = dt.datetime(obj.year, obj.month, obj.day, tzinfo=dt.timezone.utc)
return (obj_dt - DT_EPOCH).total_seconds() * 1000

def convert_timedelta_type(obj: dt.timedelta | np.timedelta64) -> float:
''' Convert any recognized timedelta value to floating point absolute
Expand Down