From f38a5c196c6c8bf5e7f8e277257f3b33c5b8f15f Mon Sep 17 00:00:00 2001 From: jonasvdd Date: Mon, 9 Sep 2024 10:41:37 +0200 Subject: [PATCH 1/3] :thinking: fix for [BUG] Error handling timezones #305 --- .../aggregation/plotly_aggregator_parser.py | 11 ++- tests/test_figure_resampler.py | 69 ++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/plotly_resampler/aggregation/plotly_aggregator_parser.py b/plotly_resampler/aggregation/plotly_aggregator_parser.py index c359aa29..6d0c4e7f 100644 --- a/plotly_resampler/aggregation/plotly_aggregator_parser.py +++ b/plotly_resampler/aggregation/plotly_aggregator_parser.py @@ -38,8 +38,12 @@ def to_same_tz( return None elif reference_tz is not None: if ts.tz is not None: - assert ts.tz.__str__() == reference_tz.__str__() - return ts + # compare if these two have the same timezone / offset + print('to same tz', 'ts', ts.tz.__str__(), 'ref', reference_tz.__str__()) + try: + assert ts.tz.__str__() == reference_tz.__str__() + except AssertionError: + assert ts.utcoffset() == reference_tz.utcoffset(ts.tz_convert(None)) else: # localize -> time remains the same return ts.tz_localize(reference_tz) elif reference_tz is None and ts.tz is not None: @@ -78,7 +82,8 @@ def get_start_end_indices(hf_trace_data, axis_type, start, end) -> Tuple[int, in # convert start & end to the same timezone if isinstance(hf_trace_data["x"], pd.DatetimeIndex): tz = hf_trace_data["x"].tz - assert start.tz == end.tz + # print(start.tz.__str__(), end.tz.__str__()) + assert start.tz.__str__() == end.tz.__str__() start = PlotlyAggregatorParser.to_same_tz(start, tz) end = PlotlyAggregatorParser.to_same_tz(end, tz) diff --git a/tests/test_figure_resampler.py b/tests/test_figure_resampler.py index 640914ec..35e496dd 100644 --- a/tests/test_figure_resampler.py +++ b/tests/test_figure_resampler.py @@ -758,6 +758,53 @@ def test_tz_xaxis_range(): assert len(out[2]["x"]) == 2000 +def test_compare_tz_with_fixed_offset(): + # related: https://github.com/predict-idlab/plotly-resampler/issues/305 + fig = FigureResampler() + + x = pd.date_range("2024-04-01T00:00:00", "2025-01-01T00:00:00", freq="H") + x = x.tz_localize("Asia/Taipei") + y = np.random.randn(len(x)) + + fig.add_trace( + go.Scattergl(x=x, y=y, name="demo", mode="lines+markers"), + max_n_samples=int(len(x) * 0.2), + ) + + relayout_data = { + "xaxis.range[0]": "2024-04-27T08:00:00+08:00", + "xaxis.range[1]": "2024-05-04T17:15:39.491031+08:00", + } + + fig.construct_update_data_patch(relayout_data) + + +def test_compare_tz_with_fixed_offset_2(): + # related: https://github.com/predict-idlab/plotly-resampler/issues/305 + fig = FigureResampler() + + x = pd.date_range("2024-04-01T00:00:00", "2025-01-01T00:00:00", freq="H") + x = x.tz_localize("UTC") + x = x.tz_convert("Canada/Pacific") + y = np.random.randn(len(x)) + + fig.add_trace( + go.Scattergl(x=x, y=y, name="demo", mode="lines+markers"), + max_n_samples=int(len(x) * 0.2), + ) + + relayout_data = { + "xaxis.range[0]": pd.Timestamp("2024-03-01T00:00:00").tz_localize( + "Canada/Pacific" + ), + "xaxis.range[1]": pd.Timestamp("2024-03-31T00:00:00").tz_localize( + "Canada/Pacific" + ), + } + + fig.construct_update_data_patch(relayout_data) + + def test_datetime_hf_x_no_index(): df = pd.DataFrame( {"timestamp": pd.date_range("2020-01-01", "2020-01-02", freq="1s")} @@ -1013,7 +1060,7 @@ def test_time_tz_slicing_different_timestamp(): cs = [ dr, dr.tz_localize(None).tz_localize("Europe/Amsterdam"), - dr.tz_convert("Europe/Brussels"), + dr.tz_convert("Europe/Lisbon"), dr.tz_convert("Australia/Perth"), dr.tz_convert("Australia/Canberra"), ] @@ -1031,6 +1078,26 @@ def test_time_tz_slicing_different_timestamp(): hf_data_dict, hf_data_dict["axis_type"], t_start, t_stop ) + # THESE have the same timezone offset -> no AssertionError should be raised + cs = [ + dr.tz_localize(None).tz_localize("Europe/Amsterdam"), + dr.tz_convert("Europe/Brussels"), + dr.tz_convert("Europe/Oslo"), + dr.tz_convert("Europe/Paris"), + dr.tz_convert("Europe/Rome"), + ] + + for i, s in enumerate(cs): + t_start, t_stop = sorted(s.iloc[np.random.randint(0, n, 2)].index) + t_start = t_start.tz_convert(cs[(i + 1) % len(cs)].index.tz) + t_stop = t_stop.tz_convert(cs[(i + 1) % len(cs)].index.tz) + + hf_data_dict = construct_hf_data_dict(s.index, s.values) + start_idx, end_idx = PlotlyAggregatorParser.get_start_end_indices( + hf_data_dict, hf_data_dict["axis_type"], t_start, t_stop + ) + + def test_different_tz_no_tz_series_slicing(): n = 60 * 60 * 24 * 3 From 1f88adbf390598f8180a414418fdbad55f713a87 Mon Sep 17 00:00:00 2001 From: jonasvdd Date: Mon, 9 Sep 2024 10:45:01 +0200 Subject: [PATCH 2/3] :see_no_evil: linting --- plotly_resampler/aggregation/plotly_aggregator_parser.py | 1 - tests/test_figure_resampler.py | 1 - 2 files changed, 2 deletions(-) diff --git a/plotly_resampler/aggregation/plotly_aggregator_parser.py b/plotly_resampler/aggregation/plotly_aggregator_parser.py index 6d0c4e7f..3e4277bd 100644 --- a/plotly_resampler/aggregation/plotly_aggregator_parser.py +++ b/plotly_resampler/aggregation/plotly_aggregator_parser.py @@ -39,7 +39,6 @@ def to_same_tz( elif reference_tz is not None: if ts.tz is not None: # compare if these two have the same timezone / offset - print('to same tz', 'ts', ts.tz.__str__(), 'ref', reference_tz.__str__()) try: assert ts.tz.__str__() == reference_tz.__str__() except AssertionError: diff --git a/tests/test_figure_resampler.py b/tests/test_figure_resampler.py index 35e496dd..205f5a6e 100644 --- a/tests/test_figure_resampler.py +++ b/tests/test_figure_resampler.py @@ -1098,7 +1098,6 @@ def test_time_tz_slicing_different_timestamp(): ) - def test_different_tz_no_tz_series_slicing(): n = 60 * 60 * 24 * 3 dr = pd.Series( From b31dce7110a94f8962be01198de53c06195dcf52 Mon Sep 17 00:00:00 2001 From: jonasvdd Date: Wed, 23 Oct 2024 13:37:57 +1100 Subject: [PATCH 3/3] :dash: Refactor timezone handling in PlotlyAggregatorParser --- plotly_resampler/aggregation/plotly_aggregator_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plotly_resampler/aggregation/plotly_aggregator_parser.py b/plotly_resampler/aggregation/plotly_aggregator_parser.py index 3e4277bd..4f0b7c98 100644 --- a/plotly_resampler/aggregation/plotly_aggregator_parser.py +++ b/plotly_resampler/aggregation/plotly_aggregator_parser.py @@ -43,6 +43,7 @@ def to_same_tz( assert ts.tz.__str__() == reference_tz.__str__() except AssertionError: assert ts.utcoffset() == reference_tz.utcoffset(ts.tz_convert(None)) + return ts else: # localize -> time remains the same return ts.tz_localize(reference_tz) elif reference_tz is None and ts.tz is not None: