Repository: dask/dask

Issue:
Bug report: DataFrame resample fails for some timezones
When a resample bin falls in a DST change datetime, the resample fails with NonExistentTimeError. This was a bug solved in pandas version 0.24.0 and what they do when a datetime doesnt exist by dst change is making a shift to the closest existing datetime.

To replicate the error copy and paste this:
```
import pandas as pd
import dask.dataframe as ddf
df = pd.DataFrame([[1], [2], [3]], index=[pd.Timestamp("2016-10-15 00:00:00"), pd.Timestamp("2016-10-16 10:00:00"), pd.Timestamp("2016-10-17 00:00:00")])
df.index = df.index.tz_localize('America/Sao_Paulo')
ddf2 = ddf.from_pandas(df, npartitions=1)
ddf2.resample('1D').mean().compute()
```

I suggest changing the following instruction at dask/dataframe/tseries/resample.py in the _resample_series function:
```
pd.date_range(start, end, freq=rule, closed=reindex_closed, name=out.index.name)
```
by the following one:
```
pd.date_range(start.tz_localize(None), end.tz_localize(None), freq=rule, closed=reindex_closed, name=out.index.name).tz_localize(start.tz, nonexistent="shift_forward")
```



Discussion:
Thank you for the excellent bug report and reproducible example @dfonnegra .  I apologise for the long delay in response. 

It sounds like you have a solid idea for a fix.  Would you be willing to submit a PR?

Also, cc'ing resident Pandas expert @TomAugspurger just for his awareness.
@dfonnegra are you interested in working on this? Your suggested fix sounds good.
@TomAugspurger Yes Tom! Sorry, the begining of the year has been a little hard, but I'll submit a PR soon... 
Great, thanks! No rush of course :) 

Resolving patch:
diff --git a/dask/array/svg.py b/dask/array/svg.py
--- a/dask/array/svg.py
+++ b/dask/array/svg.py
@@ -49,7 +49,7 @@ def svg_2d(chunks, offset=(0, 0), skew=(0, 0), size=200, sizes=None):
 
     header = (
         '<svg width="%d" height="%d" style="stroke:rgb(0,0,0);stroke-width:1" >\n'
-        % (max_x + 50, max_y + 50,)
+        % (max_x + 50, max_y + 50)
     )
     footer = "\n</svg>"
 
@@ -87,7 +87,7 @@ def svg_3d(chunks, size=200, sizes=None, offset=(0, 0)):
 
     header = (
         '<svg width="%d" height="%d" style="stroke:rgb(0,0,0);stroke-width:1" >\n'
-        % (max_z + 50, max_y + 50,)
+        % (max_z + 50, max_y + 50)
     )
     footer = "\n</svg>"
 
@@ -154,7 +154,7 @@ def svg_nd(chunks, size=200):
 
     header = (
         '<svg width="%d" height="%d" style="stroke:rgb(0,0,0);stroke-width:1" >\n'
-        % (left, total_height,)
+        % (left, total_height)
     )
     footer = "\n</svg>"
     return header + "\n\n".join(out) + footer
diff --git a/dask/dataframe/tseries/resample.py b/dask/dataframe/tseries/resample.py
--- a/dask/dataframe/tseries/resample.py
+++ b/dask/dataframe/tseries/resample.py
@@ -6,6 +6,7 @@
 from ...base import tokenize
 from ...utils import derived_from
 from ...highlevelgraph import HighLevelGraph
+from .._compat import PANDAS_GT_0240
 
 
 def getnanos(rule):
@@ -30,12 +31,20 @@ def _resample_series(
     out = getattr(series.resample(rule, **resample_kwargs), how)(
         *how_args, **how_kwargs
     )
-    return out.reindex(
-        pd.date_range(
+    if PANDAS_GT_0240:
+        new_index = pd.date_range(
+            start.tz_localize(None),
+            end.tz_localize(None),
+            freq=rule,
+            closed=reindex_closed,
+            name=out.index.name,
+        ).tz_localize(start.tz, nonexistent="shift_forward")
+
+    else:
+        new_index = pd.date_range(
             start, end, freq=rule, closed=reindex_closed, name=out.index.name
-        ),
-        fill_value=fill_value,
-    )
+        )
+    return out.reindex(new_index, fill_value=fill_value)
 
 
 def _resample_bin_and_out_divs(divisions, rule, closed="left", label="left"):


Test patch:
diff --git a/dask/dataframe/tseries/tests/test_resample.py b/dask/dataframe/tseries/tests/test_resample.py
--- a/dask/dataframe/tseries/tests/test_resample.py
+++ b/dask/dataframe/tseries/tests/test_resample.py
@@ -4,6 +4,7 @@
 import pytest
 
 from dask.dataframe.utils import assert_eq, PANDAS_VERSION
+from dask.dataframe._compat import PANDAS_GT_0240
 import dask.dataframe as dd
 
 
@@ -103,6 +104,22 @@ def test_resample_index_name():
     assert ddf.resample("D").mean().head().index.name == "date"
 
 
+@pytest.mark.skipif(not PANDAS_GT_0240, reason="nonexistent not in 0.23 or older")
+def test_series_resample_non_existent_datetime():
+    index = [
+        pd.Timestamp("2016-10-15 00:00:00"),
+        pd.Timestamp("2016-10-16 10:00:00"),
+        pd.Timestamp("2016-10-17 00:00:00"),
+    ]
+    df = pd.DataFrame([[1], [2], [3]], index=index)
+    df.index = df.index.tz_localize("America/Sao_Paulo")
+    ddf = dd.from_pandas(df, npartitions=1)
+    result = ddf.resample("1D").mean()
+    expected = df.resample("1D").mean()
+
+    assert_eq(result, expected)
+
+
 @pytest.mark.skipif(PANDAS_VERSION <= "0.23.4", reason="quantile not in 0.23")
 @pytest.mark.parametrize("agg", ["nunique", "mean", "count", "size", "quantile"])
 def test_common_aggs(agg):