Skip to content

Commit 7106d68

Browse files
jonasvddjvddemmanuel-ferdmanivanovmg
authored
Fix for [BUG] Error handling timezones #305 (#318)
* fix: check if update_data contains update before batch_update * add test + avoid same error when verbose=True * 🧹 create _hf_data_container if correct trace type * 🙏 python 3.7 not supported on Apple Silicon * remove WIP * 🖊️ more verbose asserts * 🖊️ more verbose asserts * 🙏 more sleep time * 🙏 * 🙌 * 🤔 fix for [BUG] Error handling timezones #305 * 🙈 linting * 💨 Refactor timezone handling in PlotlyAggregatorParser * Update minmax operator image Signed-off-by: Emmanuel Ferdman <[email protected]> * Drop duplicate sentence * Feat/plotly6 (#338) * Parametrize test_utils.py on is_figure * 🔍 remove dtype parsing as orjon>3.10 supports float16 #118 * 💪 refactor: streamline JupyterDash integration and remove unused persistent inline logic * 💨 move construct_update_data_patch method into the FigureResampler class * 🐐 refactor: enhance test utilities and add support for Plotly>=6 data handling * 🙏 enhance serialization tests for plotly>6 * 📝 remove debug print statement and enhance type handling for hf_x * 🔒 update dependency versions in pyproject.toml to Support plotly 6 #334 * 🔍 drop python3.7 CI workflow and upgrade upload-artifact action * 🙏 fix pickling of figurewidget resampler * 🙏 fix tests * 💨 migration of code towards new upload artifact * 💪 enhance CI workflow to improve test result uploads and add retention settings * 🕳️ fix: ensure correct dtype handling for aggregated x indices in PlotlyAggregatorParser * ⬆️ chore: update dependency constraints for pandas and pyarrow in pyproject.toml * 🙈 fix linting * 🔍 fix: correct spelling in streamlit_app.py comments and update dash-extensions and pyarrow versions in requirements.txt * ⬆️ chore: update ipywidgets version constraint to allow for newer versions * 🚧 test: set random seed for reproducibility in test_wrap_aggregate * 🙈 chore: update ipywidgets version constraint for serialization support * 🙈 * 🔍 ci: conditionally skip tests on Python 3.12 for Ubuntu (as it keeps hanging in github actions) * 🔍 ci: exclude Python 3.12 on Ubuntu from test matrix to prevent hangs * 🖊️ review code * 🧹 cleanup comments --------- Co-authored-by: Maxim Ivanov <[email protected]> Co-authored-by: jeroen <[email protected]> * 🤔 fix for [BUG] Error handling timezones #305 * 🙈 linting * 💨 Refactor timezone handling in PlotlyAggregatorParser * 📌 bug: Fix timezone handling for DST in PlotlyAggregatorParser and update tests --------- Signed-off-by: Emmanuel Ferdman <[email protected]> Co-authored-by: jvdd <[email protected]> Co-authored-by: Jeroen Van Der Donckt <[email protected]> Co-authored-by: Emmanuel Ferdman <[email protected]> Co-authored-by: Maxim Ivanov <[email protected]>
1 parent 18da98f commit 7106d68

File tree

2 files changed

+105
-6
lines changed

2 files changed

+105
-6
lines changed

plotly_resampler/aggregation/plotly_aggregator_parser.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ def to_same_tz(
3838
return None
3939
elif reference_tz is not None:
4040
if ts.tz is not None:
41-
assert ts.tz.__str__() == reference_tz.__str__()
41+
# compare if these two have the same timezone / offset
42+
try:
43+
assert ts.tz.__str__() == reference_tz.__str__()
44+
except AssertionError:
45+
assert ts.utcoffset() == reference_tz.utcoffset(ts.tz_convert(None))
4246
return ts
4347
else: # localize -> time remains the same
4448
return ts.tz_localize(reference_tz)
@@ -78,7 +82,15 @@ def get_start_end_indices(hf_trace_data, axis_type, start, end) -> Tuple[int, in
7882
# convert start & end to the same timezone
7983
if isinstance(hf_trace_data["x"], pd.DatetimeIndex):
8084
tz = hf_trace_data["x"].tz
81-
assert start.tz == end.tz
85+
try:
86+
assert start.tz.__str__() == end.tz.__str__()
87+
except (TypeError, AssertionError):
88+
# This fix is needed for DST (when the timezone is not fixed)
89+
assert start.tz_localize(None) == start.tz_convert(tz).tz_localize(
90+
None
91+
)
92+
assert end.tz_localize(None) == end.tz_convert(tz).tz_localize(None)
93+
8294
start = PlotlyAggregatorParser.to_same_tz(start, tz)
8395
end = PlotlyAggregatorParser.to_same_tz(end, tz)
8496

tests/test_figure_resampler.py

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,74 @@ def test_tz_xaxis_range():
754754
assert len(out[2]["x"]) == 2000
755755

756756

757+
def test_compare_tz_with_fixed_offset():
758+
# related: https://github.com/predict-idlab/plotly-resampler/issues/305
759+
fig = FigureResampler()
760+
761+
x = pd.date_range("2024-04-01T00:00:00", "2025-01-01T00:00:00", freq="h")
762+
x = x.tz_localize("Asia/Taipei")
763+
y = np.random.randn(len(x))
764+
765+
fig.add_trace(
766+
go.Scattergl(x=x, y=y, name="demo", mode="lines+markers"),
767+
max_n_samples=int(len(x) * 0.2),
768+
)
769+
770+
relayout_data = {
771+
"xaxis.range[0]": "2024-04-27T08:00:00+08:00",
772+
"xaxis.range[1]": "2024-05-04T17:15:39.491031+08:00",
773+
}
774+
775+
fig.construct_update_data_patch(relayout_data)
776+
777+
778+
def test_compare_tz_with_fixed_offset_2():
779+
# related: https://github.com/predict-idlab/plotly-resampler/issues/305
780+
fig = FigureResampler()
781+
782+
x = pd.date_range("2024-04-01T00:00:00", "2025-01-01T00:00:00", freq="h")
783+
x = x.tz_localize("UTC")
784+
x = x.tz_convert("Canada/Pacific")
785+
y = np.random.randn(len(x))
786+
787+
fig.add_trace(
788+
go.Scattergl(x=x, y=y, name="demo", mode="lines+markers"),
789+
max_n_samples=int(len(x) * 0.2),
790+
)
791+
792+
relayout_data = {
793+
"xaxis.range[0]": pd.Timestamp("2024-03-01T00:00:00").tz_localize(
794+
"Canada/Pacific"
795+
),
796+
"xaxis.range[1]": pd.Timestamp("2024-03-31T00:00:00").tz_localize(
797+
"Canada/Pacific"
798+
),
799+
}
800+
801+
fig.construct_update_data_patch(relayout_data)
802+
803+
804+
def test_relayout_tz_DST():
805+
# related: https://github.com/predict-idlab/plotly-resampler/issues/305
806+
fig = FigureResampler()
807+
808+
x = pd.date_range(
809+
"2024-09-27 17:00:00", "2024-12-11 16:00:00", tz="US/Pacific", freq="1h"
810+
)
811+
y = np.random.randn(len(x))
812+
fig.add_trace(
813+
go.Scattergl(x=x, y=y, name="demo", mode="lines+markers"),
814+
max_n_samples=int(len(x) * 0.2),
815+
)
816+
817+
relayout_data = {
818+
"xaxis.range[0]": "2024-09-27T17:00:00-07:00",
819+
"xaxis.range[1]": "2024-12-12T15:59:00-08:00",
820+
}
821+
822+
fig.construct_update_data_patch(relayout_data)
823+
824+
757825
def test_datetime_hf_x_no_index():
758826
df = pd.DataFrame(
759827
{"timestamp": pd.date_range("2020-01-01", "2020-01-02", freq="1s")}
@@ -789,8 +857,8 @@ def test_multiple_timezones_in_single_x_index__datetimes_and_timestamps():
789857
# TODO: can be improved with pytest parametrize
790858
y = np.arange(20)
791859

792-
index1 = pd.date_range("2018-01-01", periods=10, freq="H", tz="US/Eastern")
793-
index2 = pd.date_range("2018-01-02", periods=10, freq="H", tz="Asia/Dubai")
860+
index1 = pd.date_range("2018-01-01", periods=10, freq="h", tz="US/Eastern")
861+
index2 = pd.date_range("2018-01-02", periods=10, freq="h", tz="Asia/Dubai")
794862
index_timestamps = index1.append(index2)
795863
assert all(isinstance(x, pd.Timestamp) for x in index_timestamps)
796864
index1_datetimes = pd.Index([x.to_pydatetime() for x in index1])
@@ -1009,7 +1077,7 @@ def test_time_tz_slicing_different_timestamp():
10091077
cs = [
10101078
dr,
10111079
dr.tz_localize(None).tz_localize("Europe/Amsterdam"),
1012-
dr.tz_convert("Europe/Brussels"),
1080+
dr.tz_convert("Europe/Lisbon"),
10131081
dr.tz_convert("Australia/Perth"),
10141082
dr.tz_convert("Australia/Canberra"),
10151083
]
@@ -1027,6 +1095,25 @@ def test_time_tz_slicing_different_timestamp():
10271095
hf_data_dict, hf_data_dict["axis_type"], t_start, t_stop
10281096
)
10291097

1098+
# THESE have the same timezone offset -> no AssertionError should be raised
1099+
cs = [
1100+
dr.tz_localize(None).tz_localize("Europe/Amsterdam"),
1101+
dr.tz_convert("Europe/Brussels"),
1102+
dr.tz_convert("Europe/Oslo"),
1103+
dr.tz_convert("Europe/Paris"),
1104+
dr.tz_convert("Europe/Rome"),
1105+
]
1106+
1107+
for i, s in enumerate(cs):
1108+
t_start, t_stop = sorted(s.iloc[np.random.randint(0, n, 2)].index)
1109+
t_start = t_start.tz_convert(cs[(i + 1) % len(cs)].index.tz)
1110+
t_stop = t_stop.tz_convert(cs[(i + 1) % len(cs)].index.tz)
1111+
1112+
hf_data_dict = construct_hf_data_dict(s.index, s.values)
1113+
start_idx, end_idx = PlotlyAggregatorParser.get_start_end_indices(
1114+
hf_data_dict, hf_data_dict["axis_type"], t_start, t_stop
1115+
)
1116+
10301117

10311118
def test_different_tz_no_tz_series_slicing():
10321119
n = 60 * 60 * 24 * 3
@@ -1092,7 +1179,7 @@ def test_multiple_tz_no_tz_series_slicing():
10921179

10931180
# Now the assumption cannot be made that s has the same time-zone as the
10941181
# timestamps -> AssertionError will be raised.
1095-
with pytest.raises(AssertionError):
1182+
with pytest.raises((TypeError, AssertionError)):
10961183
hf_data_dict = construct_hf_data_dict(s.tz_localize(None).index, s.values)
10971184
PlotlyAggregatorParser.get_start_end_indices(
10981185
hf_data_dict, hf_data_dict["axis_type"], t_start, t_stop

0 commit comments

Comments
 (0)