Repository: pandas-dev/pandas

Issue:
BUG: Incorrect localization of naive time string with Series and datetime[ns, tz] dtype
```python
In [2]: ts_string = '2013-01-01 00:00:00' 

In [3]: pd.Series([ts_string], dtype='datetime64[ns, CET]')
Out[3]: 
0   2013-01-01 01:00:00+01:00
dtype: datetime64[ns, CET]

In [4]: pd.Series([pd.Timestamp(ts_string, tz='CET')])
Out[4]: 
0   2013-01-01 00:00:00+01:00
dtype: datetime64[ns, CET]
```
#### Problem description

It appears that a Series will localize a naive string to UTC first and then convert it to the specified timezone specified in `dtype` instead of immediately localizing the Series to the timezone.

Note: It looks to work correctly for numeric timestamps:

```
In [11]: ts = 1.5e15

In [12]: pd.Series([ts], dtype='datetime64[ns, CET]')
Out[12]: 
0   1970-01-18 09:40:00+01:00
dtype: datetime64[ns, CET]

In [13]: pd.Series([pd.Timestamp(ts, tz='CET')])
Out[13]: 
0   1970-01-18 09:40:00+01:00
dtype: datetime64[ns, CET]

```
#### Expected Output
```
Out[4]: 
0   2013-01-01 00:00:00+01:00
```
#### Output of ``pd.show_versions()``

<details>

```
INSTALLED VERSIONS
------------------
commit: 0d676a3ccf1d7aa986416a7488b941496f936d98
python: 2.7.13.final.0
python-bits: 64
OS: Linux
OS-release: 4.4.0-45-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: None.None

pandas: 0.21.0.dev+405.g0d676a3
pytest: 3.2.1
pip: 9.0.1
setuptools: 27.2.0
Cython: 0.26
numpy: 1.13.1
scipy: 0.19.1
pyarrow: None
xarray: None
IPython: 5.3.0
sphinx: 1.5.6
patsy: 0.4.1
dateutil: 2.6.1
pytz: 2017.2
blosc: None
bottleneck: 1.2.1
tables: 3.4.2
numexpr: 2.6.2
feather: None
matplotlib: 2.0.2
openpyxl: 2.4.8
xlrd: 1.0.0
xlwt: 1.2.0
xlsxwriter: 0.9.8
lxml: 3.8.0
bs4: 4.3.2
html5lib: 0.9999999
sqlalchemy: 1.1.13
pymysql: 0.7.9.None
psycopg2: 2.7.1 (dt dec pq3 ext lo64)
jinja2: 2.9.6
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
```
</details>



Discussion:
Weird and buggy...nice catch!  PR to patch is welcome!
Looks like the culprit is [here](https://github.com/pandas-dev/pandas/blob/83436af8ae1ccad49b7ceac7471c060d823d10ab/pandas/core/dtypes/cast.py#L970). Not completely sure why the comment mentions things have to be UTC at this point but will attempt to change and see if anything breaks. 

Resolving patch:
diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py
--- a/pandas/core/dtypes/cast.py
+++ b/pandas/core/dtypes/cast.py
@@ -20,7 +20,7 @@
                      is_integer_dtype,
                      is_datetime_or_timedelta_dtype,
                      is_bool_dtype, is_scalar,
-                     _string_dtypes,
+                     is_string_dtype, _string_dtypes,
                      pandas_dtype,
                      _ensure_int8, _ensure_int16,
                      _ensure_int32, _ensure_int64,
@@ -1003,12 +1003,20 @@ def maybe_cast_to_datetime(value, dtype, errors='raise'):
                         if is_datetime64:
                             value = to_datetime(value, errors=errors)._values
                         elif is_datetime64tz:
-                            # input has to be UTC at this point, so just
-                            # localize
-                            value = (to_datetime(value, errors=errors)
-                                     .tz_localize('UTC')
-                                     .tz_convert(dtype.tz)
-                                     )
+                            # The string check can be removed once issue #13712
+                            # is solved. String data that is passed with a
+                            # datetime64tz is assumed to be naive which should
+                            # be localized to the timezone.
+                            is_dt_string = is_string_dtype(value)
+                            value = to_datetime(value, errors=errors)
+                            if is_dt_string:
+                                # Strings here are naive, so directly localize
+                                value = value.tz_localize(dtype.tz)
+                            else:
+                                # Numeric values are UTC at this point,
+                                # so localize and convert
+                                value = (value.tz_localize('UTC')
+                                         .tz_convert(dtype.tz))
                         elif is_timedelta64:
                             value = to_timedelta(value, errors=errors)._values
                     except (AttributeError, ValueError, TypeError):


Test patch:
diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py
--- a/pandas/tests/series/test_constructors.py
+++ b/pandas/tests/series/test_constructors.py
@@ -707,6 +707,14 @@ def test_constructor_with_datetime_tz(self):
         expected = Series(pd.DatetimeIndex(['NaT', 'NaT'], tz='US/Eastern'))
         assert_series_equal(s, expected)
 
+    @pytest.mark.parametrize('arg',
+                             ['2013-01-01 00:00:00', pd.NaT, np.nan, None])
+    def test_constructor_with_naive_string_and_datetimetz_dtype(self, arg):
+        # GH 17415: With naive string
+        result = Series([arg], dtype='datetime64[ns, CET]')
+        expected = Series(pd.Timestamp(arg)).dt.tz_localize('CET')
+        assert_series_equal(result, expected)
+
     def test_construction_interval(self):
         # construction from interval & array of intervals
         index = IntervalIndex.from_breaks(np.arange(3), closed='right')