Skip to content

Commit 58b7192

Browse files
committed
Handle pandas Timestamp scalars in orjson engine
Add test for Timestamp and lists of time values
1 parent d4068de commit 58b7192

File tree

2 files changed

+33
-7
lines changed

2 files changed

+33
-7
lines changed

packages/python/plotly/plotly/io/_json.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,9 +490,9 @@ def clean_to_json_compatible(obj, **kwargs):
490490
return np.ascontiguousarray(obj.values)
491491
elif obj.dtype.kind == "M":
492492
if isinstance(obj, pd.Series):
493-
dt_values = obj.dt.to_pydatetime().tolist()
493+
dt_values = obj.dt.tz_localize(None).dt.to_pydatetime().tolist()
494494
else: # DatetimeIndex
495-
dt_values = obj.to_pydatetime().tolist()
495+
dt_values = obj.tz_localize(None).to_pydatetime().tolist()
496496

497497
if not datetime_allowed:
498498
# Note: We don't need to handle dropping timezones here because
@@ -514,13 +514,18 @@ def clean_to_json_compatible(obj, **kwargs):
514514
if np and isinstance(obj, np.datetime64):
515515
return str(obj)
516516
else:
517+
res = None
517518
try:
518519
# Need to drop timezone for scalar datetimes. Don't need to convert
519520
# to string since engine can do that
520-
return obj.replace(tzinfo=None)
521-
except AttributeError:
521+
res = obj.replace(tzinfo=None)
522+
res = res.to_pydatetime()
523+
except(TypeError, AttributeError):
522524
pass
523525

526+
if res is not None:
527+
return res
528+
524529
# Try .tolist() convertible, do not recurse inside
525530
try:
526531
return obj.tolist()

packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ def object_numpy_array(request):
102102
datetime.datetime(2003, 7, 12, 8, 34, 22),
103103
datetime.datetime.now(),
104104
np.datetime64(datetime.datetime.utcnow()),
105+
pd.Timestamp(datetime.datetime.now()),
105106
eastern.localize(datetime.datetime(2003, 7, 12, 8, 34, 22)),
106107
eastern.localize(datetime.datetime.now()),
108+
pd.Timestamp(datetime.datetime.now(), tzinfo=eastern),
107109
],
108110
)
109111
def datetime_value(request):
@@ -112,6 +114,7 @@ def datetime_value(request):
112114

113115
@pytest.fixture(
114116
params=[
117+
list, # plain list of datetime values
115118
lambda a: pd.DatetimeIndex(a), # Pandas DatetimeIndex
116119
lambda a: pd.Series(pd.DatetimeIndex(a)), # Pandas Datetime Series
117120
lambda a: pd.DatetimeIndex(a).values, # Numpy datetime64 array
@@ -162,13 +165,31 @@ def test_datetime(datetime_value, engine, pretty):
162165

163166

164167
def test_datetime_arrays(datetime_array, engine, pretty):
168+
if engine == "legacy":
169+
pytest.skip("legacy encoder doesn't strip timezone from datetimes arrays")
170+
165171
value = build_test_dict(datetime_array)
166172
result = pio.to_json_plotly(value, engine=engine)
167173

168-
if isinstance(datetime_array, pd.Series):
169-
dt_values = [d.isoformat() for d in datetime_array.dt.to_pydatetime().tolist()]
174+
def to_str(v):
175+
try:
176+
v = v.replace(tzinfo=None)
177+
except (TypeError, AttributeError):
178+
pass
179+
180+
try:
181+
v = v.isoformat(sep="T")
182+
except (TypeError, AttributeError):
183+
pass
184+
185+
return str(v)
186+
187+
if isinstance(datetime_array, list):
188+
dt_values = [to_str(d) for d in datetime_array]
189+
elif isinstance(datetime_array, pd.Series):
190+
dt_values = [to_str(d) for d in datetime_array.dt.to_pydatetime().tolist()]
170191
elif isinstance(datetime_array, pd.DatetimeIndex):
171-
dt_values = [d.isoformat() for d in datetime_array.to_pydatetime().tolist()]
192+
dt_values = [to_str(d) for d in datetime_array.to_pydatetime().tolist()]
172193
else: # numpy datetime64 array
173194
dt_values = datetime_array.tolist()
174195

0 commit comments

Comments
 (0)