Repository: pandas-dev/pandas

Issue:
AmbiguousTimeError when constructing a Series with DST change
In Pandas 0.17, if I construct a `Series` which contains a day light saving transition, then Pandas crashes with an `AmbiguousTimeError`.

Here's the minimal code example to reproduce the bug:

``` python
import pandas as pd
index = pd.date_range("2013-10-26 23:00", "2013-10-27 01:00",
                      tz="Europe/London", freq="H")
series = pd.Series(0, index=index)
```

And here's the traceback:

``` python
In [4]: ---------------------------------------------------------------------------
AmbiguousTimeError                        Traceback (most recent call last)
<ipython-input-4-5deb3c83fafd> in <module>()
----> 1 __pyfile = open('''/tmp/py7201vZi''');exec(compile(__pyfile.read(), '''/home/jack/temp/pandas_dst_bug.py''', 'exec'));__pyfile.close(); import os; os.remove('''/tmp/py7201vZi''')

/home/jack/temp/pandas_dst_bug.py in <module>()
      2 index = pd.date_range("2013-10-26 23:00", "2013-10-27 01:00",
      3                       tz="Europe/London", freq="H")
----> 4 series = pd.Series(0, index=index)


/usr/local/lib/python2.7/dist-packages/pandas/tseries/index.pyc in date_range(start, end, periods, freq, tz, normalize, name, closed)
   1912     return DatetimeIndex(start=start, end=end, periods=periods,
   1913                          freq=freq, tz=tz, normalize=normalize, name=name,
-> 1914                          closed=closed)
   1915 
   1916 

/usr/local/lib/python2.7/dist-packages/pandas/util/decorators.pyc in wrapper(*args, **kwargs)
     87                 else:
     88                     kwargs[new_arg_name] = new_arg_value
---> 89             return func(*args, **kwargs)
     90         return wrapper
     91     return _deprecate_kwarg

/usr/local/lib/python2.7/dist-packages/pandas/tseries/index.pyc in __new__(cls, data, freq, start, end, periods, copy, name, tz, verify_integrity, normalize, closed, ambiguous, dtype, **kwargs)
    234             return cls._generate(start, end, periods, name, freq,
    235                                  tz=tz, normalize=normalize, closed=closed,
--> 236                                  ambiguous=ambiguous)
    237 
    238         if not isinstance(data, (np.ndarray, Index, ABCSeries)):

/usr/local/lib/python2.7/dist-packages/pandas/tseries/index.pyc in _generate(cls, start, end, periods, name, offset, tz, normalize, ambiguous, closed)
    450 
    451                 if end is not None and end.tz is None:
--> 452                     end = end.tz_localize(tz)
    453 
    454             if start and end:

pandas/tslib.pyx in pandas.tslib.Timestamp.tz_localize (pandas/tslib.c:11965)()

pandas/tslib.pyx in pandas.tslib.tz_localize_to_utc (pandas/tslib.c:64516)()

AmbiguousTimeError: Cannot infer dst time from Timestamp('2013-10-27 01:00:00'), try using the 'ambiguous' argument
```

I'm not certain but I expect this bug is related to #11619

This bug report follows on from #11624

I'm 99.999% certain that this bug did not exist in Pandas 0.16.2

```
In [30]: show_versions()

INSTALLED VERSIONS
------------------
commit: None
python: 2.7.9.final.0
python-bits: 64
OS: Linux
OS-release: 3.19.0-33-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8

pandas: 0.17.0
nose: 1.3.7
pip: 1.5.6
setuptools: 15.2
Cython: 0.23.1
numpy: 1.10.1
scipy: 0.16.0
statsmodels: 0.6.1
IPython: 4.0.0
sphinx: 1.2.3
patsy: 0.3.0
dateutil: 2.4.2
pytz: 2015.7
blosc: None
bottleneck: None
tables: 3.2.2
numexpr: 2.4.6
matplotlib: 1.4.3
openpyxl: None
xlrd: 0.9.2
xlwt: None
xlsxwriter: None
lxml: None
bs4: 4.3.2
html5lib: 0.999
httplib2: 0.9
apiclient: None
sqlalchemy: None
pymysql: None
psycopg2: 2.5.3 (dt dec pq3 ext)
```

Finally, just to say another huge thank you to everyone who supports Pandas.  It must be a huge amount of work and I am hugely grateful.  Pandas is an awesome tool.  Thank you.



Discussion:

Resolving patch:
diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py
--- a/pandas/tseries/index.py
+++ b/pandas/tseries/index.py
@@ -355,7 +355,7 @@ def __new__(cls, data=None,
             if freq is not None and not freq_infer:
                 inferred = subarr.inferred_freq
                 if inferred != freq.freqstr:
-                    on_freq = cls._generate(subarr[0], None, len(subarr), None, freq, tz=tz)
+                    on_freq = cls._generate(subarr[0], None, len(subarr), None, freq, tz=tz, ambiguous=ambiguous)
                     if not np.array_equal(subarr.asi8, on_freq.asi8):
                         raise ValueError('Inferred frequency {0} from passed dates does not '
                                          'conform to passed frequency {1}'.format(inferred, freq.freqstr))
@@ -440,17 +440,17 @@ def _generate(cls, start, end, periods, name, offset,
             if inferred_tz is None and tz is not None:
                 # naive dates
                 if start is not None and start.tz is None:
-                    start = start.tz_localize(tz)
+                    start = start.tz_localize(tz, ambiguous=False)
 
                 if end is not None and end.tz is None:
-                    end = end.tz_localize(tz)
+                    end = end.tz_localize(tz, ambiguous=False)
 
             if start and end:
                 if start.tz is None and end.tz is not None:
-                    start = start.tz_localize(end.tz)
+                    start = start.tz_localize(end.tz, ambiguous=False)
 
                 if end.tz is None and start.tz is not None:
-                    end = end.tz_localize(start.tz)
+                    end = end.tz_localize(start.tz, ambiguous=False)
 
             if _use_cached_range(offset, _normalized, start, end):
                 index = cls._cached_range(start, end, periods=periods,
@@ -1884,7 +1884,7 @@ def _generate_regular_range(start, end, periods, offset):
 
 
 def date_range(start=None, end=None, periods=None, freq='D', tz=None,
-               normalize=False, name=None, closed=None):
+               normalize=False, name=None, closed=None, **kwargs):
     """
     Return a fixed frequency datetime index, with day (calendar) as the default
     frequency
@@ -1920,11 +1920,11 @@ def date_range(start=None, end=None, periods=None, freq='D', tz=None,
     """
     return DatetimeIndex(start=start, end=end, periods=periods,
                          freq=freq, tz=tz, normalize=normalize, name=name,
-                         closed=closed)
+                         closed=closed, **kwargs)
 
 
 def bdate_range(start=None, end=None, periods=None, freq='B', tz=None,
-                normalize=True, name=None, closed=None):
+                normalize=True, name=None, closed=None, **kwargs):
     """
     Return a fixed frequency datetime index, with business day as the default
     frequency
@@ -1961,7 +1961,7 @@ def bdate_range(start=None, end=None, periods=None, freq='B', tz=None,
 
     return DatetimeIndex(start=start, end=end, periods=periods,
                          freq=freq, tz=tz, normalize=normalize, name=name,
-                         closed=closed)
+                         closed=closed, **kwargs)
 
 
 def cdate_range(start=None, end=None, periods=None, freq='C', tz=None,


Test patch:
diff --git a/pandas/io/tests/test_pytables.py b/pandas/io/tests/test_pytables.py
--- a/pandas/io/tests/test_pytables.py
+++ b/pandas/io/tests/test_pytables.py
@@ -3049,7 +3049,7 @@ def test_select_dtypes(self):
             result = store.select(
                 'df4', where='values>2.0')
             tm.assert_frame_equal(expected, result)
-        
+
         # test selection with comparison against numpy scalar
         # GH 11283
         with ensure_clean_store(self.path) as store:
@@ -4988,6 +4988,21 @@ def test_legacy_datetimetz_object(self):
             result = store['df']
             assert_frame_equal(result, expected)
 
+    def test_dst_transitions(self):
+        # make sure we are not failing on transaitions
+        with ensure_clean_store(self.path) as store:
+            times = pd.date_range("2013-10-26 23:00", "2013-10-27 01:00",
+                                  tz="Europe/London",
+                                  freq="H",
+                                  ambiguous='infer')
+
+            for i in [times, times+pd.Timedelta('10min')]:
+                _maybe_remove(store, 'df')
+                df = DataFrame({'A' : range(len(i)), 'B' : i }, index=i)
+                store.append('df',df)
+                result = store.select('df')
+                assert_frame_equal(result, df)
+
 def _test_sort(obj):
     if isinstance(obj, DataFrame):
         return obj.reindex(sorted(obj.index))
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -15439,6 +15439,26 @@ def test_to_csv_date_format(self):
 
                 assert_frame_equal(test, nat_frame)
 
+    def test_to_csv_with_dst_transitions(self):
+        pname = '__tmp_to_csv_date_format_with_dst__'
+        with ensure_clean(pname) as path:
+            # make sure we are not failing on transitions
+            times = pd.date_range("2013-10-26 23:00", "2013-10-27 01:00",
+                                  tz="Europe/London",
+                                  freq="H",
+                                  ambiguous='infer')
+
+            for i in [times, times+pd.Timedelta('10s')]:
+                df = DataFrame({'A' : range(len(i))}, index=i)
+                df.to_csv(path,index=True)
+
+                # we have to reconvert the index as we
+                # don't parse the tz's
+                result = read_csv(path,index_col=0)
+                result.index = pd.to_datetime(result.index).tz_localize('UTC').tz_convert('Europe/London')
+                assert_frame_equal(result,df)
+
+
     def test_concat_empty_dataframe_dtypes(self):
         df = DataFrame(columns=list("abc"))
         df['a'] = df['a'].astype(np.bool_)
diff --git a/pandas/tseries/tests/test_timezones.py b/pandas/tseries/tests/test_timezones.py
--- a/pandas/tseries/tests/test_timezones.py
+++ b/pandas/tseries/tests/test_timezones.py
@@ -502,6 +502,22 @@ def test_ambiguous_flags(self):
         localized_is_dst = dr.tz_localize(tz, ambiguous=is_dst)
         self.assert_numpy_array_equal(localized, localized_is_dst)
 
+        # construction with an ambiguous end-point
+        # GH 11626
+        tz=self.tzstr("Europe/London")
+
+        def f():
+            date_range("2013-10-26 23:00", "2013-10-27 01:00",
+                       tz="Europe/London",
+                       freq="H")
+            self.assertRaises(pytz.AmbiguousTimeError, f)
+        times = date_range("2013-10-26 23:00", "2013-10-27 01:00",
+                              freq="H",
+                              tz=tz,
+                              ambiguous='infer')
+        self.assertEqual(times[0],Timestamp('2013-10-26 23:00',tz=tz))
+        self.assertEqual(times[-1],Timestamp('2013-10-27 01:00',tz=tz))
+
     def test_ambiguous_nat(self):
         tz = self.tz('US/Eastern')
         times = ['11/06/2011 00:00', '11/06/2011 01:00',